52aca456e9
Support DATABASE_URL with connection pooling, pg_dump backups, SQLite migration script, and deploy_postgres.sh with docs. Co-authored-by: Cursor <cursoragent@cursor.com>
313 lines
22 KiB
HTML
313 lines
22 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') }}?v={{ asset_v }}">
|
|
{% 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 %}
|
|
<span class="text-muted trade-session-clock" id="session-clock-wrap">
|
|
· <span id="clock-now">{{ session_clock.now_time }}</span>
|
|
· <span id="clock-status" class="{% if session_clock.in_session %}text-profit{% else %}text-muted{% endif %}">{{ session_clock.status_label }}</span>
|
|
<span id="clock-detail" class="session-clock-detail">
|
|
{% if not session_clock.in_session and session_clock.next_open_at %}
|
|
· 下次{{ session_clock.next_open_label }} {{ session_clock.next_open_at }}
|
|
· 距开盘 <strong id="clock-countdown-open">{{ session_clock.countdown_open }}</strong>
|
|
{% elif session_clock.in_session %}
|
|
{% if session_clock.countdown_break %}
|
|
· 距{{ session_clock.break_label }} <strong id="clock-countdown-break">{{ session_clock.countdown_break }}</strong>
|
|
{% endif %}
|
|
{% if session_clock.countdown_close %}
|
|
· 距{{ session_clock.close_label }} <strong id="clock-countdown-close">{{ session_clock.countdown_close }}</strong>
|
|
{% endif %}
|
|
{% endif %}
|
|
</span>
|
|
</span>
|
|
</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 %}自动连接已关闭 · 盘前 30 分钟及交易时段仍会按计划连接 · 断开请手动操作{% 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>
|
|
|
|
<details class="module-rules">
|
|
<summary>规则说明</summary>
|
|
<div class="module-rules-body">
|
|
<p><strong>开仓</strong></p>
|
|
<ul>
|
|
<li>须 <strong>CTP 已连接</strong> 且处于<strong>交易时段</strong>;账户风控允许(顶栏状态)</li>
|
|
<li>固定金额计仓、开启<strong>移动保本</strong>时须填写<strong>止损</strong></li>
|
|
<li>手数:固定手数,或按止损距离与单笔风险金额自动计算;保证金占用不超过系统设置上限</li>
|
|
<li>限价未成交 → 挂单中,超时自动撤单(系统设置可改分钟数)</li>
|
|
</ul>
|
|
<p><strong>止盈 / 止损 / 移动保本</strong></p>
|
|
<ul>
|
|
<li>成交后由程序<strong>本地监控</strong>;触及止盈或止损 → 市价平仓</li>
|
|
<li><strong>移动保本</strong>:须填止损、不设固定止盈;达 1R 止损移至开仓±缓冲跳,2R 移 1R,依次类推</li>
|
|
<li>手动平仓写入交易记录;当日手动平仓次数超限 → 当日禁止新开仓</li>
|
|
</ul>
|
|
<p><strong>可开仓品种表</strong></p>
|
|
<ul>
|
|
<li>按权益与保证金上限筛选;权益 ≤20 万或 CTP 未连接时仅四品种(玉米、豆粕、甲醇、螺纹钢)</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
|
|
<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" id="field-tp">
|
|
<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">
|
|
<span>移动保本</span>
|
|
</label>
|
|
<p class="hint trailing-be-hint" id="trailing-be-hint" hidden>已开启:仅监控止损,达 1R 后移动保本平仓</p>
|
|
<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(recommend_capital) }}</strong> 元<span id="rec-margin-hint" class="text-muted"></span>。
|
|
{% if sizing_mode == 'fixed' %}仅显示最大手数 ≥ <strong>{{ fixed_lots }}</strong> 手的品种。{% endif %}
|
|
{% if small_account_scope %}<span class="text-muted">{{ small_account_scope_hint }}。</span>{% endif %}
|
|
{% if small_account_margin_rec %}<span class="text-muted">{{ small_account_margin_rec.label }}。</span>{% endif %}
|
|
{% if night_session %}<span class="text-muted">当前为夜盘时段,品种下拉与下表仅显示有夜盘品种;带「夜盘」标记。</span>{% elif not small_account_scope %}<span class="text-muted">有夜盘交易的品种带「夜盘」标记。</span>{% 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>{% if r.has_night_session %} <span class="night-session-tag">夜盘</span>{% endif %}
|
|
<span class="text-accent">{{ r.main_code }}</span>
|
|
</a>
|
|
{% else %}
|
|
<strong class="{% if r.trend_transition %}trend-name{% endif %}">{{ r.name }}</strong>{% if r.has_night_session %} <span class="night-session-tag">夜盘</span>{% endif %}
|
|
<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>
|
|
|
|
<div id="sl-tp-modal" class="modal-mask" role="dialog" aria-labelledby="sl-tp-modal-title">
|
|
<div class="modal-box sl-tp-modal">
|
|
<h3 id="sl-tp-modal-title">设置止盈止损</h3>
|
|
<div class="sl-tp-modal-fields">
|
|
<div class="trade-field">
|
|
<label class="text-label" for="sl-tp-modal-sl">止损</label>
|
|
<input type="number" id="sl-tp-modal-sl" step="any" placeholder="必填(移动保本须填止损)">
|
|
</div>
|
|
<div class="trade-field" id="sl-tp-modal-tp-wrap">
|
|
<label class="text-label" for="sl-tp-modal-tp">止盈</label>
|
|
<input type="number" id="sl-tp-modal-tp" step="any" placeholder="可留空">
|
|
</div>
|
|
<label class="trailing-be-toggle sl-tp-modal-trailing">
|
|
<input type="checkbox" id="sl-tp-modal-trailing">
|
|
<span>移动保本</span>
|
|
</label>
|
|
<p class="hint" id="sl-tp-modal-trailing-hint" hidden>开启后不设固定止盈;达 1R 后止损移至开仓价 ± 缓冲跳</p>
|
|
</div>
|
|
<div class="sl-tp-modal-actions">
|
|
<button type="button" class="btn-secondary" id="sl-tp-modal-cancel">取消</button>
|
|
<button type="button" class="btn-primary" id="sl-tp-modal-save">保存</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
{% block extra_js %}
|
|
<script type="application/json" id="trade-page-data">{{ {
|
|
'sizing_mode': sizing_mode,
|
|
'market_nav_enabled': nav_items.market,
|
|
'fixed_lots': fixed_lots,
|
|
'fixed_amount': fixed_amount,
|
|
'product_categories': product_categories | default([]),
|
|
'recommend_rows': recommend_rows | default([]),
|
|
'ctp_auto_connect': ctp_auto_connect,
|
|
'session_clock': session_clock,
|
|
'capital': capital,
|
|
'bootstrap_live': bootstrap_live | default({})
|
|
} | tojson }}</script>
|
|
<script src="{{ url_for('static', filename='js/trade.js') }}?v={{ asset_v }}"></script>
|
|
{% endblock %}
|