fix: 清理幽灵止盈止损监控并修正仓位上限冻结误触发
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+117
-24
@@ -125,6 +125,49 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _ctp_position_keys(mode: str) -> set[tuple[str, str]]:
|
||||
keys: set[tuple[str, str]] = set()
|
||||
for p in _ctp_positions(mode):
|
||||
lots = int(p.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
sym = (p.get("symbol") or "").lower()
|
||||
direction = p.get("direction") or "long"
|
||||
keys.add((sym, direction))
|
||||
return keys
|
||||
|
||||
def _monitor_matches_ctp_position(mon: dict, position_keys: set[tuple[str, str]]) -> bool:
|
||||
ms = mon.get("symbol") or ""
|
||||
md = mon.get("direction") or "long"
|
||||
for ps, pd in position_keys:
|
||||
if pd != md:
|
||||
continue
|
||||
if _match_ctp_symbol(ps, ms):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _sync_trade_monitors_with_ctp(conn, mode: str) -> int:
|
||||
"""关闭无对应 CTP 持仓的 active 监控(委托被拒或未成交的幽灵记录)。"""
|
||||
if not ctp_status(mode).get("connected"):
|
||||
return 0
|
||||
position_keys = _ctp_position_keys(mode)
|
||||
closed = 0
|
||||
for r in conn.execute("SELECT * FROM trade_order_monitors WHERE status='active'").fetchall():
|
||||
mon = dict(r)
|
||||
if _monitor_matches_ctp_position(mon, position_keys):
|
||||
continue
|
||||
conn.execute("UPDATE trade_order_monitors SET status='closed' WHERE id=?", (mon["id"],))
|
||||
closed += 1
|
||||
return closed
|
||||
|
||||
def _effective_active_position_count(conn, mode: str) -> int:
|
||||
if ctp_status(mode).get("connected"):
|
||||
return len(_ctp_position_keys(mode))
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) AS n FROM trade_order_monitors WHERE status='active'"
|
||||
).fetchone()
|
||||
return int(row["n"] or 0)
|
||||
|
||||
def _build_pending_orders(conn, mode: str) -> list[dict]:
|
||||
pending: list[dict] = []
|
||||
for r in conn.execute(
|
||||
@@ -141,6 +184,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
"direction_label": "做多" if direction == "long" else "做空",
|
||||
"lots": lots,
|
||||
"source": "monitor",
|
||||
"monitor_id": mon.get("id"),
|
||||
}
|
||||
sl = mon.get("stop_loss")
|
||||
tp = mon.get("take_profit")
|
||||
@@ -235,6 +279,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
"price": sl,
|
||||
"lots": lots,
|
||||
"source": "monitor",
|
||||
"monitor_id": mon["id"] if mon else None,
|
||||
})
|
||||
if tp is not None:
|
||||
pending_for_row.append({
|
||||
@@ -243,6 +288,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
"price": tp,
|
||||
"lots": lots,
|
||||
"source": "monitor",
|
||||
"monitor_id": mon["id"] if mon else None,
|
||||
})
|
||||
rows.append({
|
||||
"key": f"ctp:{sym.lower()}:{direction}",
|
||||
@@ -280,8 +326,9 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
init_strategy_tables(conn)
|
||||
mode = get_trading_mode(get_setting)
|
||||
ctp_st = ctp_status(mode)
|
||||
_sync_trade_monitors_with_ctp(conn, mode)
|
||||
capital = _capital(conn)
|
||||
risk = get_risk_status(conn)
|
||||
risk = get_risk_status(conn, active_count=_effective_active_position_count(conn, mode))
|
||||
ctp_acc = _ctp_account(mode) if ctp_st.get("connected") else {}
|
||||
active_trend = conn.execute(
|
||||
"SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC LIMIT 1"
|
||||
@@ -328,10 +375,11 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
init_strategy_tables(conn)
|
||||
mode = get_trading_mode(get_setting)
|
||||
ctp_st = ctp_status(mode)
|
||||
_sync_trade_monitors_with_ctp(conn, mode)
|
||||
rows = _build_trading_live_rows(conn)
|
||||
pending_orders = _build_pending_orders(conn, mode)
|
||||
capital = _capital(conn)
|
||||
risk = get_risk_status(conn)
|
||||
risk = get_risk_status(conn, active_count=_effective_active_position_count(conn, mode))
|
||||
conn.commit()
|
||||
return jsonify({
|
||||
"rows": rows,
|
||||
@@ -344,6 +392,34 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route("/api/trading/monitor/dismiss", methods=["POST"])
|
||||
@login_required
|
||||
def api_trading_monitor_dismiss():
|
||||
d = request.get_json(silent=True) or {}
|
||||
try:
|
||||
monitor_id = int(d.get("monitor_id") or 0)
|
||||
except (TypeError, ValueError):
|
||||
monitor_id = 0
|
||||
if monitor_id <= 0:
|
||||
return jsonify({"ok": False, "error": "无效的监控记录"}), 400
|
||||
conn = get_db()
|
||||
try:
|
||||
init_strategy_tables(conn)
|
||||
row = conn.execute(
|
||||
"SELECT id FROM trade_order_monitors WHERE id=? AND status='active'",
|
||||
(monitor_id,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return jsonify({"ok": False, "error": "记录不存在或已关闭"}), 404
|
||||
conn.execute(
|
||||
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
|
||||
(monitor_id,),
|
||||
)
|
||||
conn.commit()
|
||||
return jsonify({"ok": True, "message": "已取消本地止盈止损监控"})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route("/api/trading/close", methods=["POST"])
|
||||
@login_required
|
||||
def api_trading_close():
|
||||
@@ -523,12 +599,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
return jsonify({"ok": False, "error": "品种或价格无效"}), 400
|
||||
conn = get_db()
|
||||
init_strategy_tables(conn)
|
||||
mode = get_trading_mode(get_setting)
|
||||
if offset.startswith("open"):
|
||||
err = assert_can_open(conn)
|
||||
_sync_trade_monitors_with_ctp(conn, mode)
|
||||
err = assert_can_open(conn, active_count=_effective_active_position_count(conn, mode))
|
||||
if err:
|
||||
conn.close()
|
||||
return jsonify({"ok": False, "error": err}), 403
|
||||
mode = get_trading_mode(get_setting)
|
||||
ctp_st = ctp_status(mode)
|
||||
if not ctp_st.get("connected"):
|
||||
conn.close()
|
||||
@@ -569,25 +646,40 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
if offset.startswith("open"):
|
||||
sl = d.get("stop_loss")
|
||||
tp = d.get("take_profit")
|
||||
codes = ths_to_codes(sym)
|
||||
conn.execute(
|
||||
"""INSERT INTO trade_order_monitors (
|
||||
symbol, symbol_name, market_code, direction, lots, entry_price,
|
||||
stop_loss, take_profit, open_time, monitor_type, status
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?, 'active')""",
|
||||
(
|
||||
sym,
|
||||
codes.get("name", sym) if codes else sym,
|
||||
codes.get("market_code", "") if codes else "",
|
||||
direction,
|
||||
lots,
|
||||
price,
|
||||
float(sl) if sl else None,
|
||||
float(tp) if tp else None,
|
||||
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"manual",
|
||||
),
|
||||
)
|
||||
if sl or tp:
|
||||
import time
|
||||
time.sleep(2.0)
|
||||
actual_lots = lots
|
||||
has_pos = False
|
||||
for p in _ctp_positions(mode):
|
||||
if int(p.get("lots") or 0) <= 0:
|
||||
continue
|
||||
if (p.get("direction") or "long") != direction:
|
||||
continue
|
||||
if _match_ctp_symbol(p.get("symbol") or "", sym):
|
||||
has_pos = True
|
||||
actual_lots = int(p.get("lots") or lots)
|
||||
break
|
||||
if has_pos:
|
||||
codes = ths_to_codes(sym)
|
||||
conn.execute(
|
||||
"""INSERT INTO trade_order_monitors (
|
||||
symbol, symbol_name, market_code, direction, lots, entry_price,
|
||||
stop_loss, take_profit, open_time, monitor_type, status
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?, 'active')""",
|
||||
(
|
||||
sym,
|
||||
codes.get("name", sym) if codes else sym,
|
||||
codes.get("market_code", "") if codes else "",
|
||||
direction,
|
||||
actual_lots,
|
||||
price,
|
||||
float(sl) if sl else None,
|
||||
float(tp) if tp else None,
|
||||
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"manual",
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
send_wechat_msg(f"{trading_mode_label(get_setting)} {offset} {sym} {direction} {lots}手 @{price}")
|
||||
conn.close()
|
||||
@@ -643,8 +735,9 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
init_strategy_tables(conn)
|
||||
mode = get_trading_mode(get_setting)
|
||||
ctp_st = ctp_status(mode)
|
||||
_sync_trade_monitors_with_ctp(conn, mode)
|
||||
capital = _capital(conn)
|
||||
risk = get_risk_status(conn)
|
||||
risk = get_risk_status(conn, active_count=_effective_active_position_count(conn, mode))
|
||||
conn.commit()
|
||||
ctp_acc = _ctp_account(mode) if ctp_st.get("connected") else {}
|
||||
positions = _ctp_positions(mode) if ctp_st.get("connected") else []
|
||||
|
||||
Reference in New Issue
Block a user