fix(key-monitor): repair trigger entry bugs in four exchange apps

Restore missing check_fib_key_monitors, fix gate preview and OKX add_key,
and unify trigger execution error handling to avoid duplicate history writes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-14 01:06:53 +08:00
parent edf4bb835d
commit 4573ccca9a
4 changed files with 179 additions and 130 deletions
+60 -56
View File
@@ -5421,18 +5421,30 @@ def _execute_trigger_entry_cross(conn, row):
conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,)) conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,))
conn.commit() conn.commit()
ok, err, det = _market_open_for_trigger_entry( try:
conn, ok, err, det = _market_open_for_trigger_entry(
symbol, conn,
direction, symbol,
ex_sym, direction,
entry, ex_sym,
sl, entry,
tp, sl,
breakeven_enabled=be_en, tp,
time_close_enabled=tc_en, breakeven_enabled=be_en,
time_close_hours=tc_h, time_close_enabled=tc_en,
) time_close_hours=tc_h,
)
except Exception as e:
fail_msg = friendly_exchange_error(e)
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 计划入场:{format_price_for_symbol(symbol, entry)}\n"
f"- 原因:{fail_msg}\n"
)
insert_key_monitor_history(conn, row, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
return False, fail_msg
if ok and det: if ok and det:
rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-" rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-"
msg = ( msg = (
@@ -5498,22 +5510,12 @@ def check_trigger_entry_key_monitors():
_finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE) _finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE)
continue continue
if trigger_entry_reached(direction, mark, entry): if trigger_entry_reached(direction, mark, entry):
try: _execute_trigger_entry_cross(conn, r)
_execute_trigger_entry_cross(conn, r)
except Exception as e:
fail_msg = friendly_exchange_error(e)
try:
insert_key_monitor_history(conn, r, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
except Exception:
pass
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n**账户:{_wechat_account_label()}**\n- {fail_msg}\n"
)
conn.commit() conn.commit()
conn.close() conn.close()
def check_fib_key_monitors():
conn = get_db() conn = get_db()
rows = conn.execute("SELECT * FROM key_monitors").fetchall() rows = conn.execute("SELECT * FROM key_monitors").fetchall()
for r in rows: for r in rows:
@@ -6857,6 +6859,7 @@ def api_price_snapshot():
gate_metrics = "" gate_metrics = ""
fib_gate_ok = True fib_gate_ok = True
fb_gate_ok = True fb_gate_ok = True
te_gate_ok = True
if is_fib: if is_fib:
direction = (r["direction"] or "long").lower() direction = (r["direction"] or "long").lower()
inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"])
@@ -6895,7 +6898,7 @@ def api_price_snapshot():
) )
gate_summary = prev.get("summary") or "-" gate_summary = prev.get("summary") or "-"
gate_metrics = prev.get("metrics") or "" gate_metrics = prev.get("metrics") or ""
fib_gate_ok = bool(prev.get("gate_ok")) te_gate_ok = bool(prev.get("gate_ok"))
elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES:
try: try:
prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"])
@@ -6951,6 +6954,7 @@ def api_price_snapshot():
"gate_ok": ( "gate_ok": (
fib_gate_ok if is_fib fib_gate_ok if is_fib
else fb_gate_ok if is_fb else fb_gate_ok if is_fb
else te_gate_ok if is_te
else bool(gate and gate.get("ok")) else bool(gate and gate.get("ok"))
), ),
"gate_metrics": gate_metrics, "gate_metrics": gate_metrics,
@@ -7512,40 +7516,40 @@ def add_key():
if tc_en and not tc_h: if tc_en and not tc_h:
tc_en = 0 tc_en = 0
if is_trigger_entry_key_monitor_type(mt): if is_trigger_entry_key_monitor_type(mt):
if direction_sel not in ("long", "short"): if direction_sel not in ("long", "short"):
conn.close()
conn = None
flash("触价开仓请选择做多或做空")
return redirect("/key_monitor")
try:
entry_px = float(d.get("trigger_entry") or 0)
sl_px = float(d.get("trigger_sl") or 0)
tp_px = float(d.get("trigger_tp") or 0)
except (TypeError, ValueError):
entry_px = sl_px = tp_px = 0
if entry_px <= 0 or sl_px <= 0 or tp_px <= 0:
conn.close()
conn = None
flash("触价开仓须填写有效的入场价、止损价、止盈价")
return redirect("/key_monitor")
ok_te, err_te = _add_trigger_entry_key_monitor(
conn, symbol, direction_sel, entry_px, sl_px, tp_px, breakeven_enabled=be_flag,
time_close_enabled=tc_en, time_close_hours=tc_h,
)
conn.commit()
conn.close() conn.close()
conn = None conn = None
if not ok_te: flash("触价开仓请选择做多或做空")
flash(err_te or "触价开仓监控添加失败")
return redirect("/key_monitor")
flash(
f"触价开仓已添加({symbol} 日成交量排名 {rank}/{total}"
f"|有效期 {TRIGGER_ENTRY_VALIDITY_HOURS}h"
f"|标记价触达入场价后下一轮询市价开仓"
f"|移动保本:{'' if be_flag else ''}"
+ (f"{time_close_label(tc_h)}" if tc_en else "")
)
return redirect("/key_monitor") return redirect("/key_monitor")
try:
entry_px = float(d.get("trigger_entry") or 0)
sl_px = float(d.get("trigger_sl") or 0)
tp_px = float(d.get("trigger_tp") or 0)
except (TypeError, ValueError):
entry_px = sl_px = tp_px = 0
if entry_px <= 0 or sl_px <= 0 or tp_px <= 0:
conn.close()
conn = None
flash("触价开仓须填写有效的入场价、止损价、止盈价")
return redirect("/key_monitor")
ok_te, err_te = _add_trigger_entry_key_monitor(
conn, symbol, direction_sel, entry_px, sl_px, tp_px, breakeven_enabled=be_flag,
time_close_enabled=tc_en, time_close_hours=tc_h,
)
conn.commit()
conn.close()
conn = None
if not ok_te:
flash(err_te or "触价开仓监控添加失败")
return redirect("/key_monitor")
flash(
f"触价开仓已添加({symbol} 日成交量排名 {rank}/{total}"
f"|有效期 {TRIGGER_ENTRY_VALIDITY_HOURS}h"
f"|标记价触达入场价后下一轮询市价开仓"
f"|移动保本:{'' if be_flag else ''}"
+ (f"{time_close_label(tc_h)}" if tc_en else "")
)
return redirect("/key_monitor")
if is_false_breakout_key_monitor_type(mt): if is_false_breakout_key_monitor_type(mt):
fb_sym = normalize_false_breakout_symbol(symbol) fb_sym = normalize_false_breakout_symbol(symbol)
if not fb_sym: if not fb_sym:
+28 -24
View File
@@ -5391,18 +5391,30 @@ def _execute_trigger_entry_cross(conn, row):
conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,)) conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,))
conn.commit() conn.commit()
ok, err, det = _market_open_for_trigger_entry( try:
conn, ok, err, det = _market_open_for_trigger_entry(
symbol, conn,
direction, symbol,
ex_sym, direction,
entry, ex_sym,
sl, entry,
tp, sl,
breakeven_enabled=be_en, tp,
time_close_enabled=tc_en, breakeven_enabled=be_en,
time_close_hours=tc_h, time_close_enabled=tc_en,
) time_close_hours=tc_h,
)
except Exception as e:
fail_msg = friendly_exchange_error(e)
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 计划入场:{format_price_for_symbol(symbol, entry)}\n"
f"- 原因:{fail_msg}\n"
)
insert_key_monitor_history(conn, row, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
return False, fail_msg
if ok and det: if ok and det:
rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-" rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-"
msg = ( msg = (
@@ -5468,17 +5480,7 @@ def check_trigger_entry_key_monitors():
_finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE) _finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE)
continue continue
if trigger_entry_reached(direction, mark, entry): if trigger_entry_reached(direction, mark, entry):
try: _execute_trigger_entry_cross(conn, r)
_execute_trigger_entry_cross(conn, r)
except Exception as e:
fail_msg = friendly_exchange_error(e)
try:
insert_key_monitor_history(conn, r, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
except Exception:
pass
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n**账户:{_wechat_account_label()}**\n- {fail_msg}\n"
)
conn.commit() conn.commit()
conn.close() conn.close()
@@ -6989,6 +6991,7 @@ def api_price_snapshot():
gate_metrics = "" gate_metrics = ""
fib_gate_ok = True fib_gate_ok = True
fb_gate_ok = True fb_gate_ok = True
te_gate_ok = True
if is_fib: if is_fib:
direction = (r["direction"] or "long").lower() direction = (r["direction"] or "long").lower()
inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"])
@@ -7027,7 +7030,7 @@ def api_price_snapshot():
) )
gate_summary = prev.get("summary") or "-" gate_summary = prev.get("summary") or "-"
gate_metrics = prev.get("metrics") or "" gate_metrics = prev.get("metrics") or ""
fib_gate_ok = bool(prev.get("gate_ok")) te_gate_ok = bool(prev.get("gate_ok"))
elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES:
try: try:
prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"])
@@ -7087,6 +7090,7 @@ def api_price_snapshot():
"gate_ok": ( "gate_ok": (
fib_gate_ok if is_fib fib_gate_ok if is_fib
else fb_gate_ok if is_fb else fb_gate_ok if is_fb
else te_gate_ok if is_te
else bool(gate and gate.get("ok")) else bool(gate and gate.get("ok"))
), ),
"gate_metrics": gate_metrics, "gate_metrics": gate_metrics,
+29 -25
View File
@@ -5391,18 +5391,30 @@ def _execute_trigger_entry_cross(conn, row):
conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,)) conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,))
conn.commit() conn.commit()
ok, err, det = _market_open_for_trigger_entry( try:
conn, ok, err, det = _market_open_for_trigger_entry(
symbol, conn,
direction, symbol,
ex_sym, direction,
entry, ex_sym,
sl, entry,
tp, sl,
breakeven_enabled=be_en, tp,
time_close_enabled=tc_en, breakeven_enabled=be_en,
time_close_hours=tc_h, time_close_enabled=tc_en,
) time_close_hours=tc_h,
)
except Exception as e:
fail_msg = friendly_exchange_error(e)
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 计划入场:{format_price_for_symbol(symbol, entry)}\n"
f"- 原因:{fail_msg}\n"
)
insert_key_monitor_history(conn, row, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
return False, fail_msg
if ok and det: if ok and det:
rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-" rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-"
msg = ( msg = (
@@ -5468,22 +5480,12 @@ def check_trigger_entry_key_monitors():
_finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE) _finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE)
continue continue
if trigger_entry_reached(direction, mark, entry): if trigger_entry_reached(direction, mark, entry):
try: _execute_trigger_entry_cross(conn, r)
_execute_trigger_entry_cross(conn, r)
except Exception as e:
fail_msg = friendly_exchange_error(e)
try:
insert_key_monitor_history(conn, r, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
except Exception:
pass
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n**账户:{_wechat_account_label()}**\n- {fail_msg}\n"
)
conn.commit() conn.commit()
conn.close() conn.close()
def check_fib_key_monitors():
conn = get_db() conn = get_db()
rows = conn.execute("SELECT * FROM key_monitors").fetchall() rows = conn.execute("SELECT * FROM key_monitors").fetchall()
for r in rows: for r in rows:
@@ -6989,6 +6991,7 @@ def api_price_snapshot():
gate_metrics = "" gate_metrics = ""
fib_gate_ok = True fib_gate_ok = True
fb_gate_ok = True fb_gate_ok = True
te_gate_ok = True
if is_fib: if is_fib:
direction = (r["direction"] or "long").lower() direction = (r["direction"] or "long").lower()
inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"])
@@ -7027,7 +7030,7 @@ def api_price_snapshot():
) )
gate_summary = prev.get("summary") or "-" gate_summary = prev.get("summary") or "-"
gate_metrics = prev.get("metrics") or "" gate_metrics = prev.get("metrics") or ""
fib_gate_ok = bool(prev.get("gate_ok")) te_gate_ok = bool(prev.get("gate_ok"))
elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES:
try: try:
prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"])
@@ -7087,6 +7090,7 @@ def api_price_snapshot():
"gate_ok": ( "gate_ok": (
fib_gate_ok if is_fib fib_gate_ok if is_fib
else fb_gate_ok if is_fb else fb_gate_ok if is_fb
else te_gate_ok if is_te
else bool(gate and gate.get("ok")) else bool(gate and gate.get("ok"))
), ),
"gate_metrics": gate_metrics, "gate_metrics": gate_metrics,
+62 -25
View File
@@ -1653,6 +1653,7 @@ def _count_opens_for_segment(conn, start_td, end_td, segment_key):
"key_fib618": "斐波回调0.618", "key_fib618": "斐波回调0.618",
"key_fib786": "斐波回调0.786", "key_fib786": "斐波回调0.786",
"key_false_breakout": FALSE_BREAKOUT_MONITOR_TYPE, "key_false_breakout": FALSE_BREAKOUT_MONITOR_TYPE,
"key_trigger": TRIGGER_ENTRY_MONITOR_TYPE,
} }
kst = kst_map.get(segment_key) kst = kst_map.get(segment_key)
if kst: if kst:
@@ -4948,18 +4949,30 @@ def _execute_trigger_entry_cross(conn, row):
conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,)) conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,))
conn.commit() conn.commit()
ok, err, det = _market_open_for_trigger_entry( try:
conn, ok, err, det = _market_open_for_trigger_entry(
symbol, conn,
direction, symbol,
ex_sym, direction,
entry, ex_sym,
sl, entry,
tp, sl,
breakeven_enabled=be_en, tp,
time_close_enabled=tc_en, breakeven_enabled=be_en,
time_close_hours=tc_h, time_close_enabled=tc_en,
) time_close_hours=tc_h,
)
except Exception as e:
fail_msg = friendly_exchange_error(e)
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 计划入场:{format_price_for_symbol(symbol, entry)}\n"
f"- 原因:{fail_msg}\n"
)
insert_key_monitor_history(conn, row, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
return False, fail_msg
if ok and det: if ok and det:
rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-" rr_txt = format_wechat_scalar_2dp(det.get("planned_rr_fill")) if det.get("planned_rr_fill") is not None else "-"
msg = ( msg = (
@@ -5025,22 +5038,12 @@ def check_trigger_entry_key_monitors():
_finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE) _finalize_key_monitor_one_shot(conn, r, msg, TRIGGER_ENTRY_CLOSE_TP_INVALIDATE)
continue continue
if trigger_entry_reached(direction, mark, entry): if trigger_entry_reached(direction, mark, entry):
try: _execute_trigger_entry_cross(conn, r)
_execute_trigger_entry_cross(conn, r)
except Exception as e:
fail_msg = friendly_exchange_error(e)
try:
insert_key_monitor_history(conn, r, 0, fail_msg, TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED)
except Exception:
pass
send_wechat_msg(
f"# ❌ {symbol} 触价开仓异常\n**账户:{_wechat_account_label()}**\n- {fail_msg}\n"
)
conn.commit() conn.commit()
conn.close() conn.close()
def check_fib_key_monitors():
conn = get_db() conn = get_db()
rows = conn.execute("SELECT * FROM key_monitors").fetchall() rows = conn.execute("SELECT * FROM key_monitors").fetchall()
for r in rows: for r in rows:
@@ -6573,6 +6576,7 @@ def api_price_snapshot():
gate_metrics = "" gate_metrics = ""
fib_gate_ok = True fib_gate_ok = True
fb_gate_ok = True fb_gate_ok = True
te_gate_ok = True
if is_fib: if is_fib:
direction = (r["direction"] or "long").lower() direction = (r["direction"] or "long").lower()
inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"]) inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"])
@@ -6611,7 +6615,7 @@ def api_price_snapshot():
) )
gate_summary = prev.get("summary") or "-" gate_summary = prev.get("summary") or "-"
gate_metrics = prev.get("metrics") or "" gate_metrics = prev.get("metrics") or ""
fib_gate_ok = bool(prev.get("gate_ok")) te_gate_ok = bool(prev.get("gate_ok"))
elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES: elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES:
try: try:
prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"]) prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"])
@@ -6671,6 +6675,7 @@ def api_price_snapshot():
"gate_ok": ( "gate_ok": (
fib_gate_ok if is_fib fib_gate_ok if is_fib
else fb_gate_ok if is_fb else fb_gate_ok if is_fb
else te_gate_ok if is_te
else bool(gate and gate.get("ok")) else bool(gate and gate.get("ok"))
), ),
"gate_metrics": gate_metrics, "gate_metrics": gate_metrics,
@@ -7256,6 +7261,38 @@ def add_key():
tc_h = parse_time_close_hours_form(d.get("time_close_hours")) if tc_en else None tc_h = parse_time_close_hours_form(d.get("time_close_hours")) if tc_en else None
if tc_en and not tc_h: if tc_en and not tc_h:
tc_en = 0 tc_en = 0
if is_trigger_entry_key_monitor_type(mt):
if direction_sel not in ("long", "short"):
conn.close()
flash("触价开仓请选择做多或做空")
return redirect("/key_monitor")
try:
entry_px = float(d.get("trigger_entry") or 0)
sl_px = float(d.get("trigger_sl") or 0)
tp_px = float(d.get("trigger_tp") or 0)
except (TypeError, ValueError):
entry_px = sl_px = tp_px = 0
if entry_px <= 0 or sl_px <= 0 or tp_px <= 0:
conn.close()
flash("触价开仓须填写有效的入场价、止损价、止盈价")
return redirect("/key_monitor")
ok_te, err_te = _add_trigger_entry_key_monitor(
conn, symbol, direction_sel, entry_px, sl_px, tp_px, breakeven_enabled=be_flag,
time_close_enabled=tc_en, time_close_hours=tc_h,
)
conn.commit()
conn.close()
if not ok_te:
flash(err_te or "触价开仓监控添加失败")
return redirect("/key_monitor")
flash(
f"触价开仓已添加({symbol} 日成交量排名 {rank}/{total}"
f"|有效期 {TRIGGER_ENTRY_VALIDITY_HOURS}h"
f"|标记价触达入场价后下一轮询市价开仓"
f"|移动保本:{'' if be_flag else ''}"
+ (f"{time_close_label(tc_h)}" if tc_en else "")
)
return redirect("/key_monitor")
if is_false_breakout_key_monitor_type(mt): if is_false_breakout_key_monitor_type(mt):
fb_sym = normalize_false_breakout_symbol(symbol) fb_sym = normalize_false_breakout_symbol(symbol)
if not fb_sym: if not fb_sym: