持仓监控页整合期货下单、持仓与品种推荐三卡片。
程序报单状态与推荐表内嵌同一页面,/recommend 跳转至 #recommend。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
-8
@@ -288,7 +288,18 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
|
|||||||
capital = _capital(conn)
|
capital = _capital(conn)
|
||||||
risk = get_risk_status(conn)
|
risk = get_risk_status(conn)
|
||||||
ctp_acc = _ctp_account(mode) if ctp_st.get("connected") else {}
|
ctp_acc = _ctp_account(mode) if ctp_st.get("connected") else {}
|
||||||
|
recommend_rows = list_product_recommendations(capital, _main_price)
|
||||||
|
active_trend = conn.execute(
|
||||||
|
"SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC LIMIT 1"
|
||||||
|
).fetchone()
|
||||||
|
monitor_count = conn.execute(
|
||||||
|
"SELECT COUNT(*) AS n FROM trade_order_monitors WHERE status='active'"
|
||||||
|
).fetchone()["n"]
|
||||||
|
roll_count = conn.execute(
|
||||||
|
"SELECT COUNT(*) AS n FROM roll_groups WHERE status='active'"
|
||||||
|
).fetchone()["n"]
|
||||||
conn.close()
|
conn.close()
|
||||||
|
sizing = get_sizing_mode(get_setting)
|
||||||
return render_template(
|
return render_template(
|
||||||
"trade.html",
|
"trade.html",
|
||||||
trading_mode=mode,
|
trading_mode=mode,
|
||||||
@@ -297,8 +308,20 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
|
|||||||
risk_status=risk,
|
risk_status=risk,
|
||||||
ctp_status=ctp_st,
|
ctp_status=ctp_st,
|
||||||
ctp_account=ctp_acc,
|
ctp_account=ctp_acc,
|
||||||
|
recommend_rows=recommend_rows,
|
||||||
|
active_trend=dict(active_trend) if active_trend else None,
|
||||||
|
monitor_count=monitor_count,
|
||||||
|
roll_count=roll_count,
|
||||||
|
sizing_mode=sizing,
|
||||||
|
sizing_mode_label="以损定仓" if sizing == MODE_RISK else "固定张数",
|
||||||
|
risk_percent=get_risk_percent(get_setting),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@app.route("/recommend")
|
||||||
|
@login_required
|
||||||
|
def recommend_page():
|
||||||
|
return redirect(url_for("positions") + "#recommend")
|
||||||
|
|
||||||
@app.route("/api/trading/live")
|
@app.route("/api/trading/live")
|
||||||
@login_required
|
@login_required
|
||||||
def api_trading_live():
|
def api_trading_live():
|
||||||
@@ -313,6 +336,7 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
|
|||||||
"capital": _capital(get_db()),
|
"capital": _capital(get_db()),
|
||||||
"ctp_status": ctp_st,
|
"ctp_status": ctp_st,
|
||||||
"trading_mode_label": trading_mode_label(get_setting),
|
"trading_mode_label": trading_mode_label(get_setting),
|
||||||
|
"risk_status": get_risk_status(get_db()),
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.route("/api/trading/close", methods=["POST"])
|
@app.route("/api/trading/close", methods=["POST"])
|
||||||
@@ -357,14 +381,6 @@ def install_trading(app, *, login_required, get_db, get_setting, set_setting, fe
|
|||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": False, "error": str(exc)}), 400
|
return jsonify({"ok": False, "error": str(exc)}), 400
|
||||||
|
|
||||||
@app.route("/recommend")
|
|
||||||
@login_required
|
|
||||||
def recommend_page():
|
|
||||||
conn = get_db()
|
|
||||||
capital = _capital(conn)
|
|
||||||
conn.close()
|
|
||||||
rows = list_product_recommendations(capital, _main_price)
|
|
||||||
return render_template("recommend.html", capital=capital, rows=rows, trading_mode_label=trading_mode_label(get_setting))
|
|
||||||
|
|
||||||
@app.route("/strategy")
|
@app.route("/strategy")
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
+10
-5
@@ -1,9 +1,14 @@
|
|||||||
.trade-page{max-width:960px;margin:0 auto}
|
.trade-page{max-width:1100px;margin:0 auto}
|
||||||
.trade-top-bar{display:flex;flex-wrap:wrap;gap:.65rem;align-items:center;margin-bottom:1rem}
|
.trade-top-bar{display:flex;flex-wrap:wrap;gap:.65rem;align-items:center;margin-bottom:1.25rem}
|
||||||
.trade-subnav{display:flex;gap:1rem;margin-bottom:1rem;font-size:.88rem}
|
.trade-dashboard{display:flex;flex-direction:column;gap:1.25rem}
|
||||||
.trade-subnav span.active{color:var(--accent);font-weight:600;border-bottom:2px solid var(--accent);padding-bottom:.25rem}
|
.trade-card{margin-bottom:0}
|
||||||
.trade-subnav a{color:var(--text-muted);text-decoration:none}
|
.trade-card h2{margin-bottom:.65rem}
|
||||||
|
.trade-order-status{display:grid;gap:.55rem;margin:.75rem 0;padding:.85rem 1rem;background:var(--card-inner);border:1px solid var(--card-border);border-radius:8px;font-size:.85rem}
|
||||||
|
.trade-order-status .status-row{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .65rem}
|
||||||
|
.trade-order-status .trend-active{padding-top:.35rem;border-top:1px dashed var(--card-border)}
|
||||||
|
.trade-order-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 1rem;margin-top:1rem}
|
||||||
.trade-footer{background:var(--card-inner);border-radius:8px;padding:.75rem 1rem;font-size:.82rem;line-height:1.55;border:1px solid var(--card-border);margin-top:1rem}
|
.trade-footer{background:var(--card-inner);border-radius:8px;padding:.75rem 1rem;font-size:.82rem;line-height:1.55;border:1px solid var(--card-border);margin-top:1rem}
|
||||||
.trade-footer strong{color:var(--accent)}
|
.trade-footer strong{color:var(--accent)}
|
||||||
.rec-blocked td{opacity:.55}
|
.rec-blocked td{opacity:.55}
|
||||||
.rec-ok td:first-child{font-weight:600}
|
.rec-ok td:first-child{font-weight:600}
|
||||||
|
#positions .card-body{max-height:520px}
|
||||||
|
|||||||
+8
-1
@@ -114,6 +114,13 @@
|
|||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
var cap = document.getElementById('cap-display');
|
var cap = document.getElementById('cap-display');
|
||||||
if (cap && data.capital != null) cap.textContent = Number(data.capital).toFixed(2);
|
if (cap && data.capital != null) cap.textContent = Number(data.capital).toFixed(2);
|
||||||
|
var recCap = document.getElementById('rec-capital');
|
||||||
|
if (recCap && data.capital != null) recCap.textContent = Number(data.capital).toFixed(2);
|
||||||
|
var riskBadge = document.getElementById('risk-badge');
|
||||||
|
if (riskBadge && data.risk_status) {
|
||||||
|
riskBadge.textContent = data.risk_status.status_label;
|
||||||
|
riskBadge.className = 'badge ' + (data.risk_status.can_trade ? 'profit' : 'loss');
|
||||||
|
}
|
||||||
var ctpBadge = document.getElementById('ctp-badge');
|
var ctpBadge = document.getElementById('ctp-badge');
|
||||||
if (ctpBadge && data.ctp_status) {
|
if (ctpBadge && data.ctp_status) {
|
||||||
ctpBadge.textContent = data.ctp_status.connected ? 'CTP 已连接' : 'CTP 未连接';
|
ctpBadge.textContent = data.ctp_status.connected ? 'CTP 已连接' : 'CTP 未连接';
|
||||||
@@ -121,7 +128,7 @@
|
|||||||
}
|
}
|
||||||
var rows = data.rows || [];
|
var rows = data.rows || [];
|
||||||
if (!rows.length) {
|
if (!rows.length) {
|
||||||
list.innerHTML = '<div class="empty-hint">暂无持仓。请先在「策略交易」开仓,或连接 CTP 同步柜台持仓。</div>';
|
list.innerHTML = '<div class="empty-hint">暂无持仓。请通过上方「期货下单 → 策略交易」开仓,或连接 CTP 同步柜台持仓。</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = rows.map(buildPosCard).join('');
|
list.innerHTML = rows.map(buildPosCard).join('');
|
||||||
|
|||||||
+1
-2
@@ -483,8 +483,7 @@
|
|||||||
<h1 class="site-title">国内期货 · 交易监控 + 复盘<span class="site-title-sub">FUTURES MONITOR SYSTEM</span></h1>
|
<h1 class="site-title">国内期货 · 交易监控 + 复盘<span class="site-title-sub">FUTURES MONITOR SYSTEM</span></h1>
|
||||||
<button type="button" class="nav-backdrop" id="nav-backdrop" aria-label="关闭菜单" hidden></button>
|
<button type="button" class="nav-backdrop" id="nav-backdrop" aria-label="关闭菜单" hidden></button>
|
||||||
<nav class="site-nav" id="site-nav">
|
<nav class="site-nav" id="site-nav">
|
||||||
<a href="{{ url_for('positions') }}" class="{% if request.endpoint in ('positions', 'trade_page') %}active{% endif %}">持仓监控</a>
|
<a href="{{ url_for('positions') }}" class="{% if request.endpoint in ('positions', 'trade_page', 'recommend_page') %}active{% endif %}">持仓监控</a>
|
||||||
<a href="{{ url_for('recommend_page') }}" class="{% if request.endpoint == 'recommend_page' %}active{% endif %}">品种推荐</a>
|
|
||||||
<a href="{{ url_for('strategy_page') }}" class="{% if request.endpoint in ('strategy_page', 'strategy_records_page') %}active{% endif %}">策略交易</a>
|
<a href="{{ url_for('strategy_page') }}" class="{% if request.endpoint in ('strategy_page', 'strategy_records_page') %}active{% endif %}">策略交易</a>
|
||||||
<a href="{{ url_for('plans') }}" class="{% if request.endpoint == 'plans' %}active{% endif %}">开单计划</a>
|
<a href="{{ url_for('plans') }}" class="{% if request.endpoint == 'plans' %}active{% endif %}">开单计划</a>
|
||||||
<a href="{{ url_for('keys') }}" class="{% if request.endpoint == 'keys' %}active{% endif %}">关键位监控</a>
|
<a href="{{ url_for('keys') }}" class="{% if request.endpoint == 'keys' %}active{% endif %}">关键位监控</a>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<button type="button" class="btn-primary" id="btn-roll-exec" hidden>执行滚仓</button>
|
<button type="button" class="btn-primary" id="btn-roll-exec" hidden>执行滚仓</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="empty-hint">请先在「策略交易」开仓,持仓将自动出现在「持仓监控」。</p>
|
<p class="empty-hint">请先在「持仓监控 → 期货下单」进入策略交易开仓。</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+77
-14
@@ -13,25 +13,88 @@
|
|||||||
<span class="badge {% if risk_status.can_trade %}profit{% else %}loss{% endif %}" id="risk-badge">{{ risk_status.status_label }}</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>
|
<span class="text-muted">权益 <strong id="cap-display">{{ '%.2f'|format(capital) }}</strong> 元</span>
|
||||||
{% if ctp_account.available is defined and ctp_status.connected %}
|
{% if ctp_account.available is defined and ctp_status.connected %}
|
||||||
<span class="text-muted">可用 <strong>{{ '%.2f'|format(ctp_account.available) }}</strong> 元</span>
|
<span class="text-muted">可用 <strong id="avail-display">{{ '%.2f'|format(ctp_account.available) }}</strong> 元</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button type="button" class="btn-primary" id="btn-ctp-connect" style="padding:.4rem .9rem;font-size:.8rem">连接 CTP</button>
|
<button type="button" class="btn-primary" id="btn-ctp-connect" style="padding:.4rem .9rem;font-size:.8rem">连接 CTP</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="trade-subnav">
|
<div class="trade-dashboard">
|
||||||
<span class="active">持仓监控</span>
|
<div class="card trade-card" id="order">
|
||||||
<a href="{{ url_for('recommend_page') }}">品种推荐</a>
|
<h2>期货下单</h2>
|
||||||
<a href="{{ url_for('strategy_page') }}">策略交易</a>
|
<div class="card-body">
|
||||||
</div>
|
<p class="hint">开仓、加仓由<strong>程序</strong>在「策略交易」中执行,经 CTP 自动报单至 SimNow / 期货公司柜台。</p>
|
||||||
|
<div class="trade-order-status">
|
||||||
<div class="card">
|
<div class="status-row">
|
||||||
<h2>实时持仓</h2>
|
<span class="text-muted">计仓模式</span>
|
||||||
<div class="card-body" id="position-live-list">
|
<strong>{{ sizing_mode_label }}</strong>
|
||||||
<div class="empty-hint">加载中…</div>
|
{% if sizing_mode == 'risk' %}
|
||||||
|
<span class="text-muted">· 单笔风险 {{ risk_percent }}%</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="status-row">
|
||||||
|
<span class="text-muted">风控状态</span>
|
||||||
|
<strong class="{% if risk_status.can_trade %}text-profit{% else %}text-loss{% endif %}">{{ risk_status.status_label }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="status-row">
|
||||||
|
<span class="text-muted">程序监控</span>
|
||||||
|
<strong>{{ monitor_count }}</strong> 笔
|
||||||
|
{% if roll_count %}<span class="text-muted">· 滚仓组 {{ roll_count }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if active_trend %}
|
||||||
|
<div class="status-row trend-active">
|
||||||
|
<span class="text-muted">趋势回调</span>
|
||||||
|
<strong>#{{ active_trend.id }} {{ active_trend.symbol }} {{ '多' if active_trend.direction=='long' else '空' }}</strong>
|
||||||
|
<span class="text-muted">已开 {{ active_trend.lots_open or 0 }}/{{ active_trend.target_lots }} 手</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if not ctp_status.connected %}
|
||||||
|
<p class="hint text-accent" style="margin-top:.75rem">请先连接 CTP,程序报单才会进入柜台。</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if ctp_status.last_error %}
|
||||||
|
<p class="text-loss" style="font-size:.78rem;margin-top:.5rem">{{ ctp_status.last_error }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="trade-order-actions">
|
||||||
|
<a href="{{ url_for('strategy_page') }}" class="btn-primary">前往策略交易</a>
|
||||||
|
<a href="{{ url_for('strategy_records_page') }}" class="text-muted" style="font-size:.82rem">策略记录 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="trade-footer" id="trade-footer">
|
|
||||||
<p class="hint">开仓请使用「策略交易」;连接 CTP 后自动同步 SimNow / 柜台持仓与程序监控。</p>
|
<div class="card trade-card" id="positions">
|
||||||
{% if ctp_status.last_error %}<p class="text-loss" style="font-size:.78rem;margin-top:.5rem">{{ ctp_status.last_error }}</p>{% endif %}
|
<h2>持仓监控</h2>
|
||||||
|
<div class="card-body card-scroll" id="position-live-list">
|
||||||
|
<div class="empty-hint">加载中…</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card trade-card" id="recommend">
|
||||||
|
<h2>品种推荐</h2>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="hint">按当前权益 <strong class="text-accent" id="rec-capital">{{ '%.2f'|format(capital) }}</strong> 元筛选;
|
||||||
|
灰色为保证金不足,优先展示可开 1 手且风险规则较友好的品种。</p>
|
||||||
|
<div class="trade-table-wrap">
|
||||||
|
<table class="trade-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>品种</th><th>交易所</th><th>参考价</th><th>1手保证金</th><th>建议最低资金</th><th>状态</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for r in recommend_rows %}
|
||||||
|
<tr class="rec-{{ r.status }}">
|
||||||
|
<td><strong>{{ r.name }}</strong> <span class="text-muted">{{ r.ths }}</span></td>
|
||||||
|
<td>{{ r.exchange }}</td>
|
||||||
|
<td>{% if r.price %}{{ r.price }}{% else %}—{% endif %}</td>
|
||||||
|
<td>{% if r.margin_one_lot %}{{ r.margin_one_lot }}{% else %}—{% endif %}</td>
|
||||||
|
<td>{% if r.min_capital_one_lot %}{{ r.min_capital_one_lot }}{% else %}—{% endif %}</td>
|
||||||
|
<td><span class="badge {% if r.status=='ok' %}profit{% elif r.status=='blocked' %}loss{% else %}planned{% endif %}">{{ r.status_label }}</span></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user