From 7bb6ee11542dfd1c95df9825d1dea367b8a45711 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 5 Jul 2026 22:21:30 +0800 Subject: [PATCH] =?UTF-8?q?remove:=20=E5=85=A8=E9=9D=A2=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E3=80=8C=E9=94=99=E8=BF=87=E6=9C=BA=E4=BC=9A=E3=80=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=B8=8E=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除手动记录表单与 /add_miss;停止自动生成错过类交易记录;列表/统计/顶栏不再展示;复盘卡片改为整行。 Co-authored-by: Cursor --- crypto_monitor_binance/app.py | 70 +++------------ crypto_monitor_binance/templates/index.html | 30 +------ crypto_monitor_gate/app.py | 88 +++---------------- crypto_monitor_gate/templates/index.html | 30 +------ crypto_monitor_okx/app.py | 70 +++------------ crypto_monitor_okx/templates/index.html | 30 +------ lib/instance/instance_embed_context_lib.py | 10 +-- .../templates/embed_page_fragment.html | 29 +----- lib/instance/templates/embed_shell.html | 1 - lib/trade/trade_result_lib.py | 16 ++++ tests/test_trade_result_miss_filter.py | 26 ++++++ 11 files changed, 87 insertions(+), 313 deletions(-) create mode 100644 tests/test_trade_result_miss_filter.py diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index d882746..356a271 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -220,7 +220,11 @@ from lib.common.history_window_lib import ( utc_window_to_bj_sql_strings, utc_window_to_utc_sql_strings, ) -from lib.trade.trade_result_lib import count_winning_trades, normalize_result_with_pnl +from lib.trade.trade_result_lib import ( + count_winning_trades, + filter_trade_records_excluding_miss, + normalize_result_with_pnl, +) from lib.trade.trade_exchange_stats_lib import ( attach_exchange_stats_to_trade, filter_position_lifecycle_fills, @@ -7119,27 +7123,20 @@ def render_main_page(page="trade", embed_mode=None): f"SELECT * FROM trade_records WHERE {tr_ts} >= ? AND {tr_ts} <= ? ORDER BY id DESC LIMIT 1000", (start_bj, end_bj), ).fetchall() - records = [to_effective_trade_dict(r) for r in raw_records] - total = len(records) - miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") - win = count_winning_trades(records) - occupied_miss_total = sum( - 1 - for r in records - if (r.get("effective_result") or "") == "错过" - and ("持仓占用" in str(r.get("effective_miss_reason") or "")) + records = filter_trade_records_excluding_miss( + [to_effective_trade_dict(r) for r in raw_records] ) + total = len(records) + win = count_winning_trades(records) rate = round(win / total * 100, 2) if total else 0 elif plan.records_summary: summary = trade_records_summary(conn, start_bj, end_bj, tr_ts) records = summary["records"] total = summary["total"] - miss_count = summary["miss_count"] rate = summary["rate"] - occupied_miss_total = summary["occupied_miss_total"] else: records = [] - total = miss_count = rate = occupied_miss_total = 0 + total = rate = 0 active_count = len(order_list) from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors @@ -7196,7 +7193,6 @@ def render_main_page(page="trade", embed_mode=None): orphan_live_positions=orphan_live_positions, record=records, total=total, - miss_count=miss_count, rate=rate, trading_day=trading_day, funding_usdt=funding_usdt, @@ -7239,7 +7235,6 @@ def render_main_page(page="trade", embed_mode=None): ), breakeven_rr_trigger=BREAKEVEN_RR_TRIGGER, breakeven_offset_pct=BREAKEVEN_OFFSET_PCT, - occupied_miss_total=occupied_miss_total, price_fmt=format_price_for_symbol, funds_fmt=format_funds_u, entry_reason_options=list(ENTRY_REASON_OPTIONS), @@ -8358,27 +8353,6 @@ def add_order(): return redirect("/trade") ok, reason = precheck_risk(conn, symbol, direction) if not ok: - if "已达最大持仓数" in reason: - try: - tp_raw = parse_positive_float(d.get("tp")) - sl_raw = parse_positive_float(d.get("sl")) - tgt_raw = parse_positive_float(d.get("tgt")) - except Exception: - tp_raw = sl_raw = tgt_raw = None - insert_trade_record( - conn, - symbol=symbol, - monitor_type="下单监控", - direction=direction if direction in ("long", "short") else "long", - trigger_price=tp_raw or 0, - stop_loss=sl_raw or 0, - take_profit=tgt_raw or 0, - result="错过", - miss_reason=f"持仓占用:{reason}", - opened_at=app_now_str(), - closed_at=app_now_str(), - ) - conn.commit() conn.close() flash(f"风控拒绝下单:{reason}") return redirect("/trade") @@ -9091,30 +9065,6 @@ def del_order(id): conn.close() return redirect("/") -@app.route("/add_miss", methods=["POST"]) -@login_required -def add_miss(): - d = request.form - direction = d.get("direction", "long") - conn = get_db() - insert_trade_record( - conn, - symbol=d["symbol"], - monitor_type=d["type"], - direction=direction, - trigger_price=d["tp"], - stop_loss=d["sl"], - take_profit=d["tgt"], - result="错过", - miss_reason=d["reason"], - opened_at=app_now_str(), - closed_at=app_now_str(), - ) - conn.commit() - conn.close() - flash("已记录错过机会") - return _redirect_records() - @app.route("/add_journal", methods=["POST"]) @login_required diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index 4a547bc..eaca758 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -334,7 +334,6 @@
交易所
{{ exchange_display }}
总交易
{{ total }}
-
错过次数
{{ miss_count }}
胜率
{{ rate }}%
资金账户(USDT)
{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}
交易日
{{ trading_day }}
@@ -565,7 +564,7 @@ {% if page == 'records' %}
-

