c8ed098bfc
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
237 lines
10 KiB
HTML
237 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}期权交易{% endblock %}
|
|
{% block heading %}期权交易{% endblock %}
|
|
{% block breadcrumb %}期权开平仓记录{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
{% set view = request.query_params.get('view', '') %}
|
|
|
|
<style>
|
|
.tabs { display: flex; gap: 0; margin-bottom: 24px; border-bottom: 2px solid var(--border); }
|
|
.tab-btn {
|
|
padding: 10px 20px; border: none; background: none;
|
|
font-size: 0.88rem; font-weight: 500; color: var(--sub);
|
|
cursor: pointer; text-decoration: none; border-bottom: 2px solid transparent;
|
|
margin-bottom: -2px; transition: color .12s, border-color .12s;
|
|
}
|
|
.tab-btn:hover { color: var(--fg); }
|
|
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
|
.tab-panel { display: none; }
|
|
.tab-panel.active { display: block; }
|
|
</style>
|
|
|
|
<div class="tabs">
|
|
<a href="/options/" class="tab-btn{% if view != 'closed' %} active{% endif %}">📋 持仓</a>
|
|
<a href="/options/?view=closed" class="tab-btn{% if view == 'closed' %} active{% endif %}">📊 已平仓</a>
|
|
</div>
|
|
|
|
{% if view != 'closed' %}
|
|
<div class="tab-panel active">
|
|
|
|
{# ── 新建开仓 ── #}
|
|
<div class="section-title">新建开仓</div>
|
|
<div class="form-card" style="margin-bottom:28px;max-width:100%;">
|
|
<form method="post" action="/options/open">
|
|
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:100px;">
|
|
<label>品种</label>
|
|
<select id="productSelect" required onchange="filterContracts()">
|
|
<option value="">--</option>
|
|
{% for pcode in product_contracts %}
|
|
<option value="{{ pcode }}">{{ pcode }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:120px;">
|
|
<label>合约</label>
|
|
<select name="contract_code" id="contractSelect" required disabled>
|
|
<option value="">-- 先选品种 --</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
|
|
<label>类型</label>
|
|
<select name="option_type" required>
|
|
{% for v, label in option_types %}
|
|
<option value="{{ v }}">{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:90px;">
|
|
<label>行权价</label>
|
|
<input type="number" step="any" name="strike_price" required placeholder="1300" style="font-size:0.9rem;">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
|
|
<label>权利金</label>
|
|
<input type="number" step="any" name="open_price" required placeholder="25" style="font-size:0.9rem;">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:130px;">
|
|
<label>开仓日期</label>
|
|
<input type="date" name="open_date" value="{{ today }}" required style="font-size:0.85rem;">
|
|
</div>
|
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
|
|
<label>手续费</label>
|
|
<input type="number" step="any" name="open_fee" value="0" style="font-size:0.9rem;">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" style="font-size:0.82rem;padding:8px 18px;">开仓</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{# ── 持仓列表 ── #}
|
|
<div class="section-title">持仓中 · {{ open_trades|length }} 笔</div>
|
|
|
|
{% if open_trades %}
|
|
<div class="table-wrap">
|
|
<table>
|
|
<tr><th>品种</th><th>合约</th><th>类型</th><th>行权价</th><th>权利金</th><th>开仓日期</th><th>手续费</th><th>操作</th></tr>
|
|
{% for t in open_trades %}
|
|
<tr>
|
|
<td>{{ t.product_code }}</td>
|
|
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
|
|
<td>
|
|
{% if t.option_type == 'C' %}
|
|
<span class="badge badge-up">C 看涨</span>
|
|
{% else %}
|
|
<span class="badge badge-down">P 看跌</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ t.strike_price }}</td>
|
|
<td>{{ t.open_price }}</td>
|
|
<td>{{ t.open_date }}</td>
|
|
<td>{{ t.open_fee or 0 }}</td>
|
|
<td>
|
|
<button style="background:var(--accent);color:#fff;border:none;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
|
|
onclick="showCloseForm({{ t.id }})">平仓</button>
|
|
<form method="post" action="/options/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
|
|
<button style="background:var(--danger-bg);color:var(--danger-fg);border:1px solid var(--danger);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删除</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div style="text-align:center;padding:40px;color:var(--sub);">暂无持仓</div>
|
|
{% endif %}
|
|
|
|
{# ── 平仓弹窗 ── #}
|
|
<div id="closeFormOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:99;" onclick="hideCloseForm()"></div>
|
|
<div id="closeFormBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:100;width:360px;max-width:90vw;">
|
|
<h3 style="margin-bottom:16px;">平仓</h3>
|
|
<form method="post" id="closeForm">
|
|
<div class="form-group">
|
|
<label>平仓日期</label>
|
|
<input type="date" name="close_date" value="{{ today }}" required style="font-size:0.85rem;">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>平仓价格</label>
|
|
<input type="number" step="any" name="close_price" required style="font-size:0.9rem;">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>手续费</label>
|
|
<input type="number" step="any" name="close_fee" value="0" style="font-size:0.9rem;">
|
|
</div>
|
|
<div style="display:flex;gap:10px;justify-content:flex-end;">
|
|
<button type="button" class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideCloseForm()">取消</button>
|
|
<button type="submit" class="btn btn-primary">确认平仓</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
{% else %}
|
|
<div class="tab-panel active">
|
|
{# ═══════════════ 已平仓 ═══════════════ #}
|
|
|
|
{% if closed_trades %}
|
|
<div class="section-title">已平仓 · {{ closed_trades|length }} 笔</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<tr><th>品种</th><th>合约</th><th>类型</th><th>行权价</th><th>开仓</th><th>平仓</th><th>权利金</th><th>平仓价</th><th>开仓费</th><th>平仓费</th><th>盈亏</th><th>操作</th></tr>
|
|
{% for t in closed_trades %}
|
|
<tr>
|
|
<td>{{ t.product_code }}</td>
|
|
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
|
|
<td>
|
|
{% if t.option_type == 'C' %}
|
|
<span class="badge badge-up">C 看涨</span>
|
|
{% else %}
|
|
<span class="badge badge-down">P 看跌</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ t.strike_price }}</td>
|
|
<td style="font-size:0.8rem;">{{ t.open_date }}</td>
|
|
<td style="font-size:0.8rem;">{{ t.close_date }}</td>
|
|
<td>{{ t.open_price }}</td>
|
|
<td>{{ t.close_price }}</td>
|
|
<td>{{ t.open_fee or 0 }}</td>
|
|
<td>{{ t.close_fee or 0 }}</td>
|
|
<td>
|
|
{% set p = t.pnl %}
|
|
{% if p is not none %}
|
|
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% else %}color:var(--sub);{% endif %}">
|
|
{% if p > 0 %}+{% endif %}{{ p }}
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<form method="post" action="/options/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
|
|
<button style="background:var(--danger-bg);color:var(--danger-fg);border:1px solid var(--danger);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删除</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
|
|
{% set ns = namespace(total_pnl=0, total_open_fee=0, total_close_fee=0) %}
|
|
{% for t in closed_trades %}
|
|
{% set ns.total_pnl = ns.total_pnl + (t.pnl or 0) %}
|
|
{% set ns.total_open_fee = ns.total_open_fee + (t.open_fee or 0) %}
|
|
{% set ns.total_close_fee = ns.total_close_fee + (t.close_fee or 0) %}
|
|
{% endfor %}
|
|
<div style="display:flex;gap:24px;margin-bottom:24px;padding:12px 18px;background:var(--surface);border:1px solid var(--border);border-radius:8px;font-size:0.88rem;flex-wrap:wrap;">
|
|
<span>盈亏合计 <b style="{% if ns.total_pnl > 0 %}color:var(--success-fg);{% elif ns.total_pnl < 0 %}color:var(--danger-fg);{% endif %}">{% if ns.total_pnl > 0 %}+{% endif %}{{ '%.2f'|format(ns.total_pnl) }}</b></span>
|
|
<span style="color:var(--sub);">|</span>
|
|
<span>开仓手续费 <b>{{ '%.2f'|format(ns.total_open_fee) }}</b></span>
|
|
<span style="color:var(--sub);">|</span>
|
|
<span>平仓手续费 <b>{{ '%.2f'|format(ns.total_close_fee) }}</b></span>
|
|
<span style="color:var(--sub);">|</span>
|
|
<span>手续费合计 <b>{{ '%.2f'|format(ns.total_open_fee + ns.total_close_fee) }}</b></span>
|
|
</div>
|
|
{% else %}
|
|
<div style="text-align:center;padding:60px;color:var(--sub);">暂无已平仓记录</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if view != 'closed' %}
|
|
<script>
|
|
var productContracts = {{ product_contracts | tojson }};
|
|
function filterContracts() {
|
|
var prod = document.getElementById('productSelect').value;
|
|
var sel = document.getElementById('contractSelect');
|
|
sel.innerHTML = '<option value="">-- 选择合约 --</option>';
|
|
sel.disabled = !prod;
|
|
if (prod && productContracts[prod]) {
|
|
productContracts[prod].forEach(function(c) {
|
|
var display = c.startsWith(prod) ? c.substring(prod.length) : c;
|
|
sel.innerHTML += '<option value="' + c + '">' + display + '</option>';
|
|
});
|
|
}
|
|
}
|
|
function showCloseForm(tradeId) {
|
|
document.getElementById('closeForm').action = '/options/' + tradeId + '/close';
|
|
document.getElementById('closeFormOverlay').style.display = 'block';
|
|
document.getElementById('closeFormBox').style.display = 'block';
|
|
}
|
|
function hideCloseForm() {
|
|
document.getElementById('closeFormOverlay').style.display = 'none';
|
|
document.getElementById('closeFormBox').style.display = 'none';
|
|
}
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|