diff --git a/app.py b/app.py index b841f7a..1c78e71 100644 --- a/app.py +++ b/app.py @@ -728,11 +728,19 @@ def keys(): history = conn.execute( "SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100" ).fetchall() - positions = conn.execute( + conn.close() + return render_template("keys.html", keys=key_list, history=history) + + +@app.route("/positions") +@login_required +def positions(): + conn = get_db() + pos_list = conn.execute( "SELECT * FROM position_monitors WHERE status='active' ORDER BY id DESC" ).fetchall() conn.close() - return render_template("keys.html", keys=key_list, history=history, positions=positions) + return render_template("positions.html", positions=pos_list) @app.route("/add_key", methods=["POST"]) @@ -773,7 +781,7 @@ def add_position(): sina_code = d.get("sina_code", "").strip() if not symbol or not market_code: flash("请从下拉列表选择品种") - return redirect(url_for("keys")) + return redirect(url_for("positions")) entry = float(d["entry_price"]) sl = float(d["stop_loss"]) tp = float(d["take_profit"]) @@ -796,7 +804,7 @@ def add_position(): conn.commit() conn.close() flash("持仓已添加") - return redirect(url_for("keys")) + return redirect(url_for("positions")) @app.route("/del_position/") @@ -813,7 +821,7 @@ def close_position(pid): if not row: conn.close() flash("持仓不存在") - return redirect(url_for("keys")) + return redirect(url_for("positions")) sym = row["symbol"] market = row["market_code"] or "" sina = row["sina_code"] or "" @@ -828,7 +836,7 @@ def close_position(pid): if close_price is None: conn.close() flash("无法获取现价,平仓失败") - return redirect(url_for("keys")) + return redirect(url_for("positions")) capital = float(get_setting("live_capital", "0") or 0) metrics = calc_position_metrics(direction, entry, sl, tp, lots, close_price, capital, sym) pnl = metrics.get("float_pnl") or 0.0 @@ -850,16 +858,13 @@ def close_position(pid): conn.commit() conn.close() flash(f"已平仓,盈亏 {pnl:.2f} 元,已记入交易记录") - return redirect(url_for("keys")) + return redirect(url_for("positions")) @app.route("/trades") @login_required def trades(): - conn = get_db() - rows = conn.execute("SELECT * FROM trade_logs ORDER BY id DESC LIMIT 500").fetchall() - conn.close() - return render_template("trades.html", trades=rows) + return redirect(url_for("records")) @app.route("/update_trade/", methods=["POST"]) @@ -895,7 +900,7 @@ def update_trade(tid): conn.commit() conn.close() flash("交易记录已核对保存") - return redirect(url_for("trades")) + return redirect(url_for("records")) @app.route("/del_trade/") @@ -906,7 +911,7 @@ def del_trade(tid): conn.commit() conn.close() flash("已删除") - return redirect(url_for("trades")) + return redirect(url_for("records")) @app.route("/fill_review/") @@ -917,7 +922,7 @@ def fill_review_from_trade(tid): conn.close() if not row: flash("记录不存在") - return redirect(url_for("trades")) + return redirect(url_for("records")) q = { "symbol": row["symbol"], "symbol_name": row["symbol_name"] or row["symbol"], @@ -933,7 +938,8 @@ def fill_review_from_trade(tid): "close_time": row["close_time"], "pnl": row["pnl"], } - return redirect(url_for("records", **{k: v for k, v in q.items() if v is not None})) + params = {k: v for k, v in q.items() if v is not None} + return redirect(url_for("records", **params) + "#review-panel") @app.route("/del_key/") @@ -974,6 +980,9 @@ def records(): auto_list = conn.execute( "SELECT * FROM trade_records ORDER BY id DESC LIMIT 30" ).fetchall() + trade_list = conn.execute( + "SELECT * FROM trade_logs ORDER BY id DESC LIMIT 500" + ).fetchall() conn.close() trade_prefill_keys = ( @@ -986,6 +995,7 @@ def records(): return render_template( "records.html", reviews=review_list, + trades=trade_list, auto_records=auto_list, preset=preset, start=start, diff --git a/static/js/keys.js b/static/js/keys.js index a1c67c7..8671edf 100644 --- a/static/js/keys.js +++ b/static/js/keys.js @@ -1,17 +1,11 @@ (function () { var keyTimer = null; - var posTimer = null; function fmtDist(v) { if (v === null || v === undefined) return '--'; return Number(v).toFixed(2); } - function fmtNum(v, digits) { - if (v === null || v === undefined) return '--'; - return Number(v).toFixed(digits === undefined ? 2 : digits); - } - function pollKeyPrices() { var list = document.getElementById('key-monitor-list'); if (!list || !list.querySelector('.key-item')) return; @@ -33,72 +27,8 @@ .catch(function () { /* ignore */ }); } - function buildPosCard(row) { - var pnlClass = ''; - if (row.float_pnl > 0) pnlClass = 'pnl-pos'; - if (row.float_pnl < 0) pnlClass = 'pnl-neg'; - var pnlText = '--'; - if (row.float_pnl != null) { - var sign = row.float_pnl >= 0 ? '+' : ''; - pnlText = sign + fmtNum(row.float_pnl) + '元'; - if (row.float_pct != null) { - pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)'; - } - } - var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--'; - var openT = (row.open_time || '').replace('T', ' ').slice(0, 16); - - return ( - '
' + - '
' + - '
' + row.symbol + ' ' + row.direction + '
' + - '
' + - '
' + - '
' + - '
来源 手动输入 · 风险 ' + - fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元
' + - '
' + - '
' + fmtNum(row.entry_price) + '
' + - '
' + fmtNum(row.stop_loss) + '
' + - '
' + fmtNum(row.take_profit) + '
' + - '
' + rr + '
' + - '
' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '
' + - '
' + pnlText + '
' + - '
' + - '
' - ); - } - - function pollPositions() { - var list = document.getElementById('position-live-list'); - if (!list) return; - - fetch('/api/position_live') - .then(function (r) { return r.json(); }) - .then(function (rows) { - if (!rows.length) { - list.innerHTML = '
暂无持仓,左侧录入后显示
'; - return; - } - list.innerHTML = rows.map(buildPosCard).join(''); - }) - .catch(function () { /* ignore */ }); - } - - function startPolling() { - if (keyTimer) clearInterval(keyTimer); - if (posTimer) clearInterval(posTimer); + document.addEventListener('DOMContentLoaded', function () { pollKeyPrices(); - pollPositions(); keyTimer = setInterval(pollKeyPrices, 1000); - posTimer = setInterval(pollPositions, 1000); - } - - document.addEventListener('DOMContentLoaded', startPolling); + }); })(); diff --git a/static/js/positions.js b/static/js/positions.js new file mode 100644 index 0000000..c05f43e --- /dev/null +++ b/static/js/positions.js @@ -0,0 +1,71 @@ +(function () { + var posTimer = null; + + function fmtNum(v, digits) { + if (v === null || v === undefined) return '--'; + return Number(v).toFixed(digits === undefined ? 2 : digits); + } + + function buildPosCard(row) { + var pnlClass = ''; + if (row.float_pnl > 0) pnlClass = 'pnl-pos'; + if (row.float_pnl < 0) pnlClass = 'pnl-neg'; + var pnlText = '--'; + if (row.float_pnl != null) { + var sign = row.float_pnl >= 0 ? '+' : ''; + pnlText = sign + fmtNum(row.float_pnl) + '元'; + if (row.float_pct != null) { + pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)'; + } + } + var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--'; + var openT = (row.open_time || '').replace('T', ' ').slice(0, 16); + + return ( + '
' + + '
' + + '
' + row.symbol + ' ' + row.direction + '
' + + '
' + + '
' + + '
' + + '
来源 手动输入 · 风险 ' + + fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元
' + + '
' + + '
' + fmtNum(row.entry_price) + '
' + + '
' + fmtNum(row.stop_loss) + '
' + + '
' + fmtNum(row.take_profit) + '
' + + '
' + rr + '
' + + '
' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '
' + + '
' + pnlText + '
' + + '
' + + '
' + ); + } + + function pollPositions() { + var list = document.getElementById('position-live-list'); + if (!list) return; + + fetch('/api/position_live') + .then(function (r) { return r.json(); }) + .then(function (rows) { + if (!rows.length) { + list.innerHTML = '
暂无持仓,左侧录入后显示
'; + return; + } + list.innerHTML = rows.map(buildPosCard).join(''); + }) + .catch(function () { /* ignore */ }); + } + + document.addEventListener('DOMContentLoaded', function () { + pollPositions(); + posTimer = setInterval(pollPositions, 1000); + }); +})(); diff --git a/templates/base.html b/templates/base.html index b665ab4..f13d798 100644 --- a/templates/base.html +++ b/templates/base.html @@ -350,7 +350,13 @@ .pos-footer span{color:var(--text-primary)} .pos-del{font-size:.75rem;padding:.35rem .65rem} .trade-toolbar{display:flex;align-items:center;gap:1rem;margin-bottom:1rem;flex-wrap:wrap} - .trade-toolbar label{display:flex;align-items:center;gap:.4rem;font-size:.85rem;cursor:pointer;color:var(--text-muted)} + .trade-switch-label{ + display:flex;align-items:center;gap:.35rem; + font-size:.68rem;color:var(--text-muted); + white-space:nowrap;margin-bottom:.65rem;cursor:pointer; + } + .trade-switch-label span{line-height:1} + .trade-switch-label input{flex-shrink:0} .trade-table-wrap{overflow-x:auto} .trade-table{font-size:.8rem} .trade-table th{font-size:.75rem;padding:.55rem .45rem} @@ -396,8 +402,8 @@ diff --git a/templates/keys.html b/templates/keys.html index 2812fef..6d848c3 100644 --- a/templates/keys.html +++ b/templates/keys.html @@ -79,47 +79,6 @@ - -
-
-

持仓录入

-
-
-
-
- - - - - -
-
-
-
开仓时间
- -
-
- - - -
-
- -
-
-

方向根据止损与成交价自动判断;风险比例依赖系统设置中的实盘资金。

-
-
- -
-

实时持仓

-
- {% if not positions %} -
暂无持仓,左侧录入后显示
- {% endif %} -
-
-
{% endblock %} {% block extra_js %} diff --git a/templates/positions.html b/templates/positions.html new file mode 100644 index 0000000..549124f --- /dev/null +++ b/templates/positions.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} +{% block title %}持仓监控 - 国内期货监控系统{% endblock %} +{% block content %} +
+
+

持仓录入

+
+
+
+
+ + + + + +
+
+
+ + +
+
+ + + +
+
+ +
+
+

方向根据止损与成交价自动判断;风险比例依赖系统设置中的实盘资金。

+
+
+ +
+

实时持仓

+
+ {% if not positions %} +
暂无持仓,左侧录入后显示
+ {% endif %} +
+
+
+{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/templates/records.html b/templates/records.html index 12c801a..caa65fa 100644 --- a/templates/records.html +++ b/templates/records.html @@ -1,8 +1,114 @@ {% extends "base.html" %} -{% block title %}复盘 - 国内期货监控系统{% endblock %} +{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %} {% block content %} +
+

交易记录

+
+ +
+ + + + + + + + + + + + {% for t in trades %} + + + + + + + + + + + + + + + + + {% else %} + + {% endfor %} + +
品种类型方向成交止损(开仓)止盈基数杠杆持仓分钟开仓时间平仓时间盈亏(元)结果操作
{{ t.symbol_name or t.symbol }} + {{ t.monitor_type }} + + + + {{ '做多' if t.direction == 'long' else '做空' }} + + + + {{ t.entry_price }} + + + {{ t.stop_loss }} + + + {{ t.take_profit }} + + + {{ t.lots }}手 / {{ t.margin or '-' }} + + + + {{ t.holding_minutes or 0 }} + + + {{ (t.open_time or '')[:16].replace('T',' ') }} + + + {{ (t.close_time or '')[:16].replace('T',' ') }} + + + + {{ t.pnl if t.pnl is not none else '-' }} + + + + + + + {% if t.result == '止盈' %}{{ t.result }} + {% elif t.result == '止损' %}{{ t.result }} + {% elif t.result == '手动平仓' %}{{ t.result }} + {% else %}{{ t.result }}{% endif %} + {% if t.verified %}已核对{% endif %} + + + +
+ 填入复盘 + + 删除 +
+
暂无交易记录
+
+
+
+
-
+

复盘上传

@@ -178,10 +284,12 @@ {% endblock %} {% block extra_js %} + {% if prefill %} {% endif %} + {% endblock %} diff --git a/templates/trades.html b/templates/trades.html deleted file mode 100644 index 102f537..0000000 --- a/templates/trades.html +++ /dev/null @@ -1,111 +0,0 @@ -{% extends "base.html" %} -{% block title %}交易记录 - 国内期货监控系统{% endblock %} -{% block content %} -
-

交易记录

-
-
- -
-
- - - - - - - - - - - - {% for t in trades %} - - - - - - - - - - - - - - - - - {% else %} - - {% endfor %} - -
品种类型方向成交止损(开仓)止盈基数杠杆持仓分钟开仓时间平仓时间盈亏(元)结果操作
{{ t.symbol_name or t.symbol }} - {{ t.monitor_type }} - - - - {{ '做多' if t.direction == 'long' else '做空' }} - - - - {{ t.entry_price }} - - - {{ t.stop_loss }} - - - {{ t.take_profit }} - - - {{ t.lots }}手 / {{ t.margin or '-' }} - - - - {{ t.holding_minutes or 0 }} - - - {{ (t.open_time or '')[:16].replace('T',' ') }} - - - {{ (t.close_time or '')[:16].replace('T',' ') }} - - - - {{ t.pnl if t.pnl is not none else '-' }} - - - - - - - {% if t.result == '止盈' %}{{ t.result }} - {% elif t.result == '止损' %}{{ t.result }} - {% elif t.result == '手动平仓' %}{{ t.result }} - {% else %}{{ t.result }}{% endif %} - {% if t.verified %}已核对{% endif %} - - - -
- 填入复盘 - - 删除 -
-
暂无交易记录
-
-
-
-{% endblock %} -{% block extra_js %} - -{% endblock %}