Files
qihuo/templates/trade.html
T
dekun ddfe2a52aa Merge orders and positions into one card and hide stale pending when CTP is off.
Stop showing DB pending orders while disconnected, invalidate session cache when CTP is down, and add a local DB clear script without embedded credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 19:27:02 +08:00

241 lines
16 KiB
HTML

{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %}
{% block title %}下单监控 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/trade.css') }}">
{% endblock %}
{% block content %}
<div class="trade-page">
<div class="trade-top-bar">
<div class="trade-top-bar-main">
<span class="badge dir">{{ trading_mode_label }}</span>
<span class="badge {% if ctp_status.connected %}profit{% else %}planned{% endif %}" id="ctp-badge">
{% if ctp_status.connected %}CTP 已连接{% else %}CTP 未连接{% endif %}
</span>
<span class="badge {% if risk_status.can_trade %}profit{% else %}loss{% endif %}" id="risk-badge">{{ risk_status.status_label }}</span>
<span class="text-muted">权益 <strong id="cap-display">{{ '%.2f'|format(capital) }}</strong></span>
{% if ctp_account.available is defined and ctp_status.connected %}
<span class="text-muted">可用 <strong id="avail-display">{{ '%.2f'|format(ctp_account.available) }}</strong></span>
{% endif %}
</div>
<div class="trade-top-bar-actions">
<button type="button" class="btn-primary btn-ctp-sm" id="btn-ctp-connect"
{% if not ctp_auto_connect %}disabled title="请先在系统设置 → CTP 连接 中开启自动连接"{% endif %}>
{% if ctp_status.connected %}重连 CTP{% else %}连接 CTP{% endif %}
</button>
<span class="text-muted trade-top-hint" id="ctp-auto-hint">{% if ctp_auto_connect %}断线自动重连 · 开盘前 30 分钟自动连接{% else %}CTP 自动连接已关闭{% endif %}</span>
</div>
</div>
<div class="split-grid trade-split">
<div class="card trade-card" id="order">
<h2>期货下单</h2>
<div class="card-body">
<div class="trade-order-status trade-order-status-compact">
<div class="status-row">
<span class="text-muted">计仓</span>
<strong id="sizing-label">{{ sizing_mode_label }}</strong>
{% 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>
<div class="trade-form-rows">
<div class="trade-form-line line-3">
<div class="symbol-wrap trade-field symbol-mains">
<label class="text-label">品种</label>
<input type="text" id="trade-symbol" class="symbol-input" placeholder="输入中文或代码,选择主力合约" autocomplete="off">
<input type="hidden" id="trade-symbol-code" class="symbol-ths-code">
<div class="symbol-dropdown"></div>
<div class="symbol-selected" id="sym-selected"></div>
</div>
<div class="trade-field">
<label class="text-label">方向</label>
<select id="trade-direction">
<option value="long">做多</option>
<option value="short">做空</option>
</select>
</div>
<div class="trade-field" id="field-lots">
<label class="text-label">手数</label>
<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>
<div class="trade-form-line line-3">
<div class="trade-field">
<label class="text-label">入场价</label>
<div class="price-type-tabs">
<button type="button" class="price-tab active" data-type="limit">限价</button>
<button type="button" class="price-tab" data-type="market">市价</button>
</div>
<input type="number" id="trade-price" step="any" placeholder="限价">
<p class="hint market-hint" id="market-hint" hidden>市价以 FAK 即时成交报单(非限价挂单)</p>
</div>
<div class="trade-field">
<label class="text-label">止盈</label>
<input type="number" id="trade-tp" step="any">
</div>
<div class="trade-field">
<label class="text-label">止损</label>
<input type="number" id="trade-sl" step="any">
</div>
</div>
</div>
<div class="trade-action-row">
<label class="trailing-be-toggle">
<input type="checkbox" id="trailing-be" checked>
<span>移动保本</span>
</label>
<span class="hint trade-rr-hint" id="trade-rr-hint" hidden></span>
<button type="button" class="btn-primary btn-open" id="btn-open">开仓</button>
<p class="hint session-hint text-muted" id="session-hint" hidden>不在交易时间段</p>
<p class="trade-order-msg" id="order-msg" hidden></p>
</div>
<div class="trade-footer" id="trade-footer">
<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>
{% endif %}
</div>
</div>
</div>
<div class="card trade-card" id="trading-live">
<h2>委托与持仓 <span class="sync-badge text-muted" id="sync-badge" hidden></span></h2>
<p class="hint pos-hint">委托、持仓均以 CTP 柜台为准;止盈止损为程序本地监控,触发后市价平仓。</p>
<div class="card-body card-scroll trading-live-body">
<section class="trading-live-section" id="active-orders">
<h3 class="trading-live-subtitle">当前委托</h3>
<div id="order-live-list">
<div class="empty-hint" id="order-placeholder">加载委托…</div>
</div>
</section>
<section class="trading-live-section trading-live-positions" id="positions">
<h3 class="trading-live-subtitle">当前持仓</h3>
<div id="position-live-list">
<div class="empty-hint" id="position-placeholder">加载持仓…</div>
</div>
</section>
</div>
</div>
</div>
<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> 元。
{% 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>
<p class="trend-hint">走势:近一周日线,近3日重叠≥70%为震荡;跳空=今日开盘 vs 昨日收盘。成交量为昨日成交手数,成交额=成交量×昨收×合约乘数。支持按走势/跳空/成交量/振幅排序,可按行业筛选。</p>
<div class="rec-stats" id="rec-stats"></div>
<div class="rec-sort-bar">
<label for="rec-industry-filter">行业</label>
<select id="rec-industry-filter">
<option value="" selected>全部</option>
{% for cat in product_categories %}
<option value="{{ cat }}">{{ cat }}</option>
{% endfor %}
</select>
<label for="rec-sort-key">排序</label>
<select id="rec-sort-key">
<option value="trend" selected>走势</option>
<option value="gap">跳空</option>
<option value="volume">成交量</option>
<option value="amplitude">振幅</option>
</select>
<button type="button" class="rec-sort-dir-btn" id="rec-sort-dir" title="切换升序/降序"></button>
</div>
<div class="trade-table-wrap">
<table class="trade-table" id="recommend-table">
<thead>
<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>
<th>乘数</th><th>最小变动</th>
<th>1手保证金</th><th>1手手续费</th><th>最大手数</th><th>状态</th>
</tr>
</thead>
<tbody id="recommend-list">
{% if recommend_rows %}
{% for r in recommend_rows %}
<tr class="rec-{{ r.status }}{% if r.trend_transition %} rec-trend-break{% endif %}">
<td>
{% if r.main_code and nav_items.market %}
<a href="{{ url_for('market_page', symbol=r.main_code, period='d') }}" class="rec-market-link" title="查看日线 K 线">
<strong class="{% if r.trend_transition %}trend-name{% endif %}">{{ r.name }}</strong>
<span class="text-accent">{{ r.main_code }}</span>
</a>
{% else %}
<strong class="{% if r.trend_transition %}trend-name{% endif %}">{{ r.name }}</strong>
<span class="text-accent">{{ r.main_code or r.ths }}</span>
{% endif %}
</td>
<td>{{ r.exchange }}</td>
<td>{{ r.category or '—' }}</td>
<td>
{% if r.trend_label and r.trend_label != '—' %}
<span class="badge trend-badge {% if r.trend in ('break_long', 'break_short') %}break{% elif r.trend == 'long' %}profit{% elif r.trend == 'short' %}loss{% else %}planned{% endif %}" title="{% if r.trend_overlap_pct is not none %}近3日重叠 {{ r.trend_overlap_pct }}%{% endif %}">
{% if r.trend_transition %}★ {% endif %}{{ r.trend_label }}
</span>
{% else %}—{% endif %}
</td>
<td>
{% if r.gap_label and r.gap_label != '—' %}
<span class="badge gap-badge {% if r.gap == 'up' %}profit{% elif r.gap == 'down' %}loss{% else %}planned{% endif %}"{% if r.gap_pct %} title="跳空 {{ '%+.2f'|format(r.gap_pct) }}%"{% endif %}>{{ r.gap_label }}</span>
{% else %}—{% endif %}
</td>
<td>{% if r.price %}{{ r.price }}{% else %}—{% endif %}</td>
<td>{% if r.prev_close is not none %}{{ r.prev_close }}{% else %}—{% endif %}</td>
<td>{% if r.today_open is not none %}{{ r.today_open }}{% else %}—{% endif %}</td>
<td>
{% if r.yesterday_change is not none %}
<span class="{% if r.yesterday_change > 0 %}rec-change-up{% elif r.yesterday_change < 0 %}rec-change-down{% endif %}">
{{ '%+.4f'|format(r.yesterday_change) }}{% if r.yesterday_change_pct is not none %} ({{ '%+.2f'|format(r.yesterday_change_pct) }}%){% endif %}
</span>
{% else %}—{% endif %}
</td>
<td>{% if r.yesterday_amplitude_pct is not none %}{{ '%.2f'|format(r.yesterday_amplitude_pct) }}%{% else %}—{% endif %}</td>
<td>{% if r.volume is not none %}{{ r.volume }}{% else %}—{% endif %}</td>
<td>{% if r.turnover is not none %}{{ '%.0f'|format(r.turnover) }}{% else %}—{% endif %}</td>
<td>{% if r.mult is not none %}{{ '%g'|format(r.mult) }}{% if r.spec_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% else %}—{% endif %}</td>
<td>{% if r.tick_size is not none %}{{ '%g'|format(r.tick_size) }}{% if r.spec_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% 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>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="18" class="empty-hint">等待今日后台刷新推荐…</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
window.TRADE_SIZING_MODE = {{ sizing_mode|tojson }};
window.MARKET_NAV_ENABLED = {{ nav_items.market|tojson }};
window.TRADE_FIXED_LOTS = {{ fixed_lots|tojson }};
window.TRADE_FIXED_AMOUNT = {{ fixed_amount|tojson }};
window.PRODUCT_CATEGORIES = {{ product_categories | default([]) | tojson }};
window.__RECOMMEND_ROWS__ = {{ recommend_rows | default([]) | tojson }};
window.CTP_AUTO_CONNECT = {{ ctp_auto_connect | tojson }};
</script>
<script src="{{ url_for('static', filename='js/trade.js') }}?v={{ asset_v }}"></script>
{% endblock %}