diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 0d064a2..baa4a76 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -49,8 +49,10 @@ from fib_key_monitor_lib import ( ) from strategy_trade_labels import ( STRATEGY_ENTRY_REASON_OPTIONS, + apply_order_monitor_source_labels, entry_reason_for_monitor_type, handoff_trade_miss_reason, + order_monitor_source_type, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, ) @@ -2556,9 +2558,7 @@ def enrich_order_item(raw_item, current_capital): item["breakeven_enabled"] = 0 if be is not None and int(be) == 0 else 1 except Exception: item["breakeven_enabled"] = 1 - if not (item.get("monitor_type") or "").strip(): - item["monitor_type"] = ORDER_MONITOR_TYPE_MANUAL - return item + return apply_order_monitor_source_labels(item, default_manual=ORDER_MONITOR_TYPE_MANUAL) def ensure_exchange_live_ready(): @@ -2570,17 +2570,7 @@ def ensure_exchange_live_ready(): def order_row_monitor_type(row): - if row is None: - return ORDER_MONITOR_TYPE_MANUAL - try: - keys = row.keys() if hasattr(row, "keys") else [] - except Exception: - keys = [] - if "monitor_type" in keys: - mt = (row["monitor_type"] or "").strip() - if mt: - return mt - return ORDER_MONITOR_TYPE_MANUAL + return order_monitor_source_type(row, default_manual=ORDER_MONITOR_TYPE_MANUAL) def trade_record_monitor_type(conn, row): diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index ad07c97..316b4bf 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -50,8 +50,10 @@ from fib_key_monitor_lib import ( ) from strategy_trade_labels import ( STRATEGY_ENTRY_REASON_OPTIONS, + apply_order_monitor_source_labels, entry_reason_for_monitor_type, handoff_trade_miss_reason, + order_monitor_source_type, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, ) @@ -2282,9 +2284,7 @@ def enrich_order_item(raw_item, current_capital): item["breakeven_enabled"] = 0 if be is not None and int(be) == 0 else 1 except Exception: item["breakeven_enabled"] = 1 - if not (item.get("monitor_type") or "").strip(): - item["monitor_type"] = ORDER_MONITOR_TYPE_MANUAL - return item + return apply_order_monitor_source_labels(item, default_manual=ORDER_MONITOR_TYPE_MANUAL) def ensure_exchange_live_ready(): @@ -2296,17 +2296,7 @@ def ensure_exchange_live_ready(): def order_row_monitor_type(row): - if row is None: - return ORDER_MONITOR_TYPE_MANUAL - try: - keys = row.keys() if hasattr(row, "keys") else [] - except Exception: - keys = [] - if "monitor_type" in keys: - mt = (row["monitor_type"] or "").strip() - if mt: - return mt - return ORDER_MONITOR_TYPE_MANUAL + return order_monitor_source_type(row, default_manual=ORDER_MONITOR_TYPE_MANUAL) def trade_record_monitor_type(conn, row): diff --git a/crypto_monitor_gate_bot/app.py b/crypto_monitor_gate_bot/app.py index 4bc8ed7..28e9656 100644 --- a/crypto_monitor_gate_bot/app.py +++ b/crypto_monitor_gate_bot/app.py @@ -60,6 +60,7 @@ from journal_chart_lib import ( from hub_auth import request_allowed as hub_request_allowed from strategy_trade_labels import ( STRATEGY_ENTRY_REASON_OPTIONS, + apply_order_monitor_source_labels, handoff_trade_miss_reason, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, @@ -2311,7 +2312,7 @@ def enrich_order_item(raw_item, current_capital): item["breakeven_enabled"] = 0 if be is not None and int(be) == 0 else 1 except Exception: item["breakeven_enabled"] = 1 - return item + return apply_order_monitor_source_labels(item, default_manual="下单监控") def ensure_exchange_live_ready(): diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index d1a49de..e1ffa90 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -49,8 +49,10 @@ from fib_key_monitor_lib import ( ) from strategy_trade_labels import ( STRATEGY_ENTRY_REASON_OPTIONS, + apply_order_monitor_source_labels, entry_reason_for_monitor_type, handoff_trade_miss_reason, + order_monitor_source_type, trade_record_monitor_type as resolve_trade_record_monitor_type, trend_plan_id_from_monitor_row, ) @@ -2138,7 +2140,7 @@ def enrich_order_item(raw_item, current_capital): item["breakeven_enabled"] = 0 if be is not None and int(be) == 0 else 1 except Exception: item["breakeven_enabled"] = 1 - return item + return apply_order_monitor_source_labels(item, default_manual=ORDER_MONITOR_TYPE_MANUAL) def ensure_okx_live_ready(): @@ -2150,17 +2152,7 @@ def ensure_okx_live_ready(): def order_row_monitor_type(row): - if row is None: - return ORDER_MONITOR_TYPE_MANUAL - try: - keys = row.keys() if hasattr(row, "keys") else [] - except Exception: - keys = [] - if "monitor_type" in keys: - mt = (row["monitor_type"] or "").strip() - if mt: - return mt - return ORDER_MONITOR_TYPE_MANUAL + return order_monitor_source_type(row, default_manual=ORDER_MONITOR_TYPE_MANUAL) def trade_record_monitor_type(conn, row): diff --git a/hub_bridge.py b/hub_bridge.py index 2bfd1d5..1c4cd62 100644 --- a/hub_bridge.py +++ b/hub_bridge.py @@ -283,7 +283,14 @@ def register_hub_routes(app): for row in conn.execute( "SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC" ).fetchall(): - orders.append(_row_to_dict(row)) + od = _row_to_dict(row) + try: + from strategy_trade_labels import apply_order_monitor_source_labels + + od = apply_order_monitor_source_labels(od) + except Exception: + pass + orders.append(od) trends = [] if c.get("has_trend"): for row in conn.execute( diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 551194c..1717890 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -303,6 +303,27 @@ return side || "—"; } + function monitorOrderSourceLabel(mo) { + const o = mo || {}; + const tid = Number(o.trend_plan_id); + if (Number.isFinite(tid) && tid > 0) return "趋势回调"; + const mt = String(o.monitor_type || "").trim(); + if (mt === "趋势回调") return "趋势回调"; + const kst = String(o.key_signal_type || "").trim(); + if (kst === "趋势回调" || kst === "趋势回调计划") return "趋势回调"; + return mt || "下单监控"; + } + + function monitorOrderSourceHtml(mo) { + const src = monitorOrderSourceLabel(mo); + const kst = String((mo && mo.key_signal_type) || "").trim(); + let text = src; + if (kst && kst !== src && !text.includes(kst)) { + text += " · " + kst; + } + return `来源: ${esc(text)}`; + } + function renderDirectionHtml(side) { const cls = sideDirCls(side); const label = sideDirLabel(side); @@ -1052,7 +1073,9 @@ : tp.avg_entry_price; const entryN = entryRaw != null && entryRaw !== "" ? Number(entryRaw) : null; const isTrend = - !!(trendPlan && trendPlan.id) || String(mo.monitor_type || "").trim() === "趋势回调"; + !!(trendPlan && trendPlan.id) || + String(mo.monitor_type || "").trim() === "趋势回调" || + (mo.trend_plan_id != null && Number(mo.trend_plan_id) > 0); let sl = mo.stop_loss != null && mo.stop_loss !== "" ? mo.stop_loss : ""; let takeProfit = mo.take_profit != null && mo.take_profit !== "" ? mo.take_profit : ""; @@ -1422,10 +1445,8 @@ pnlText += ` (${pct >= 0 ? "" : ""}${pct.toFixed(2)}%)`; } const meta = []; - if (mo.monitor_type || mo.key_signal_type) { - meta.push( - `来源: ${esc(mo.monitor_type || "下单监控")}${mo.key_signal_type ? " · " + esc(mo.key_signal_type) : ""}` - ); + if (mo.monitor_type || mo.key_signal_type || mo.trend_plan_id) { + meta.push(monitorOrderSourceHtml(mo)); } else { meta.push("来源: 交易所持仓"); } diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index f9e15d5..e8eef02 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -229,6 +229,6 @@
- + diff --git a/scripts/fix_trend_handoff_monitor_type.py b/scripts/fix_trend_handoff_monitor_type.py new file mode 100644 index 0000000..f6be631 --- /dev/null +++ b/scripts/fix_trend_handoff_monitor_type.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""修正趋势保本移交后 monitor_type 仍为「下单监控」的历史数据。""" +from __future__ import annotations + +import argparse +import sqlite3 +from pathlib import Path + +from strategy_trade_labels import MONITOR_TYPE_TREND_PULLBACK + + +def main() -> int: + parser = argparse.ArgumentParser(description="Fix trend handoff order/trade monitor_type labels.") + parser.add_argument("--db", required=True, help="Path to instance sqlite db") + parser.add_argument("--dry-run", action="store_true", help="Preview only") + parser.add_argument("--apply", action="store_true", help="Apply updates") + args = parser.parse_args() + if not args.dry_run and not args.apply: + args.dry_run = True + + db_path = Path(args.db).expanduser().resolve() + if not db_path.is_file(): + print(f"[ERR] DB not found: {db_path}") + return 1 + + conn = sqlite3.connect(str(db_path)) + conn.row_factory = sqlite3.Row + cur = conn.cursor() + + cur.execute( + """ + SELECT COUNT(*) AS c FROM order_monitors + WHERE trend_plan_id IS NOT NULL AND trend_plan_id > 0 + AND (monitor_type IS NULL OR TRIM(monitor_type) = '' OR monitor_type = '下单监控') + """ + ) + om_n = int(cur.fetchone()["c"]) + cur.execute( + """ + SELECT COUNT(*) AS c FROM trade_records + WHERE trend_plan_id IS NOT NULL AND trend_plan_id > 0 + AND (monitor_type IS NULL OR TRIM(monitor_type) = '' OR monitor_type = '下单监控') + """ + ) + tr_n = int(cur.fetchone()["c"]) + print(f"[INFO] order_monitors to fix: {om_n}") + print(f"[INFO] trade_records to fix: {tr_n}") + + if args.dry_run: + conn.close() + return 0 + + cur.execute( + """ + UPDATE order_monitors + SET monitor_type=? + WHERE trend_plan_id IS NOT NULL AND trend_plan_id > 0 + AND (monitor_type IS NULL OR TRIM(monitor_type) = '' OR monitor_type = '下单监控') + """, + (MONITOR_TYPE_TREND_PULLBACK,), + ) + cur.execute( + """ + UPDATE trade_records + SET monitor_type=? + WHERE trend_plan_id IS NOT NULL AND trend_plan_id > 0 + AND (monitor_type IS NULL OR TRIM(monitor_type) = '' OR monitor_type = '下单监控') + """, + (MONITOR_TYPE_TREND_PULLBACK,), + ) + conn.commit() + conn.close() + print("[OK] Applied.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/strategy_trade_labels.py b/strategy_trade_labels.py index be3c7a9..b510f76 100644 --- a/strategy_trade_labels.py +++ b/strategy_trade_labels.py @@ -81,8 +81,45 @@ def _row_monitor_type(row, default_manual: str) -> str: return default_manual +def _row_key_signal_type(row) -> str: + if row is None: + return "" + try: + keys = row.keys() if hasattr(row, "keys") else [] + except Exception: + keys = [] + if "key_signal_type" not in keys: + return "" + return (row["key_signal_type"] or "").strip() + + +def order_monitor_source_type(row, *, default_manual: str = "下单监控") -> str: + """展示/平仓记录:趋势保本移交单来源为「趋势回调」,非「下单监控」。""" + if trend_plan_id_from_monitor_row(row) is not None: + return MONITOR_TYPE_TREND_PULLBACK + mt = _row_monitor_type(row, default_manual) + if mt != default_manual: + return mt + kst = _row_key_signal_type(row) + if kst in ( + MONITOR_TYPE_TREND_PULLBACK, + TREND_HANDOFF_KEY_SIGNAL, + TREND_HANDOFF_TRADE_NOTE, + ENTRY_REASON_TREND_PULLBACK, + ): + return MONITOR_TYPE_TREND_PULLBACK + return mt + + +def apply_order_monitor_source_labels(item: dict, *, default_manual: str = "下单监控") -> dict: + """实例页 / 中控 API:统一修正 order_monitors 展示用 monitor_type。""" + out = dict(item or {}) + out["monitor_type"] = order_monitor_source_type(out, default_manual=default_manual) + return out + + def trade_record_monitor_type(conn, order_row, *, default_manual: str = "下单监控") -> str: - """平仓写入 trade_records 时:曾顺势加仓则标「顺势加仓」,否则沿用监控单类型。""" + """平仓写入 trade_records 时:曾顺势加仓则标「顺势加仓」,否则沿用监控单来源类型。""" oid = None try: keys = order_row.keys() if hasattr(order_row, "keys") else [] @@ -92,7 +129,7 @@ def trade_record_monitor_type(conn, order_row, *, default_manual: str = "下单 oid = None if oid and order_had_roll_fills(conn, oid): return MONITOR_TYPE_ROLL - return _row_monitor_type(order_row, default_manual) + return order_monitor_source_type(order_row, default_manual=default_manual) def entry_reason_for_monitor_type(monitor_type: str | None) -> str: diff --git a/strategy_trend_register.py b/strategy_trend_register.py index 3990ad9..9ecd368 100644 --- a/strategy_trend_register.py +++ b/strategy_trend_register.py @@ -748,7 +748,7 @@ def _insert_trend_handoff_order_monitor( if not trading_day and callable(getattr(m, "get_trading_day", None)): trading_day = m.get_trading_day() notional = margin_cap * lev if margin_cap and lev else None - monitor_type = _order_monitor_manual_type(m) + monitor_type = MONITOR_TYPE_TREND_PULLBACK conn.execute( "INSERT INTO order_monitors " "(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "