From fa59fc12736ac1ef459c1b5ff48fe8b657e4bb9b Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 11 Jun 2026 08:22:41 +0800 Subject: [PATCH] fix: show limit-order gate for false breakout monitors False breakout used box/convergence volume/break gates in the UI; now shows pending limit order status like fib monitors. Co-authored-by: Cursor --- crypto_monitor_binance/app.py | 25 ++++++++++++++++--- crypto_monitor_gate/app.py | 25 ++++++++++++++++--- crypto_monitor_gate_bot/app.py | 25 ++++++++++++++++--- crypto_monitor_okx/app.py | 25 ++++++++++++++++--- false_breakout_key_monitor_lib.py | 26 ++++++++++++++++++++ tests/test_false_breakout_key_monitor_lib.py | 15 +++++++++++ 6 files changed, 129 insertions(+), 12 deletions(-) diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index 36b27c9..26cd2b5 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -57,6 +57,7 @@ from false_breakout_key_monitor_lib import ( FALSE_BREAKOUT_VALIDITY_HOURS, calc_false_breakout_plan, expires_at_text, + false_breakout_gate_preview, is_false_breakout_expired, is_false_breakout_key_monitor_type, is_limit_key_monitor_type, @@ -6329,7 +6330,8 @@ def api_price_snapshot(): key_prices = [] for r in key_rows: is_fib = is_fib_key_monitor_type(r["monitor_type"]) - if is_fib: + is_fb = is_false_breakout_key_monitor_type(r["monitor_type"]) + if is_fib or is_fb: price = get_symbol_mark_price(r["symbol"]) else: price = prices.get(r["symbol"]) @@ -6341,6 +6343,7 @@ def api_price_snapshot(): gate_summary = "-" gate_metrics = "" fib_gate_ok = True + fb_gate_ok = True if is_fib: direction = (r["direction"] or "long").lower() inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) @@ -6350,6 +6353,18 @@ def api_price_snapshot(): gate_summary = f"斐波 挂E={entry_txt} {'标记价将失效' if inval else '等待成交'}" if _sqlite_row_val(r, "fib_limit_order_id"): gate_metrics = f"限价单:{_sqlite_row_val(r, 'fib_limit_order_id')}" + elif is_fb: + entry = _sqlite_row_val(r, "fib_entry_price") + entry_txt = format_price_for_symbol(r["symbol"], entry) if entry else "-" + prev = false_breakout_gate_preview( + entry_display=entry_txt, + limit_order_id=_sqlite_row_val(r, "fib_limit_order_id"), + created_at=r["created_at"], + now=app_now(), + ) + gate_summary = prev.get("summary") or "-" + gate_metrics = prev.get("metrics") or "" + fb_gate_ok = bool(prev.get("gate_ok")) elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: try: prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) @@ -6357,7 +6372,7 @@ def api_price_snapshot(): gate_metrics = prev.get("metrics") or "" except Exception: gate_summary = "-" - else: + elif (r["monitor_type"] or "").strip() in KEY_MONITOR_AUTO_TYPES: try: gate = _key_hard_checks( r["symbol"], @@ -6402,7 +6417,11 @@ def api_price_snapshot(): "lower_diff": lower_diff, "lower_pct": lower_pct, "gate_summary": gate_summary, - "gate_ok": fib_gate_ok if is_fib else bool(gate and gate.get("ok")), + "gate_ok": ( + fib_gate_ok if is_fib + else fb_gate_ok if is_fb + else bool(gate and gate.get("ok")) + ), "gate_metrics": gate_metrics, }) diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 5fdd335..0f773e5 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -58,6 +58,7 @@ from false_breakout_key_monitor_lib import ( FALSE_BREAKOUT_VALIDITY_HOURS, calc_false_breakout_plan, expires_at_text, + false_breakout_gate_preview, is_false_breakout_expired, is_false_breakout_key_monitor_type, is_limit_key_monitor_type, @@ -6448,7 +6449,8 @@ def api_price_snapshot(): key_prices = [] for r in key_rows: is_fib = is_fib_key_monitor_type(r["monitor_type"]) - if is_fib: + is_fb = is_false_breakout_key_monitor_type(r["monitor_type"]) + if is_fib or is_fb: price = get_symbol_mark_price(r["symbol"]) else: price = prices.get(r["symbol"]) @@ -6460,6 +6462,7 @@ def api_price_snapshot(): gate_summary = "-" gate_metrics = "" fib_gate_ok = True + fb_gate_ok = True if is_fib: direction = (r["direction"] or "long").lower() inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) @@ -6469,6 +6472,18 @@ def api_price_snapshot(): gate_summary = f"斐波 挂E={entry_txt} {'标记价将失效' if inval else '等待成交'}" if _sqlite_row_val(r, "fib_limit_order_id"): gate_metrics = f"限价单:{_sqlite_row_val(r, 'fib_limit_order_id')}" + elif is_fb: + entry = _sqlite_row_val(r, "fib_entry_price") + entry_txt = format_price_for_symbol(r["symbol"], entry) if entry else "-" + prev = false_breakout_gate_preview( + entry_display=entry_txt, + limit_order_id=_sqlite_row_val(r, "fib_limit_order_id"), + created_at=r["created_at"], + now=app_now(), + ) + gate_summary = prev.get("summary") or "-" + gate_metrics = prev.get("metrics") or "" + fb_gate_ok = bool(prev.get("gate_ok")) elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: try: prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) @@ -6476,7 +6491,7 @@ def api_price_snapshot(): gate_metrics = prev.get("metrics") or "" except Exception: gate_summary = "-" - else: + elif (r["monitor_type"] or "").strip() in KEY_MONITOR_AUTO_TYPES: try: gate = _key_hard_checks( r["symbol"], @@ -6525,7 +6540,11 @@ def api_price_snapshot(): "lower_diff": lower_diff, "lower_pct": lower_pct, "gate_summary": gate_summary, - "gate_ok": fib_gate_ok if is_fib else bool(gate and gate.get("ok")), + "gate_ok": ( + fib_gate_ok if is_fib + else fb_gate_ok if is_fb + else bool(gate and gate.get("ok")) + ), "gate_metrics": gate_metrics, }) diff --git a/crypto_monitor_gate_bot/app.py b/crypto_monitor_gate_bot/app.py index b5f14bc..edcdac0 100644 --- a/crypto_monitor_gate_bot/app.py +++ b/crypto_monitor_gate_bot/app.py @@ -58,6 +58,7 @@ from false_breakout_key_monitor_lib import ( FALSE_BREAKOUT_VALIDITY_HOURS, calc_false_breakout_plan, expires_at_text, + false_breakout_gate_preview, is_false_breakout_expired, is_false_breakout_key_monitor_type, is_limit_key_monitor_type, @@ -6448,7 +6449,8 @@ def api_price_snapshot(): key_prices = [] for r in key_rows: is_fib = is_fib_key_monitor_type(r["monitor_type"]) - if is_fib: + is_fb = is_false_breakout_key_monitor_type(r["monitor_type"]) + if is_fib or is_fb: price = get_symbol_mark_price(r["symbol"]) else: price = prices.get(r["symbol"]) @@ -6460,6 +6462,7 @@ def api_price_snapshot(): gate_summary = "-" gate_metrics = "" fib_gate_ok = True + fb_gate_ok = True if is_fib: direction = (r["direction"] or "long").lower() inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) @@ -6469,6 +6472,18 @@ def api_price_snapshot(): gate_summary = f"斐波 挂E={entry_txt} {'标记价将失效' if inval else '等待成交'}" if _sqlite_row_val(r, "fib_limit_order_id"): gate_metrics = f"限价单:{_sqlite_row_val(r, 'fib_limit_order_id')}" + elif is_fb: + entry = _sqlite_row_val(r, "fib_entry_price") + entry_txt = format_price_for_symbol(r["symbol"], entry) if entry else "-" + prev = false_breakout_gate_preview( + entry_display=entry_txt, + limit_order_id=_sqlite_row_val(r, "fib_limit_order_id"), + created_at=r["created_at"], + now=app_now(), + ) + gate_summary = prev.get("summary") or "-" + gate_metrics = prev.get("metrics") or "" + fb_gate_ok = bool(prev.get("gate_ok")) elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: try: prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) @@ -6476,7 +6491,7 @@ def api_price_snapshot(): gate_metrics = prev.get("metrics") or "" except Exception: gate_summary = "-" - else: + elif (r["monitor_type"] or "").strip() in KEY_MONITOR_AUTO_TYPES: try: gate = _key_hard_checks( r["symbol"], @@ -6525,7 +6540,11 @@ def api_price_snapshot(): "lower_diff": lower_diff, "lower_pct": lower_pct, "gate_summary": gate_summary, - "gate_ok": fib_gate_ok if is_fib else bool(gate and gate.get("ok")), + "gate_ok": ( + fib_gate_ok if is_fib + else fb_gate_ok if is_fb + else bool(gate and gate.get("ok")) + ), "gate_metrics": gate_metrics, }) diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 3045e48..dd38fff 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -57,6 +57,7 @@ from false_breakout_key_monitor_lib import ( FALSE_BREAKOUT_VALIDITY_HOURS, calc_false_breakout_plan, expires_at_text, + false_breakout_gate_preview, is_false_breakout_expired, is_false_breakout_key_monitor_type, is_limit_key_monitor_type, @@ -6035,7 +6036,8 @@ def api_price_snapshot(): key_prices = [] for r in key_rows: is_fib = is_fib_key_monitor_type(r["monitor_type"]) - if is_fib: + is_fb = is_false_breakout_key_monitor_type(r["monitor_type"]) + if is_fib or is_fb: price = get_symbol_mark_price(r["symbol"]) else: price = prices.get(r["symbol"]) @@ -6047,6 +6049,7 @@ def api_price_snapshot(): gate_summary = "-" gate_metrics = "" fib_gate_ok = True + fb_gate_ok = True if is_fib: direction = (r["direction"] or "long").lower() inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) @@ -6056,6 +6059,18 @@ def api_price_snapshot(): gate_summary = f"斐波 挂E={entry_txt} {'标记价将失效' if inval else '等待成交'}" if _sqlite_row_val(r, "fib_limit_order_id"): gate_metrics = f"限价单:{_sqlite_row_val(r, 'fib_limit_order_id')}" + elif is_fb: + entry = _sqlite_row_val(r, "fib_entry_price") + entry_txt = format_price_for_symbol(r["symbol"], entry) if entry else "-" + prev = false_breakout_gate_preview( + entry_display=entry_txt, + limit_order_id=_sqlite_row_val(r, "fib_limit_order_id"), + created_at=r["created_at"], + now=app_now(), + ) + gate_summary = prev.get("summary") or "-" + gate_metrics = prev.get("metrics") or "" + fb_gate_ok = bool(prev.get("gate_ok")) elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: try: prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) @@ -6063,7 +6078,7 @@ def api_price_snapshot(): gate_metrics = prev.get("metrics") or "" except Exception: gate_summary = "-" - else: + elif (r["monitor_type"] or "").strip() in KEY_MONITOR_AUTO_TYPES: try: gate = _key_hard_checks( r["symbol"], @@ -6112,7 +6127,11 @@ def api_price_snapshot(): "lower_diff": lower_diff, "lower_pct": lower_pct, "gate_summary": gate_summary, - "gate_ok": fib_gate_ok if is_fib else bool(gate and gate.get("ok")), + "gate_ok": ( + fib_gate_ok if is_fib + else fb_gate_ok if is_fb + else bool(gate and gate.get("ok")) + ), "gate_metrics": gate_metrics, }) diff --git a/false_breakout_key_monitor_lib.py b/false_breakout_key_monitor_lib.py index 72b50d0..f8393a8 100644 --- a/false_breakout_key_monitor_lib.py +++ b/false_breakout_key_monitor_lib.py @@ -117,3 +117,29 @@ def expires_at_text(created_at: Any, *, hours: int = FALSE_BREAKOUT_VALIDITY_HOU if dt is None: return "—" return (dt + timedelta(hours=hours)).strftime("%Y-%m-%d %H:%M:%S") + + +def false_breakout_gate_preview( + *, + entry_display: str, + limit_order_id: Any = None, + created_at: Any = None, + now: Optional[datetime] = None, + hours: int = FALSE_BREAKOUT_VALIDITY_HOURS, +) -> dict[str, Any]: + """假突破门控预览:限价挂单状态,不使用箱体/收敛的量破幅二确门控。""" + now_dt = now or datetime.now() + expired = is_false_breakout_expired(created_at, now_dt, hours=hours) + exp_txt = expires_at_text(created_at, hours=hours) + status = "已过期" if expired else "等待成交" + metrics_parts: list[str] = [] + oid = str(limit_order_id or "").strip() + if oid: + metrics_parts.append(f"限价单:{oid}") + if exp_txt != "—": + metrics_parts.append(f"截至:{exp_txt}") + return { + "summary": f"假突破 挂E={entry_display} {status}", + "metrics": " ".join(metrics_parts), + "gate_ok": not expired, + } diff --git a/tests/test_false_breakout_key_monitor_lib.py b/tests/test_false_breakout_key_monitor_lib.py index bbeca63..3d29c1f 100644 --- a/tests/test_false_breakout_key_monitor_lib.py +++ b/tests/test_false_breakout_key_monitor_lib.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta from false_breakout_key_monitor_lib import ( FALSE_BREAKOUT_MONITOR_TYPE, calc_false_breakout_plan, + false_breakout_gate_preview, is_false_breakout_expired, key_price_from_row, normalize_false_breakout_symbol, @@ -56,6 +57,20 @@ class FalseBreakoutKeyMonitorLibTests(unittest.TestCase): def test_monitor_type_constant(self): self.assertEqual(FALSE_BREAKOUT_MONITOR_TYPE, "假突破") + def test_gate_preview_not_box_gate(self): + now = datetime(2026, 6, 7, 12, 0, 0) + prev = false_breakout_gate_preview( + entry_display="1635.0", + limit_order_id="oid-1", + created_at="2026-06-07 10:00:00", + now=now, + ) + self.assertIn("假突破", prev["summary"]) + self.assertIn("等待成交", prev["summary"]) + self.assertNotIn("量:", prev["summary"]) + self.assertIn("限价单:oid-1", prev["metrics"]) + self.assertTrue(prev["gate_ok"]) + if __name__ == "__main__": unittest.main()