交易记录 & 错过机会

+

交易记录

-
-

记录错过机会

-
- - - - - - - - -
-
- -
+

交易复盘记录上传(含截图)

@@ -797,9 +776,6 @@
-
-
持仓占用导致错过(累计)
{{ occupied_miss_total }}
-
统计分析按北京时间 {{ stats_bundle.stats_reset_hour }}:00切日计入(与顶栏 UTC 列表窗无关)。历史总开仓(累计): {{ stats_bundle.total_opens_all }} 次 diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index c16a7e4..7ee517f 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -220,7 +220,11 @@ from lib.common.history_window_lib import ( utc_window_to_bj_sql_strings, utc_window_to_utc_sql_strings, ) -from lib.trade.trade_result_lib import count_winning_trades, normalize_result_with_pnl +from lib.trade.trade_result_lib import ( + count_winning_trades, + filter_trade_records_excluding_miss, + normalize_result_with_pnl, +) from lib.trade.trade_exchange_stats_lib import attach_exchange_stats_to_trade, filter_position_lifecycle_fills @@ -6899,27 +6903,20 @@ def render_main_page(page="trade", embed_mode=None): f"SELECT * FROM trade_records WHERE {tr_ts} >= ? AND {tr_ts} <= ? ORDER BY id DESC LIMIT 1000", (start_bj, end_bj), ).fetchall() - records = [to_effective_trade_dict(r) for r in raw_records] - total = len(records) - miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") - win = count_winning_trades(records) - occupied_miss_total = sum( - 1 - for r in records - if (r.get("effective_result") or "") == "错过" - and ("持仓占用" in str(r.get("effective_miss_reason") or "")) + records = filter_trade_records_excluding_miss( + [to_effective_trade_dict(r) for r in raw_records] ) + total = len(records) + win = count_winning_trades(records) rate = round(win / total * 100, 2) if total else 0 elif plan.records_summary: summary = trade_records_summary(conn, start_bj, end_bj, tr_ts) records = summary["records"] total = summary["total"] - miss_count = summary["miss_count"] rate = summary["rate"] - occupied_miss_total = summary["occupied_miss_total"] else: records = [] - total = miss_count = rate = occupied_miss_total = 0 + total = rate = 0 active_count = len(order_list) from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors @@ -6972,7 +6969,6 @@ def render_main_page(page="trade", embed_mode=None): order=order_list, record=records, total=total, - miss_count=miss_count, rate=rate, trading_day=trading_day, funding_usdt=funding_usdt, @@ -7016,7 +7012,6 @@ def render_main_page(page="trade", embed_mode=None): ), breakeven_rr_trigger=BREAKEVEN_RR_TRIGGER, breakeven_offset_pct=BREAKEVEN_OFFSET_PCT, - occupied_miss_total=occupied_miss_total, price_fmt=format_price_for_symbol, funds_fmt=format_usdt, usdt_fmt=format_usdt, @@ -8192,32 +8187,6 @@ def add_order(): return redirect("/trade") ok, reason = precheck_risk(conn, symbol, direction) if not ok: - if "已达最大持仓数" in reason: - try: - tp_raw = parse_positive_float(d.get("tp")) - sl_raw = parse_positive_float(d.get("sl")) - tgt_raw = parse_positive_float(d.get("tgt")) - except Exception: - tp_raw = sl_raw = tgt_raw = None - ex_miss = normalize_exchange_symbol(symbol) - try: - ensure_markets_loaded() - except Exception: - pass - insert_trade_record( - conn, - symbol=symbol, - monitor_type="下单监控", - direction=direction if direction in ("long", "short") else "long", - trigger_price=round_price_to_exchange(ex_miss, tp_raw) if tp_raw else 0, - stop_loss=round_price_to_exchange(ex_miss, sl_raw) if sl_raw else 0, - take_profit=round_price_to_exchange(ex_miss, tgt_raw) if tgt_raw else 0, - result="错过", - miss_reason=f"持仓占用:{reason}", - opened_at=app_now_str(), - closed_at=app_now_str(), - ) - conn.commit() conn.close() flash(f"风控拒绝下单:{reason}") return redirect("/trade") @@ -8918,43 +8887,6 @@ def del_order(id): conn.close() return redirect("/") -@app.route("/add_miss", methods=["POST"]) -@login_required -def add_miss(): - d = request.form - direction = d.get("direction", "long") - sym_in = normalize_symbol_input(d.get("symbol")) - ex_sym = normalize_exchange_symbol(sym_in) - try: - ensure_markets_loaded() - except Exception: - pass - try: - tp_px = round_price_to_exchange(ex_sym, float(d["tp"])) - sl_px = round_price_to_exchange(ex_sym, float(d["sl"])) - tgt_px = round_price_to_exchange(ex_sym, float(d["tgt"])) - except Exception: - flash("价格格式错误") - return _redirect_records() - conn = get_db() - insert_trade_record( - conn, - symbol=sym_in, - monitor_type=d["type"], - direction=direction, - trigger_price=tp_px, - stop_loss=sl_px, - take_profit=tgt_px, - result="错过", - miss_reason=d["reason"], - opened_at=app_now_str(), - closed_at=app_now_str(), - ) - conn.commit() - conn.close() - flash("已记录错过机会") - return _redirect_records() - @app.route("/add_journal", methods=["POST"]) @login_required diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 237e037..ee0b617 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -334,7 +334,6 @@
交易所
{{ exchange_display }}
总交易
{{ total }}
-
错过次数
{{ miss_count }}
胜率
{{ rate }}%
资金账户(USDT)
{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}
交易日
{{ trading_day }}
@@ -532,7 +531,7 @@ {% if page == 'records' %}
-

交易记录 & 错过机会

+

交易记录

-
-

记录错过机会

- - - - - - - - - - -
- -
+

交易复盘记录上传(含截图)

@@ -764,9 +743,6 @@
-
-
持仓占用导致错过(累计)
{{ occupied_miss_total }}
-
统计分析按北京时间 {{ stats_bundle.stats_reset_hour }}:00切日计入(与顶栏 UTC 列表窗无关)。历史总开仓(累计): {{ stats_bundle.total_opens_all }} 次 diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index f0881f0..817fa71 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -219,7 +219,11 @@ from lib.common.history_window_lib import ( utc_window_to_bj_sql_strings, utc_window_to_utc_sql_strings, ) -from lib.trade.trade_result_lib import count_winning_trades, normalize_result_with_pnl +from lib.trade.trade_result_lib import ( + count_winning_trades, + filter_trade_records_excluding_miss, + normalize_result_with_pnl, +) from lib.trade.trade_exchange_stats_lib import attach_exchange_stats_to_trade, filter_position_lifecycle_fills @@ -6458,27 +6462,20 @@ def render_main_page(page="trade", embed_mode=None): f"SELECT * FROM trade_records WHERE {tr_ts} >= ? AND {tr_ts} <= ? ORDER BY id DESC LIMIT 1000", (start_bj, end_bj), ).fetchall() - records = [to_effective_trade_dict(r) for r in raw_records] - total = len(records) - miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") - win = count_winning_trades(records) - occupied_miss_total = sum( - 1 - for r in records - if (r.get("effective_result") or "") == "错过" - and ("持仓占用" in str(r.get("effective_miss_reason") or "")) + records = filter_trade_records_excluding_miss( + [to_effective_trade_dict(r) for r in raw_records] ) + total = len(records) + win = count_winning_trades(records) rate = round(win / total * 100, 2) if total else 0 elif plan.records_summary: summary = trade_records_summary(conn, start_bj, end_bj, tr_ts) records = summary["records"] total = summary["total"] - miss_count = summary["miss_count"] rate = summary["rate"] - occupied_miss_total = summary["occupied_miss_total"] else: records = [] - total = miss_count = rate = occupied_miss_total = 0 + total = rate = 0 active_count = len(order_list) from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors @@ -6533,7 +6530,6 @@ def render_main_page(page="trade", embed_mode=None): order=order_list, record=records, total=total, - miss_count=miss_count, rate=rate, trading_day=trading_day, daily_start_capital=DAILY_START_CAPITAL, @@ -6577,7 +6573,6 @@ def render_main_page(page="trade", embed_mode=None): ), breakeven_rr_trigger=BREAKEVEN_RR_TRIGGER, breakeven_offset_pct=BREAKEVEN_OFFSET_PCT, - occupied_miss_total=occupied_miss_total, price_fmt=format_price_for_symbol, entry_reason_options=list(ENTRY_REASON_OPTIONS), entry_reason_other_value=ENTRY_REASON_OTHER, @@ -7768,27 +7763,6 @@ def add_order(): return redirect("/trade") ok, reason = precheck_risk(conn, symbol, direction) if not ok: - if "已达最大持仓数" in reason or "一次只能持有一个仓位" in reason: - try: - tp_raw = parse_positive_float(d.get("tp")) - sl_raw = parse_positive_float(d.get("sl")) - tgt_raw = parse_positive_float(d.get("tgt")) - except Exception: - tp_raw = sl_raw = tgt_raw = None - insert_trade_record( - conn, - symbol=symbol, - monitor_type="下单监控", - direction=direction if direction in ("long", "short") else "long", - trigger_price=tp_raw or 0, - stop_loss=sl_raw or 0, - take_profit=tgt_raw or 0, - result="错过", - miss_reason="持仓占用:一次只能持有一个仓位", - opened_at=app_now_str(), - closed_at=app_now_str(), - ) - conn.commit() conn.close() flash(f"风控拒绝下单:{reason}") return redirect("/trade") @@ -8462,30 +8436,6 @@ def del_order(id): conn.close() return redirect("/") -@app.route("/add_miss", methods=["POST"]) -@login_required -def add_miss(): - d = request.form - direction = d.get("direction", "long") - conn = get_db() - insert_trade_record( - conn, - symbol=d["symbol"], - monitor_type=d["type"], - direction=direction, - trigger_price=d["tp"], - stop_loss=d["sl"], - take_profit=d["tgt"], - result="错过", - miss_reason=d["reason"], - opened_at=app_now_str(), - closed_at=app_now_str(), - ) - conn.commit() - conn.close() - flash("已记录错过机会") - return _redirect_records() - @app.route("/add_journal", methods=["POST"]) @login_required diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 7549ca2..f0f42dd 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -334,7 +334,6 @@
交易所
{{ exchange_display }}
总交易
{{ total }}
-
错过次数
{{ miss_count }}
胜率
{{ rate }}%
资金账户(USDT)
{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}
交易日
{{ trading_day }}
@@ -561,7 +560,7 @@ {% if page == 'records' %}
-

