Files
finance-talk/ft-app/app/templates/positions.html
T
2026-07-26 14:13:01 +08:00

214 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}对冲管理{% endblock %}
{% block heading %}对冲管理{% endblock %}
{% block breadcrumb %}振幅锁仓 · 仓位跟踪{% endblock %}
{% block content %}
{% set error = request.query_params.get('error', '') %}
{% if error %}
<div style="background:var(--danger-bg);color:var(--danger-fg);padding:10px 16px;border-radius:6px;margin-bottom:16px;font-size:0.88rem;">{{ error }}</div>
{% endif %}
{# ═══════════════ 新建轮次 ═══════════════ #}
<div class="section-title">新建轮次</div>
<div class="form-card" style="margin-bottom:28px;">
<form method="post" action="/positions/round">
<div style="display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;">
<div class="form-group" style="margin-bottom:0;flex:1;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:1;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:1;min-width:100px;">
<label>每日手数 N</label>
<input type="number" name="daily_hands" value="1" min="1" max="10" required>
</div>
<button type="submit" class="btn btn-primary">开始新轮</button>
</div>
</form>
</div>
{% if not active_rounds %}
<div style="text-align:center;padding:60px;color:var(--sub);">
<p style="font-size:1.1rem;margin-bottom:8px;">暂无活跃轮次</p>
<p style="font-size:0.82rem;">创建一轮新的交易,开始跟踪仓位和锁仓状态</p>
</div>
{% endif %}
{% for round in active_rounds %}
{# ═══════════════ 轮次卡片 ═══════════════ #}
{% set locks = round.total_locks %}
<div style="margin-bottom:32px;border:1px solid var(--border);border-radius:10px;overflow:hidden;">
{# ── 轮次头部 ── #}
<div style="display:flex;align-items:center;gap:16px;padding:14px 20px;background:var(--th-bg);flex-wrap:wrap;">
<strong style="font-size:1rem;">{{ round.contract_code }}</strong>
<span class="badge badge-up">{{ round.daily_hands }} 手/天</span>
<span style="font-size:0.82rem;color:var(--sub);">已开 {{ round.positions|length }} 天</span>
<span style="font-size:0.82rem;color:var(--sub);">开始 {{ round.started_at }}</span>
<span style="font-size:0.82rem;font-weight:600;{% if locks >= 3 %}color:var(--danger-fg);{% endif %}">
锁仓 {{ locks }}/3
{% if locks >= 3 %} ⚠ 熔断{% endif %}
</span>
<span style="margin-left:auto;display:flex;gap:8px;">
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
<input type="hidden" name="result" value="profit_taken">
<button class="btn" style="background:var(--success);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="confirmDelete(event, '确认 {{ round.contract_code }} 止盈清仓?', this.closest('form').action)">止盈</button>
</form>
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
<input type="hidden" name="result" value="meltdown">
<button class="btn" style="background:var(--danger);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="confirmDelete(event, '确认 {{ round.contract_code }} 熔断清仓?', this.closest('form').action)">熔断</button>
</form>
<form method="post" action="/positions/{{ round.id }}/delete" style="display:inline;">
<button style="background:none;border:none;color:var(--na);cursor:pointer;font-size:0.78rem;" onclick="confirmDelete(event, '确定删除 {{ round.contract_code }} 整轮数据?', this.closest('form').action)">删除</button>
</form>
</span>
</div>
<div style="padding:16px 20px;">
{# ── 止盈阈值 ── #}
{% if round.positions %}
{% set latest = round.positions[-1] %}
<div style="background:var(--accent-light);border:1px solid var(--accent);border-radius:6px;padding:10px 16px;margin-bottom:16px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;">
<span style="font-size:0.78rem;color:var(--sub);">止盈阈值</span>
<span style="font-weight:700;">{{ round.daily_hands }} × {{ latest.amp_threshold }} = {{ round.daily_hands * latest.amp_threshold }} 点</span>
<span style="color:var(--sub);">|</span>
<span style="font-size:0.78rem;color:var(--sub);">当前 A = {{ latest.amp_threshold }}</span>
<span style="color:var(--sub);">|</span>
<span style="font-size:0.78rem;color:var(--sub);">总手数 {{ round.daily_hands * round.positions|length }}</span>
</div>
{% endif %}
{# ── 添加仓位 ── #}
<div style="margin-bottom:16px;">
<form method="post" action="/positions/{{ round.id }}/add">
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
<div class="form-group" style="margin-bottom:0;flex:1;min-width:100px;">
<label>开仓价</label>
<input type="number" name="open_price" required placeholder="如 1300" style="font-size:0.9rem;">
</div>
<div class="form-group" style="margin-bottom:0;flex:1;min-width:100px;">
<label>振幅 A</label>
<input type="number" name="amp_threshold" required placeholder="如 18" style="font-size:0.9rem;">
</div>
<div class="form-group" style="margin-bottom:0;flex:1;min-width:70px;">
<label>手数</label>
<input type="number" name="hands" value="{{ round.daily_hands }}" min="1" max="10" required 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>
{# ── 仓位表格 ── #}
{% if round.positions %}
<div class="table-wrap" style="margin-bottom:0;">
<table style="font-size:0.84rem;">
<tr>
<th>日期</th><th>开仓价</th><th>A</th><th>锁仓价</th><th>手数</th><th>已锁</th><th>状态</th><th>操作</th>
</tr>
{% for p in round.positions|reverse %}
<tr>
<td><strong>{{ p.opened_at }}</strong></td>
<td>{{ p.open_price }}</td>
<td>{{ p.amp_threshold }}</td>
<td style="color:var(--danger-fg);font-weight:600;">{{ p.lock_price }}</td>
<td>{{ p.hands }}</td>
<td>
{% if p.locked_count > 0 %}
<span class="badge badge-warn">{{ p.locked_count }}/{{ p.hands }}</span>
{% else %}
<span class="na">0/{{ p.hands }}</span>
{% endif %}
</td>
<td>
{% if p.locked_count >= p.hands %}
<span class="badge badge-down">已全锁</span>
{% elif p.locked_count > 0 %}
<span class="badge badge-warn">部分锁</span>
{% else %}
<span class="badge badge-up">活跃</span>
{% endif %}
</td>
<td>
<div style="display:flex;gap:6px;justify-content:center;">
<form method="post" action="/positions/{{ p.id }}/lock" style="display:inline;">
<button style="background:var(--warn-bg);color:var(--warn-fg);border:1px solid var(--warn);padding:2px 8px;border-radius:4px;cursor:pointer;font-size:0.75rem;font-weight:600;"
{% if p.locked_count >= p.hands %}disabled style="opacity:0.4;cursor:default;"{% endif %}>锁</button>
</form>
<form method="post" action="/positions/{{ p.id }}/unlock" style="display:inline;">
<button style="background:var(--surface);color:var(--sub);border:1px solid var(--border);padding:2px 8px;border-radius:4px;cursor:pointer;font-size:0.75rem;"
{% if p.locked_count == 0 %}disabled style="opacity:0.4;cursor:default;"{% endif %}></button>
</form>
</div>
</td>
</tr>
{% endfor %}
</table>
</div>
{% else %}
<div style="text-align:center;padding:24px;color:var(--sub);font-size:0.84rem;">暂无仓位,请添加。</div>
{% endif %}
</div>
</div>
{% endfor %}
<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>';
});
}
}
</script>
<div id="confirmOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:199;" onclick="hideConfirm()"></div>
<div id="confirmBox" 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:28px 32px;z-index:200;text-align:center;max-width:90vw;">
<p id="confirmMsg" style="font-size:0.95rem;margin-bottom:20px;"></p>
<div style="display:flex;gap:10px;justify-content:center;">
<button class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideConfirm()">取消</button>
<button class="btn" style="background:var(--danger);color:#fff;" id="confirmBtn">确认</button>
</div>
</div>
<script>
function confirmDelete(e, msg, url) {
e.preventDefault();
document.getElementById('confirmMsg').textContent = msg;
document.getElementById('confirmBtn').onclick = function() {
var f = document.createElement('form');
f.method = 'POST';
f.action = url;
document.body.appendChild(f);
f.submit();
};
document.getElementById('confirmOverlay').style.display = 'block';
document.getElementById('confirmBox').style.display = 'block';
}
function hideConfirm() {
document.getElementById('confirmOverlay').style.display = 'none';
document.getElementById('confirmBox').style.display = 'none';
}
</script>
{% endblock %}