From f580e4542d7a0a91ed55661bed0dd2dfdebf65bc Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 7 Jul 2026 00:27:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20show=20entry=20type=20as=20category/sub?= =?UTF-8?q?type=20(=E5=8F=8D=E8=BD=AC/=E5=90=AF=E5=8A=A8A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format持仓与交易记录开仓类型为两级展示;新平仓写入与旧短标签自动展开。 Co-authored-by: Cursor --- crypto_monitor_binance/app.py | 8 +++- crypto_monitor_gate/app.py | 8 +++- crypto_monitor_okx/app.py | 8 +++- lib/hub/hub_trades_lib.py | 9 ++++- lib/trade/entry_model_lib.py | 73 +++++++++++++++++++++++++++++++---- tests/test_entry_model_lib.py | 13 ++++++- 6 files changed, 105 insertions(+), 14 deletions(-) diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index ca9b01b..85947af 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -185,6 +185,7 @@ from lib.trade.entry_model_lib import ( open_position_button_label, parse_manual_order_style_fields, resolve_effective_trade_entry_reason, + format_entry_type_display, resolve_trade_record_entry_reason, trend_manual_entry_reason_count, ) @@ -2115,6 +2116,7 @@ def to_effective_trade_dict(row): item["effective_entry_reason"] = resolve_effective_trade_entry_reason( reviewed_entry_reason=reviewed_er, entry_reason=item.get("entry_reason"), + entry_model=item.get("entry_model"), key_signal_type=(item.get("key_signal_type") or "").strip() or None, monitor_type=item.get("monitor_type"), trade_style=item.get("trade_style"), @@ -8856,7 +8858,11 @@ def export_trade_records(): er0 = (r["entry_reason"] or "").strip() if r["entry_reason"] else "" er1 = (r["reviewed_entry_reason"] or "").strip() if r["reviewed_entry_reason"] else "" kst = (r["key_signal_type"] or "").strip() if "key_signal_type" in r.keys() else "" - eff = er1 or er0 or entry_reason_from_key_signal(kst) or "" + eff = format_entry_type_display( + er1 or er0 or entry_reason_from_key_signal(kst) or "", + entry_model=r["entry_model"] if "entry_model" in r.keys() else None, + trade_style=r["trade_style"] if "trade_style" in r.keys() else None, + ) snap = r["initial_stop_loss"] if r["initial_stop_loss"] not in (None, "") else r["stop_loss"] data.append(( r["id"], r["symbol"], r["monitor_type"], kst, r["direction"], r["trigger_price"], diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 5430b04..452fcda 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -185,6 +185,7 @@ from lib.trade.entry_model_lib import ( open_position_button_label, parse_manual_order_style_fields, resolve_effective_trade_entry_reason, + format_entry_type_display, resolve_trade_record_entry_reason, trend_manual_entry_reason_count, ) @@ -2075,6 +2076,7 @@ def to_effective_trade_dict(row): item["effective_entry_reason"] = resolve_effective_trade_entry_reason( reviewed_entry_reason=reviewed_er, entry_reason=item.get("entry_reason"), + entry_model=item.get("entry_model"), key_signal_type=(item.get("key_signal_type") or "").strip() or None, monitor_type=item.get("monitor_type"), trade_style=item.get("trade_style"), @@ -8751,7 +8753,11 @@ def export_trade_records(): er0 = (r["entry_reason"] or "").strip() if r["entry_reason"] else "" er1 = (r["reviewed_entry_reason"] or "").strip() if r["reviewed_entry_reason"] else "" kst = (r["key_signal_type"] or "").strip() if "key_signal_type" in r.keys() else "" - eff = er1 or er0 or entry_reason_from_key_signal(kst) or "" + eff = format_entry_type_display( + er1 or er0 or entry_reason_from_key_signal(kst) or "", + entry_model=r["entry_model"] if "entry_model" in r.keys() else None, + trade_style=r["trade_style"] if "trade_style" in r.keys() else None, + ) snap = r["initial_stop_loss"] if r["initial_stop_loss"] not in (None, "") else r["stop_loss"] data.append(( r["id"], r["symbol"], r["monitor_type"], kst, r["direction"], r["trigger_price"], diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index c81c1ff..6d73586 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -183,6 +183,7 @@ from lib.trade.entry_model_lib import ( open_position_button_label, parse_manual_order_style_fields, resolve_effective_trade_entry_reason, + format_entry_type_display, resolve_trade_record_entry_reason, trend_manual_entry_reason_count, ) @@ -2022,6 +2023,7 @@ def to_effective_trade_dict(row): item["effective_entry_reason"] = resolve_effective_trade_entry_reason( reviewed_entry_reason=reviewed_er, entry_reason=item.get("entry_reason"), + entry_model=item.get("entry_model"), key_signal_type=(item.get("key_signal_type") or "").strip() or None, monitor_type=item.get("monitor_type"), trade_style=item.get("trade_style"), @@ -8276,7 +8278,11 @@ def export_trade_records(): er0 = (r["entry_reason"] or "").strip() if r["entry_reason"] else "" er1 = (r["reviewed_entry_reason"] or "").strip() if r["reviewed_entry_reason"] else "" kst = (r["key_signal_type"] or "").strip() if "key_signal_type" in r.keys() else "" - eff = er1 or er0 or entry_reason_from_key_signal(kst) or "" + eff = format_entry_type_display( + er1 or er0 or entry_reason_from_key_signal(kst) or "", + entry_model=r["entry_model"] if "entry_model" in r.keys() else None, + trade_style=r["trade_style"] if "trade_style" in r.keys() else None, + ) snap = r["initial_stop_loss"] if r["initial_stop_loss"] not in (None, "") else r["stop_loss"] data.append(( r["id"], r["symbol"], r["monitor_type"], kst, r["direction"], r["trigger_price"], diff --git a/lib/hub/hub_trades_lib.py b/lib/hub/hub_trades_lib.py index 162f6da..94ea521 100644 --- a/lib/hub/hub_trades_lib.py +++ b/lib/hub/hub_trades_lib.py @@ -10,6 +10,7 @@ from lib.strategy.strategy_trade_labels import ( entry_reason_for_monitor_type, ) from lib.trade.time_close_lib import TIME_CLOSE_RESULT +from lib.trade.entry_model_lib import format_entry_type_display TRADE_COMPLETED_RESULTS = ( "止盈", @@ -132,7 +133,13 @@ def display_entry_type_label(d: dict) -> str: label = effective_entry_type(d).strip() if not label: return "—" - return _normalize_monitor_type_label(label) or label + formatted = format_entry_type_display( + label, + entry_model=d.get("entry_model"), + trade_style=d.get("trade_style"), + ) + out = _normalize_monitor_type_label(formatted) or formatted + return out or "—" def effective_hold_minutes( diff --git a/lib/trade/entry_model_lib.py b/lib/trade/entry_model_lib.py index 7e05449..da64e44 100644 --- a/lib/trade/entry_model_lib.py +++ b/lib/trade/entry_model_lib.py @@ -223,6 +223,55 @@ def entry_model_label(code: Optional[str]) -> str: return _CODE_TO_LABEL.get(c, "") +def entry_category_display_prefix(category: str) -> str: + """两级展示用的一级前缀:反转 / 顺势 / 波段单(含日内)。""" + cat = (category or "").strip() + if cat in (ENTRY_CATEGORY_SWING, ENTRY_CATEGORY_INTRADAY): + return "波段单" + return ENTRY_CATEGORY_LABELS.get(cat, "") + + +def entry_model_display_label(code: Optional[str]) -> str: + """两级展示:反转/启动A、顺势/大分歧A、波段单/小分歧、波段单/假破。""" + c = normalize_entry_model_code(code) + if not c: + return "" + label = entry_model_label(c) + if not label: + return "" + prefix = entry_category_display_prefix(entry_model_category(c)) + if prefix: + return f"{prefix}/{label}" + return label + + +def format_entry_type_display( + text: Optional[str] = None, + *, + entry_model: Optional[str] = None, + trade_style: Optional[str] = None, +) -> str: + """交易记录/持仓展示:已知 entry_model 或短标签 → 两级文案。""" + if entry_model: + disp = entry_model_display_label(entry_model) + if disp: + return disp + raw = (text or "").strip() + if not raw: + ts = (trade_style or "").strip().lower() + if ts in ("trend", "swing"): + return trade_style_label_zh(ts) + return "" + if "/" in raw: + return raw + code = normalize_entry_model_code(raw) + if code: + disp = entry_model_display_label(code) + if disp: + return disp + return raw + + def trade_style_for_entry_model(code: Optional[str]) -> str: c = normalize_entry_model_code(code) return _CODE_TO_STYLE.get(c, "trend") @@ -297,7 +346,7 @@ def resolve_trade_record_entry_reason( er = (entry_reason or "").strip() if er: return er - label = entry_model_label(entry_model) + label = entry_model_display_label(entry_model) if label: return label kst = (key_signal_type or "").strip() @@ -330,14 +379,22 @@ def resolve_effective_trade_entry_reason( for raw in (reviewed_entry_reason, entry_reason): er = (raw or "").strip() if er: - return er - return resolve_trade_record_entry_reason( + return format_entry_type_display( + er, + entry_model=entry_model, + trade_style=trade_style, + ) + return format_entry_type_display( + resolve_trade_record_entry_reason( + entry_model=entry_model, + key_signal_type=key_signal_type, + monitor_type=monitor_type, + trade_style=trade_style, + entry_reason_from_key_signal=entry_reason_from_key_signal, + entry_reason_for_monitor_type=entry_reason_for_monitor_type, + ), entry_model=entry_model, - key_signal_type=key_signal_type, - monitor_type=monitor_type, trade_style=trade_style, - entry_reason_from_key_signal=entry_reason_from_key_signal, - entry_reason_for_monitor_type=entry_reason_for_monitor_type, ) @@ -345,7 +402,7 @@ def enrich_entry_model_display(item: dict) -> dict: code = normalize_entry_model_code(item.get("entry_model")) if code: item["entry_model"] = code - item["entry_model_label"] = entry_model_label(code) + item["entry_model_label"] = entry_model_display_label(code) cat = entry_model_category(code) if cat: item["entry_model_category"] = cat diff --git a/tests/test_entry_model_lib.py b/tests/test_entry_model_lib.py index f72079d..89b47a3 100644 --- a/tests/test_entry_model_lib.py +++ b/tests/test_entry_model_lib.py @@ -11,7 +11,9 @@ from lib.trade.entry_model_lib import ( build_trend_div_entry_reason_options, entry_model_categories, entry_model_category, + entry_model_display_label, entry_model_label, + format_entry_type_display, hub_meta_entry_context, intraday_entry_model_options, is_intraday_trading_profile, @@ -123,9 +125,16 @@ class TestEntryModelLib(unittest.TestCase): def test_resolve_entry_reason_from_model(self): er = resolve_trade_record_entry_reason(entry_model=ENTRY_MODEL_BIG_DIV_B) - self.assertEqual(er, "大分歧B") + self.assertEqual(er, "顺势/大分歧B") er2 = resolve_trade_record_entry_reason(entry_model=ENTRY_MODEL_LAUNCH_A) - self.assertEqual(er2, "启动A") + self.assertEqual(er2, "反转/启动A") + + def test_entry_model_display_label(self): + self.assertEqual(entry_model_display_label(ENTRY_MODEL_LAUNCH_A), "反转/启动A") + self.assertEqual(entry_model_display_label(ENTRY_MODEL_SMALL_DIV), "波段单/小分歧") + self.assertEqual(entry_model_display_label("liquidity_false_break"), "波段单/假破") + self.assertEqual(format_entry_type_display("启动A"), "反转/启动A") + self.assertEqual(entry_model_label(ENTRY_MODEL_LAUNCH_B), "启动B") def test_resolve_entry_reason_trade_style_fallback(self): er = resolve_trade_record_entry_reason(trade_style="swing")