交易记录 & 错过机会

+

交易记录

-
-

记录错过机会

- - - - - - - - - - -
- -
+

交易复盘记录上传(含截图)

@@ -793,9 +772,6 @@
-
-
持仓占用导致错过(累计)
{{ occupied_miss_total }}
-
统计分析按北京时间 {{ stats_bundle.stats_reset_hour }}:00切日计入(与顶栏 UTC 列表窗无关)。历史总开仓(累计): {{ stats_bundle.total_opens_all }} 次 diff --git a/lib/instance/instance_embed_context_lib.py b/lib/instance/instance_embed_context_lib.py index 188b5a3..c895c02 100644 --- a/lib/instance/instance_embed_context_lib.py +++ b/lib/instance/instance_embed_context_lib.py @@ -58,25 +58,21 @@ def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[ f""" SELECT COUNT(*) AS total, - SUM(CASE WHEN result = '错过' THEN 1 ELSE 0 END) AS miss_count, - SUM(CASE WHEN {pnl_sql} > 0 THEN 1 ELSE 0 END) AS wins, - SUM(CASE WHEN result = '错过' AND COALESCE(miss_reason,'') LIKE '%持仓占用%' THEN 1 ELSE 0 END) AS occupied_miss + SUM(CASE WHEN {pnl_sql} > 0 THEN 1 ELSE 0 END) AS wins FROM trade_records WHERE {tr_ts} >= ? AND {tr_ts} <= ? + AND COALESCE(result, '') != '错过' + AND COALESCE(reviewed_result, '') != '错过' """, (start_bj, end_bj), ).fetchone() total = int(row["total"] or 0) if row else 0 - miss_count = int(row["miss_count"] or 0) if row else 0 wins = int(row["wins"] or 0) if row else 0 - occupied_miss_total = int(row["occupied_miss"] or 0) if row else 0 rate = round(wins / total * 100, 2) if total else 0 return { "records": [], "total": total, - "miss_count": miss_count, "rate": rate, - "occupied_miss_total": occupied_miss_total, } diff --git a/lib/instance/templates/embed_page_fragment.html b/lib/instance/templates/embed_page_fragment.html index 54209e9..6aca57b 100644 --- a/lib/instance/templates/embed_page_fragment.html +++ b/lib/instance/templates/embed_page_fragment.html @@ -208,7 +208,7 @@ {% if page == 'records' %}
-

