增加斐波那契下单

This commit is contained in:
dekun
2026-05-18 16:50:02 +08:00
parent a250c28ceb
commit b9f1b9e62a
5 changed files with 1018 additions and 24 deletions
+474 -12
View File
@@ -29,6 +29,19 @@ except ImportError:
ImageFont = None # type: ignore
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
_REPO_ROOT = os.path.dirname(BASE_DIR)
import sys
if _REPO_ROOT not in sys.path:
sys.path.insert(0, _REPO_ROOT)
from fib_key_monitor_lib import (
FIB_KEY_MONITOR_TYPES,
calc_fib_plan,
fib_invalidate_by_mark,
fib_ratio_from_type,
is_fib_key_monitor_type,
stored_key_signal_type,
)
def load_env_file(path):
@@ -1240,6 +1253,19 @@ def init_db():
try:
c.execute("ALTER TABLE key_monitors ADD COLUMN breakout_limit_pct REAL DEFAULT 1.5")
except: pass
for ddl in (
"ALTER TABLE key_monitors ADD COLUMN fib_limit_order_id TEXT",
"ALTER TABLE key_monitors ADD COLUMN fib_entry_price REAL",
"ALTER TABLE key_monitors ADD COLUMN fib_stop_loss REAL",
"ALTER TABLE key_monitors ADD COLUMN fib_take_profit REAL",
"ALTER TABLE key_monitors ADD COLUMN fib_order_amount REAL",
"ALTER TABLE key_monitors ADD COLUMN fib_margin_capital REAL",
"ALTER TABLE key_monitors ADD COLUMN fib_leverage INTEGER",
):
try:
c.execute(ddl)
except Exception:
pass
try:
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
except Exception:
@@ -2080,7 +2106,9 @@ def order_row_key_signal_type(row):
if "key_signal_type" not in keys:
return None
kst = (row["key_signal_type"] or "").strip()
return kst if kst in KEY_MONITOR_AUTO_TYPES else None
if kst in KEY_MONITOR_AUTO_TYPES or is_fib_key_monitor_type(kst):
return kst
return None
def exchange_private_api_configured():
@@ -4000,7 +4028,7 @@ def _market_open_for_key_monitor(conn, symbol, direction, exchange_symbol, stop_
opened_at_ms,
trading_day,
ORDER_MONITOR_TYPE_KEY_AUTO,
(key_signal_type if key_signal_type in KEY_MONITOR_AUTO_TYPES else None),
stored_key_signal_type(key_signal_type),
),
)
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
@@ -4033,6 +4061,402 @@ def _market_open_for_key_monitor(conn, symbol, direction, exchange_symbol, stop_
}
def _sqlite_row_val(row, key, default=None):
try:
v = row[key]
return default if v is None else v
except (KeyError, IndexError, TypeError):
return default
def get_symbol_mark_price(symbol):
"""斐波失效判定用标记价。"""
ex_sym = normalize_exchange_symbol(symbol)
try:
ensure_markets_loaded()
ticker = exchange.fetch_ticker(ex_sym)
m = _coerce_float(ticker.get("mark"), ticker.get("last"))
if m is None:
info = ticker.get("info") or {}
m = _coerce_float(info.get("mark_price"), info.get("last"))
if m is not None and m > 0:
return float(m)
except Exception:
pass
p = get_price(symbol)
return float(p) if p is not None else None
def cancel_fib_limit_order(exchange_symbol, order_id):
"""仅撤销本条斐波限价单,不用 cancel_all。"""
if not order_id:
return False
ok_live, _ = ensure_exchange_live_ready()
if not ok_live:
return False
ensure_markets_loaded()
oid = str(order_id)
try:
exchange.cancel_order(oid, exchange_symbol)
return True
except Exception:
pass
try:
for o in exchange.fetch_open_orders(exchange_symbol) or []:
if str(o.get("id")) == oid:
exchange.cancel_order(oid, exchange_symbol)
return True
except Exception:
pass
return False
def fib_limit_order_status(exchange_symbol, order_id):
if not order_id:
return "missing"
ensure_markets_loaded()
oid = str(order_id)
try:
o = exchange.fetch_order(oid, exchange_symbol)
st = (o.get("status") or "").lower()
if st in ("closed", "filled"):
filled = float(o.get("filled") or 0)
if filled > 0 or st == "filled":
return "filled"
if st in ("canceled", "cancelled", "expired", "rejected"):
return "canceled"
if st in ("open", "new", "partially_filled"):
return "open"
except Exception:
pass
try:
for o in exchange.fetch_open_orders(exchange_symbol) or []:
if str(o.get("id")) == oid:
return "open"
except Exception:
pass
return "unknown"
def place_fib_limit_order(exchange_symbol, direction, amount, leverage, limit_price):
ensure_markets_loaded()
exchange.set_leverage(leverage, exchange_symbol)
side = "buy" if direction == "long" else "sell"
price = round_price_to_exchange(exchange_symbol, float(limit_price))
if price is None or price <= 0:
raise ValueError("挂单价无效")
params = build_gate_order_params(direction, reduce_only=False)
return exchange.create_order(exchange_symbol, "limit", side, amount, price, params)
def _fib_key_exists_for_symbol(conn, symbol):
ph = ",".join("?" * len(FIB_KEY_MONITOR_TYPES))
row = conn.execute(
f"SELECT id FROM key_monitors WHERE symbol=? AND monitor_type IN ({ph})",
(symbol, *tuple(FIB_KEY_MONITOR_TYPES)),
).fetchone()
return row is not None
def _fib_plan_for_row(row):
typ = (row["monitor_type"] or "").strip()
ratio = fib_ratio_from_type(typ)
if ratio is None:
return None
return calc_fib_plan(row["direction"], row["upper"], row["lower"], ratio)
def _cancel_fib_monitor_limit(row):
ex_sym = normalize_exchange_symbol(row["symbol"])
oid = _sqlite_row_val(row, "fib_limit_order_id")
if oid:
cancel_fib_limit_order(ex_sym, oid)
def _fib_has_live_position(exchange_symbol, direction):
live = get_live_position_contracts(exchange_symbol, direction)
return live is not None and float(live) > 0
def _insert_order_monitor_from_fib_fill(
conn, row, trigger_price, stop_loss, take_profit, amount, leverage, margin_capital,
notional_value, position_ratio, base_amount, exchange_order_id, tpsl_attached,
):
symbol = row["symbol"]
direction = (row["direction"] or "long").lower()
exchange_symbol = normalize_exchange_symbol(symbol)
typ = (row["monitor_type"] or "").strip()
now = app_now()
trading_day = get_trading_day(now)
trade_style = (DEFAULT_TRADE_STYLE or "trend").strip().lower()
if trade_style not in ("trend", "swing"):
trade_style = "trend"
risk_percent = max(0.01, float(RISK_PERCENT))
risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage)
if risk_amount_final is None:
risk_amount_final = round(float(margin_capital) * risk_percent / 100.0, 4)
breakeven_rr_trigger = float(BREAKEVEN_RR_TRIGGER)
breakeven_offset_pct = float(BREAKEVEN_OFFSET_PCT)
breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0
if direction == "short":
breakeven_raw = float(trigger_price) * (1 - breakeven_offset_pct / 100.0)
else:
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
opened_at_bj = app_now_str()
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
conn.execute(
"INSERT INTO order_monitors "
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type) "
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
symbol,
exchange_symbol,
direction,
trigger_price,
stop_loss,
stop_loss,
take_profit,
margin_capital,
leverage,
trade_style,
risk_percent,
risk_amount_final,
breakeven_rr_trigger,
breakeven_offset_pct,
breakeven_step_r,
0,
breakeven_price,
1,
notional_value,
position_ratio,
base_amount,
amount,
exchange_order_id or "",
opened_at_bj,
opened_at_ms,
trading_day,
ORDER_MONITOR_TYPE_KEY_AUTO,
stored_key_signal_type(typ),
),
)
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
try_persist_exchange_margin_for_order(conn, new_order_id, exchange_symbol, direction, order_leverage=leverage)
return new_order_id
def _finalize_fib_key_fill(conn, row):
symbol = row["symbol"]
direction = (row["direction"] or "long").lower()
typ = (row["monitor_type"] or "").strip()
ex_sym = normalize_exchange_symbol(symbol)
plan = _fib_plan_for_row(row)
if not plan:
_finalize_key_monitor_one_shot(conn, row, "斐波计划无效", "fib_plan_invalid")
return
entry_plan, sl_plan, tp_plan = plan
sl = float(_sqlite_row_val(row, "fib_stop_loss", sl_plan) or sl_plan)
tp = float(_sqlite_row_val(row, "fib_take_profit", tp_plan) or tp_plan)
sl_adj = round_price_to_exchange(ex_sym, sl)
tp_adj = round_price_to_exchange(ex_sym, tp)
if sl_adj is not None:
sl = float(sl_adj)
if tp_adj is not None:
tp = float(tp_adj)
amount = float(_sqlite_row_val(row, "fib_order_amount") or 0)
leverage = int(_sqlite_row_val(row, "fib_leverage") or infer_leverage(symbol) or 5)
margin_capital = float(_sqlite_row_val(row, "fib_margin_capital") or 0)
oid = _sqlite_row_val(row, "fib_limit_order_id")
entry_px = float(_sqlite_row_val(row, "fib_entry_price", entry_plan) or entry_plan)
trigger_price = entry_px
if oid:
try:
o = exchange.fetch_order(str(oid), ex_sym)
trigger_price = resolve_order_entry_price(o, ex_sym, entry_px)
except Exception:
pass
tr_adj = round_price_to_exchange(ex_sym, trigger_price)
if tr_adj is not None:
trigger_price = float(tr_adj)
if amount <= 0:
live_amt = get_live_position_contracts(ex_sym, direction)
amount = float(live_amt or 0)
if amount <= 0:
send_wechat_msg(
f"# ❌ {symbol} 斐波成交后处理失败\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 无法取得持仓/下单数量,未挂 TP/SL\n"
)
return
ok, reason = precheck_risk(conn, symbol, direction)
if not ok:
send_wechat_msg(
f"# ❌ {symbol} 斐波成交后风控拒绝\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 类型:{typ}\n"
f"- 原因:{reason}\n"
f"- 请手动处理仓位与挂单\n"
)
return
tpsl_attached = False
try:
_gate_place_tp_sl_orders(ex_sym, direction, amount, sl, tp)
tpsl_attached = True
except Exception as e:
send_wechat_msg(
f"# ❌ {symbol} 斐波成交后挂 TP/SL 失败\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 错误:{friendly_exchange_error(e)}\n"
f"- 请手动补挂止盈止损\n"
)
return
contract_size = get_contract_size(ex_sym)
base_amount = round(float(amount) * contract_size, 8)
notional_value = round(float(margin_capital) * leverage, 4) if margin_capital else 0
session_row = ensure_session(conn, get_trading_day(app_now()))
capital_base = float(session_row["current_capital"] or 0)
position_ratio = round(margin_capital / capital_base * 100, 2) if capital_base and margin_capital else 0
planned_rr = calc_rr_ratio(direction, trigger_price, sl, tp)
new_order_id = _insert_order_monitor_from_fib_fill(
conn, row, trigger_price, sl, tp, amount, leverage, margin_capital,
notional_value, position_ratio, base_amount, oid, tpsl_attached,
)
rr_txt = format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
succ = (
f"# ✅ {symbol} 斐波限价成交\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 来源:{ORDER_MONITOR_TYPE_KEY_AUTO}(限价 @ E\n"
f"- 类型:{typ}{_wechat_direction_text(direction)}\n"
f"- 订单 ID**{new_order_id}**\n"
f"- 成交价:{format_price_for_symbol(symbol, trigger_price)}\n"
f"- 止损:{format_wechat_scalar_2dp(sl)}|止盈:{format_price_for_symbol(symbol, tp)}\n"
f"- 计划 RR{rr_txt}:1\n"
f"- {'已挂交易所 TP/SL' if tpsl_attached else 'TP/SL 未挂上'}\n"
)
send_wechat_msg(succ)
_finalize_key_monitor_one_shot(conn, row, succ, "fib_filled")
def check_fib_key_monitors():
conn = get_db()
rows = conn.execute("SELECT * FROM key_monitors").fetchall()
for r in rows:
typ = (r["monitor_type"] or "").strip()
if not is_fib_key_monitor_type(typ):
continue
symbol = r["symbol"]
direction = (r["direction"] or "long").lower()
ex_sym = normalize_exchange_symbol(symbol)
up, low = float(r["upper"]), float(r["lower"])
oid = _sqlite_row_val(r, "fib_limit_order_id")
mark = get_symbol_mark_price(symbol)
if mark is None:
continue
status = fib_limit_order_status(ex_sym, oid) if oid else "missing"
if status == "filled" or (status != "open" and _fib_has_live_position(ex_sym, direction)):
_finalize_fib_key_fill(conn, r)
continue
if status == "open":
if fib_invalidate_by_mark(direction, mark, up, low):
_cancel_fib_monitor_limit(r)
msg = (
f"# ⚠️ {symbol} 斐波监控失效\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 类型:{typ}{_wechat_direction_text(direction)}\n"
f"- 标记价 {format_price_for_symbol(symbol, mark)} 已触达止盈侧(未成交),已撤限价单\n"
)
send_wechat_msg(msg)
_finalize_key_monitor_one_shot(conn, r, msg, "fib_invalidate")
continue
if status in ("canceled", "missing", "unknown") and fib_invalidate_by_mark(direction, mark, up, low):
msg = (
f"# ⚠️ {symbol} 斐波监控失效(限价已不在挂单)\n"
f"**账户:{_wechat_account_label()}**\n"
f"- 标记价触达止盈侧,本条已结案\n"
)
send_wechat_msg(msg)
_finalize_key_monitor_one_shot(conn, r, msg, "fib_invalidate")
conn.commit()
conn.close()
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px):
if _fib_key_exists_for_symbol(conn, symbol):
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786"
ratio = fib_ratio_from_type(mt)
plan = calc_fib_plan(direction_sel, upper_px, lower_px, ratio)
if not plan:
return False, "斐波上下沿无效(需上沿 H > 下沿 L)"
entry, sl, tp = plan
ex_sym = normalize_exchange_symbol(symbol)
entry = round_price_to_exchange(ex_sym, entry)
sl = round_price_to_exchange(ex_sym, sl)
tp = round_price_to_exchange(ex_sym, tp)
if entry is None or sl is None or tp is None:
return False, "斐波价位经交易所精度舍入后无效"
entry, sl, tp = float(entry), float(sl), float(tp)
planned_rr = calc_rr_ratio(direction_sel, entry, sl, tp)
if planned_rr is None or planned_rr <= KEY_AUTO_MIN_PLANNED_RR:
fmt_rr = f"{planned_rr:.4f}" if planned_rr is not None else "无法计算"
return False, f"斐波计划盈亏比 {fmt_rr}:1 未达要求(>{KEY_AUTO_MIN_PLANNED_RR}:1"
ok, reason = precheck_risk(conn, symbol, direction_sel)
if not ok:
return False, reason
ok_live, reason_live = ensure_exchange_live_ready()
if not ok_live:
return False, reason_live
now = app_now()
trading_day = get_trading_day(now)
session_row = ensure_session(conn, trading_day)
_, trading_capital_live = get_exchange_capitals(force=True)
live_capital = float(trading_capital_live) if trading_capital_live is not None else float(session_row["current_capital"])
capital_base = resolve_capital_base_for_key_open(conn, trading_day, live_capital)
default_leverage = get_synced_leverage(ex_sym, direction_sel) or infer_leverage(symbol)
leverage = int(default_leverage) if default_leverage else 5
if leverage <= 0:
leverage = 5
available_usdt = get_available_trading_usdt()
risk_fraction = calc_risk_fraction(direction_sel, entry, sl)
if risk_fraction is None:
return False, "止损方向不合法(相对挂单价 E);请核对上下沿与方向"
risk_percent = max(0.01, float(RISK_PERCENT))
risk_amount = round(capital_base * risk_percent / 100.0, 4)
notional_value = round(risk_amount / risk_fraction, 4)
margin_capital = round(notional_value / leverage, 4)
if capital_base and margin_capital > capital_base:
return False, "以损定仓后保证金超过当前交易资金"
if available_usdt is not None:
max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), 4)
if margin_capital > max_margin:
return (
False,
f"保证金不足:交易账户可用约 {round(available_usdt, 2)}U,当前最多建议 {round(max_margin, 2)}U",
)
try:
amount, _ = prepare_order_amount(ex_sym, margin_capital, leverage, entry)
order_resp = place_fib_limit_order(ex_sym, direction_sel, amount, leverage, entry)
oid = str(order_resp.get("id") or "")
if not oid:
return False, "交易所未返回限价单 ID"
except Exception as e:
return False, friendly_exchange_error(e, available_usdt=available_usdt)
conn.execute(
"INSERT INTO key_monitors "
"(symbol, monitor_type, direction, upper, lower, "
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
"fib_order_amount, fib_margin_capital, fib_leverage) "
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
(
symbol, mt, direction_sel, upper_px, lower_px,
oid, entry, sl, tp, float(amount), margin_capital, leverage,
),
)
return True, None
# 关键位监控(箱体/收敛可自动开仓;阻力/支撑位仅单次提醒结案)
def check_key_monitors():
conn = get_db()
@@ -4040,6 +4464,8 @@ def check_key_monitors():
for r in rows:
sym, typ_raw, up, low = r["symbol"], r["monitor_type"], r["upper"], r["lower"]
typ = (typ_raw or "").strip()
if is_fib_key_monitor_type(typ):
continue
direction = (r["direction"] or "long").lower()
try:
checks = _key_hard_checks(sym, direction, up, low, typ)
@@ -4563,6 +4989,7 @@ def background_task():
conn.commit()
conn.close()
force_close_before_reset()
check_fib_key_monitors()
check_key_monitors()
check_order_monitors()
except:
@@ -4925,7 +5352,8 @@ def render_main_page(page="trade"):
key_gate_rule_text = (
f"周期 {KLINE_TIMEFRAME}|确认K:突破棒偏移 {KEY_CONFIRM_BREAKOUT_BAR}、确认棒偏移 {KEY_CONFIRM_BAR}"
f"量能:突破量 > 前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}"
f"自动开仓盈亏比 > {KEY_AUTO_MIN_PLANNED_RR}:1|日成交量排名前 {KEY_DAILY_VOLUME_RANK_MAX}"
f"自动开仓盈亏比 > {KEY_AUTO_MIN_PLANNED_RR}:1|日成交量排名前 {KEY_DAILY_VOLUME_RANK_MAX}"
f"斐波:添加后立即挂限价 @ E,失效按标记价触达 H/L(未成交撤单)"
)
conn.close()
return render_template(
@@ -5056,7 +5484,9 @@ def api_account_snapshot():
@login_required
def api_price_snapshot():
conn = get_db()
key_rows = conn.execute("SELECT id,symbol,monitor_type,direction,upper,lower FROM key_monitors").fetchall()
key_rows = conn.execute(
"SELECT id,symbol,monitor_type,direction,upper,lower,fib_entry_price,fib_limit_order_id FROM key_monitors"
).fetchall()
order_rows = conn.execute(
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
).fetchall()
@@ -5093,18 +5523,33 @@ def api_price_snapshot():
key_prices = []
for r in key_rows:
price = prices.get(r["symbol"])
is_fib = is_fib_key_monitor_type(r["monitor_type"])
if is_fib:
price = get_symbol_mark_price(r["symbol"])
else:
price = prices.get(r["symbol"])
if price is None:
continue
upper_diff, upper_pct = calc_price_diff_pct(price, r["upper"])
lower_diff, lower_pct = calc_price_diff_pct(price, r["lower"])
gate = None
try:
gate = _key_hard_checks(r["symbol"], (r["direction"] or "long").lower(), r["upper"], r["lower"], r["monitor_type"])
except Exception:
gate = None
gate_summary = "-"
gate_metrics = ""
fib_gate_ok = True
if is_fib:
direction = (r["direction"] or "long").lower()
inval = fib_invalidate_by_mark(direction, price, r["upper"], r["lower"])
fib_gate_ok = not inval
entry = _sqlite_row_val(r, "fib_entry_price")
entry_txt = format_price_for_symbol(r["symbol"], entry) if entry else "-"
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')}"
else:
try:
gate = _key_hard_checks(r["symbol"], (r["direction"] or "long").lower(), r["upper"], r["lower"], r["monitor_type"])
except Exception:
gate = None
if gate:
rank_seg = "ERR" if int(gate.get("rank_total") or 0) <= 0 else f"{gate.get('rank')}/{gate.get('rank_total')}"
gate_summary = (
@@ -5143,7 +5588,7 @@ def api_price_snapshot():
"lower_diff": lower_diff,
"lower_pct": lower_pct,
"gate_summary": gate_summary,
"gate_ok": bool(gate and gate.get("ok")),
"gate_ok": fib_gate_ok if is_fib else bool(gate and gate.get("ok")),
"gate_metrics": gate_metrics,
})
@@ -5598,9 +6043,13 @@ def add_key():
flash("请选择做多或做空")
return redirect("/key_monitor")
mt = (d.get("type") or "").strip()
allowed_types = tuple(KEY_MONITOR_AUTO_TYPES) + tuple(KEY_MONITOR_ALERT_ONLY_TYPES)
allowed_types = (
tuple(KEY_MONITOR_AUTO_TYPES)
+ tuple(KEY_MONITOR_ALERT_ONLY_TYPES)
+ tuple(FIB_KEY_MONITOR_TYPES)
)
if mt not in allowed_types:
flash("监控类型无效,请选择:箱体突破、收敛突破、关键阻力位、关键支撑位")
flash("监控类型无效")
return redirect("/key_monitor")
rank, total = _daily_volume_rank(symbol)
if rank is None:
@@ -5626,6 +6075,15 @@ def add_key():
pass
upper_px = round_price_to_exchange(ex_sym_key, float(d["upper"]))
lower_px = round_price_to_exchange(ex_sym_key, float(d["lower"]))
if is_fib_key_monitor_type(mt):
ok_fib, err_fib = _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px)
conn.commit()
conn.close()
if not ok_fib:
flash(err_fib or "斐波监控添加失败")
return redirect("/key_monitor")
flash(f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total}")
return redirect("/key_monitor")
conn.execute(
"INSERT INTO key_monitors (symbol,monitor_type,direction,upper,lower) VALUES (?,?,?,?,?)",
(symbol, mt, direction_sel, upper_px, lower_px),
@@ -5996,6 +6454,8 @@ def delete_key_monitor(kid):
if not row:
conn.close()
return jsonify({"ok": False, "error": "not_found"})
if is_fib_key_monitor_type(row["monitor_type"]):
_cancel_fib_monitor_limit(row)
insert_key_monitor_history(conn, row, int(row["notification_count"] or 0), None, "manual")
cur = conn.execute("DELETE FROM key_monitors WHERE id=?", (kid,))
conn.commit()
@@ -6019,6 +6479,8 @@ def del_key(id):
conn = get_db()
row = conn.execute("SELECT * FROM key_monitors WHERE id=?", (id,)).fetchone()
if row:
if is_fib_key_monitor_type(row["monitor_type"]):
_cancel_fib_monitor_limit(row)
insert_key_monitor_history(conn, row, int(row["notification_count"] or 0), None, "manual")
conn.execute("DELETE FROM key_monitors WHERE id=?", (id,))
conn.commit()