feat: 计仓改为固定手数/固定金额,推荐过滤与CTP保证金,下单与持仓UI优化

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 15:31:34 +08:00
parent c302e1f3ca
commit 9772f3d986
11 changed files with 387 additions and 119 deletions
+27 -6
View File
@@ -54,14 +54,18 @@
</div>
<div class="field">
<label>计仓模式</label>
<select name="position_sizing_mode">
<option value="risk" {% if position_sizing_mode == 'risk' %}selected{% endif %}>以损定仓</option>
<option value="fixed" {% if position_sizing_mode == 'fixed' %}selected{% endif %}>固定张数</option>
<select name="position_sizing_mode" id="position-sizing-mode">
<option value="fixed" {% if position_sizing_mode == 'fixed' %}selected{% endif %}>固定手数</option>
<option value="amount" {% if position_sizing_mode in ('amount', 'risk') %}selected{% endif %}>固定金额</option>
</select>
</div>
<div class="field">
<label>单笔风险比例(以损定仓,%</label>
<input name="risk_percent" type="number" step="0.1" min="0.1" max="100" value="{{ risk_percent }}">
<div class="field" id="field-fixed-lots" {% if position_sizing_mode in ('amount', 'risk') %}hidden{% endif %}>
<label>固定手数(手</label>
<input name="fixed_lots" type="number" step="1" min="1" value="{{ fixed_lots }}">
</div>
<div class="field" id="field-fixed-amount" {% if position_sizing_mode not in ('amount', 'risk') %}hidden{% endif %}>
<label>固定金额(元)</label>
<input name="fixed_amount" type="number" step="100" min="1" value="{{ fixed_amount }}">
</div>
<div class="field">
<label>保证金占用上限(%</label>
@@ -146,3 +150,20 @@
</div>
{% endblock %}
{% block extra_js %}
<script>
(function () {
var sel = document.getElementById('position-sizing-mode');
var lotsField = document.getElementById('field-fixed-lots');
var amountField = document.getElementById('field-fixed-amount');
function syncSizingFields() {
if (!sel) return;
var isAmount = sel.value === 'amount';
if (lotsField) lotsField.hidden = isAmount;
if (amountField) amountField.hidden = !isAmount;
}
if (sel) sel.addEventListener('change', syncSizingFields);
syncSizingFields();
})();
</script>
{% endblock %}
+13 -8
View File
@@ -31,7 +31,11 @@
<div class="status-row">
<span class="text-muted">计仓</span>
<strong id="sizing-label">{{ sizing_mode_label }}</strong>
{% if sizing_mode == 'risk' %}<span class="text-muted">· 单笔风险 {{ risk_percent }}%</span>{% endif %}
{% if sizing_mode == 'fixed' %}
<span class="text-muted">· {{ fixed_lots }} 手</span>
{% elif sizing_mode in ('amount', 'risk') %}
<span class="text-muted">· {{ '%.0f'|format(fixed_amount) }} 元</span>
{% endif %}
</div>
</div>
@@ -53,8 +57,8 @@
</div>
<div class="trade-field" id="field-lots">
<label class="text-label">手数</label>
<input type="number" id="trade-lots" min="1" step="1" value="1" {% if sizing_mode == 'risk' %}hidden{% endif %}>
<input type="text" id="trade-lots-calc" class="lots-auto" readonly placeholder="填写止损后自动计算" {% if sizing_mode != 'risk' %}hidden{% endif %}>
<input type="text" id="trade-lots-calc" class="lots-auto" readonly placeholder="">
<input type="hidden" id="trade-lots" value="{{ fixed_lots if sizing_mode == 'fixed' else '1' }}">
<p class="hint lots-warn text-loss" id="lots-warn" hidden></p>
</div>
</div>
@@ -95,8 +99,6 @@
<p class="hint" id="trade-metrics-hint">填写品种后显示精度与每跳价值;策略自动化请用 <a href="{{ url_for('strategy_page') }}">策略交易</a></p>
{% if ctp_status.last_error %}
<p class="text-loss ctp-install-hint" style="font-size:.78rem;margin-top:.35rem">{{ ctp_status.last_error }}</p>
{% else %}
<p class="text-muted ctp-install-hint" style="font-size:.72rem;margin-top:.35rem">报单需安装 vnpy 并连接 CTP(SimNow 模拟盘)。</p>
{% endif %}
</div>
</div>
@@ -114,7 +116,9 @@
<div class="card trade-card trade-card-full" id="recommend">
<h2>品种推荐</h2>
<div class="card-body">
<p class="hint">最大手数 = floor(权益 × 保证金上限 <strong>{{ max_margin_pct }}%</strong> ÷ 1手保证金);当前权益 <strong class="text-accent" id="rec-capital">{{ '%.2f'|format(capital) }}</strong> 元。参考止损/止盈按 20 跳、盈亏比 2:1 估算。
<p class="hint">最大手数 = floor(权益 × 保证金上限 <strong>{{ max_margin_pct }}%</strong> ÷ 1手保证金);当前权益 <strong class="text-accent" id="rec-capital">{{ '%.2f'|format(capital) }}</strong> 元。
{% if sizing_mode == 'fixed' %}仅显示最大手数 ≥ <strong>{{ fixed_lots }}</strong> 手的品种。{% endif %}
保证金优先读取 CTP 柜台合约信息。
{% if recommend_updated_at %}<span class="text-muted">每日后台更新 · 最近 {{ recommend_updated_at }}</span>{% else %}<span class="text-muted" id="rec-updated">等待今日后台刷新…</span>{% endif %}
</p>
<div class="trade-table-wrap">
@@ -135,7 +139,7 @@
<td>{% if r.price %}{{ r.price }}{% else %}—{% endif %}</td>
<td>{% if r.ref_stop_loss %}{{ r.ref_stop_loss }}{% else %}—{% endif %}</td>
<td>{% if r.ref_take_profit %}{{ r.ref_take_profit }}{% else %}—{% endif %}</td>
<td>{% if r.margin_one_lot %}{{ r.margin_one_lot }}{% else %}—{% endif %}</td>
<td>{% if r.margin_one_lot %}{{ r.margin_one_lot }}{% if r.margin_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% else %}—{% endif %}</td>
<td>{% if r.open_fee_one_lot is defined and r.open_fee_one_lot is not none %}{{ r.open_fee_one_lot }}{% else %}—{% endif %}</td>
<td>{% if r.max_lots is not none and r.max_lots > 0 %}{{ r.max_lots }}{% else %}—{% endif %}</td>
<td><span class="badge {% if r.status=='ok' %}profit{% else %}planned{% endif %}">{{ r.status_label }}</span></td>
@@ -154,7 +158,8 @@
{% block extra_js %}
<script>
window.TRADE_SIZING_MODE = {{ sizing_mode|tojson }};
window.TRADE_RISK_PERCENT = {{ risk_percent }};
window.TRADE_FIXED_LOTS = {{ fixed_lots|tojson }};
window.TRADE_FIXED_AMOUNT = {{ fixed_amount|tojson }};
</script>
<script src="{{ url_for('static', filename='js/trade.js') }}"></script>
{% endblock %}