交易记录 & 错过机会

+

交易记录

-
-

记录错过机会

- - - - - - - - - - -
- -
+

交易复盘记录上传(含截图)

@@ -439,9 +419,6 @@
-
-
持仓占用导致错过(累计)
{{ occupied_miss_total }}
-
统计分析按北京时间 {{ stats_bundle.stats_reset_hour }}:00切日计入(与顶栏 UTC 列表窗无关)。历史总开仓(累计): {{ stats_bundle.total_opens_all }} 次 diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 81f50fb..73fcae5 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -83,7 +83,6 @@
交易所
{{ exchange_display }}
总交易
{{ total }}
-
错过次数
{{ miss_count }}
胜率
{{ rate }}%
资金账户(USDT)
{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}
交易日
{{ trading_day }}
diff --git a/lib/trade/trade_result_lib.py b/lib/trade/trade_result_lib.py index 838e5fa..c1c3cd5 100644 --- a/lib/trade/trade_result_lib.py +++ b/lib/trade/trade_result_lib.py @@ -28,6 +28,22 @@ def count_winning_trades(trades) -> int: return sum(1 for r in trades or [] if is_winning_pnl(r.get("effective_pnl_amount"))) +MISS_TRADE_RESULT = "错过" + + +def is_miss_trade_result(result) -> bool: + return (result or "").strip() == MISS_TRADE_RESULT + + +def filter_trade_records_excluding_miss(records): + """列表/统计:不展示、不计入「错过」类交易记录。""" + return [ + r + for r in (records or []) + if not is_miss_trade_result(r.get("effective_result") or r.get("result")) + ] + + def normalize_result_with_pnl(result, pnl_amount): """ 非手动平仓且实际盈利时,不应记为「止损」。 diff --git a/tests/test_trade_result_miss_filter.py b/tests/test_trade_result_miss_filter.py new file mode 100644 index 0000000..1a2812b --- /dev/null +++ b/tests/test_trade_result_miss_filter.py @@ -0,0 +1,26 @@ +"""trade_result_lib:过滤「错过」记录。""" +import unittest + +from lib.trade.trade_result_lib import ( + filter_trade_records_excluding_miss, + is_miss_trade_result, +) + + +class TradeResultMissFilterTest(unittest.TestCase): + def test_is_miss_trade_result(self): + self.assertTrue(is_miss_trade_result("错过")) + self.assertFalse(is_miss_trade_result("止盈")) + + def test_filter_excludes_miss(self): + rows = [ + {"effective_result": "止盈", "id": 1}, + {"effective_result": "错过", "id": 2}, + {"result": "错过", "id": 3}, + ] + out = filter_trade_records_excluding_miss(rows) + self.assertEqual([r["id"] for r in out], [1]) + + +if __name__ == "__main__": + unittest.main()