修复期权币本位高危缺陷:开仓估量、卖回封顶、握库超时与条件单嵌套锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-07 12:00:08 +08:00
parent ea3e029eb9
commit 77fe0fa679
16 changed files with 724 additions and 201 deletions
+64 -39
View File
@@ -300,7 +300,7 @@ def _notify_profit_exit_close(
def run_options_profit_exits(
conn: sqlite3.Connection,
conn_or_get_db: Any,
positions: list[dict[str, Any]],
*,
close_fn: Callable[[str], dict[str, Any]],
@@ -310,56 +310,77 @@ def run_options_profit_exits(
cfg: dict[str, Any] | None = None,
ex: Any = None,
) -> int:
"""扫描开启翻倍出场的 open 仓;买一可回收达标后限价平仓.返回本次新触发条数."""
"""扫描开启翻倍出场的 open 仓;买一可回收达标后限价平仓.返回本次新触发条数.
可传 get_db:拉盘口/平仓时不握业务连接。
"""
get_db = conn_or_get_db if callable(conn_or_get_db) else None
conn = conn_or_get_db() if get_db else conn_or_get_db
ensure_profit_exit_columns(conn)
pos_by_inst = {str(p.get("inst_id") or p.get("instId") or ""): p for p in positions}
rules = profit_exit_by_inst(conn)
triggered = 0
closing_ids: list[str] = []
scan_jobs: list[tuple[str, dict[str, Any], float, float]] = []
for inst_id, info in list(rules.items()):
if not inst_id:
continue
pos = pos_by_inst.get(inst_id)
if not pos:
# 本轮暂无持仓:不改状态(避免接口抖动误标 done);真正平完由 sync 收尾
continue
state = str(info.get("profit_exit_state") or "active")
mult = normalize_profit_exit_mult(info.get("profit_exit_mult"), default=1.0)
prem = sum_open_premium_paid(conn, inst_id)
if prem is None or prem <= 0:
continue
# done/idle 不再扫描(查询层已过滤;此处双保险)
if state in ("done", "idle"):
continue
if state == "closing":
# 平仓/卖币会另开连接写库,先提交释放锁(模拟盘尤其敏感)
_commit(conn)
result = close_fn(inst_id)
if result.get("already_flat") or _result_fully_done(result):
_mark_state(conn, inst_id, "done")
try:
conn.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 0
WHERE inst_id = ? AND status = 'open'
""",
(inst_id,),
)
except Exception:
pass
_commit(conn)
else:
_mark_state(conn, inst_id, "closing")
_commit(conn)
closing_ids.append(inst_id)
continue
if not info.get("profit_exit_enabled"):
continue
scan_jobs.append((inst_id, pos, float(prem), float(mult)))
_commit(conn)
if get_db is not None:
try:
conn.close()
except Exception:
pass
conn = None
def _ensure_conn() -> sqlite3.Connection:
nonlocal conn
if conn is not None:
return conn
assert get_db is not None
conn = get_db()
ensure_profit_exit_columns(conn)
return conn
for inst_id in closing_ids:
result = close_fn(inst_id)
c = _ensure_conn()
if result.get("already_flat") or _result_fully_done(result):
_mark_state(c, inst_id, "done")
try:
c.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 0
WHERE inst_id = ? AND status = 'open'
""",
(inst_id,),
)
except Exception:
pass
_commit(c)
else:
_mark_state(c, inst_id, "closing")
_commit(c)
for inst_id, pos, prem, mult in scan_jobs:
if recycle_fn is not None:
recycle = recycle_fn(pos, prem)
elif cfg is not None and ex is not None:
@@ -371,12 +392,12 @@ def run_options_profit_exits(
if not profit_exit_hit(premium_paid=prem, recycle_usdc=recycle, mult=mult):
continue
_commit(conn)
result = close_fn(inst_id)
c = _ensure_conn()
if result.get("already_flat"):
_mark_state(conn, inst_id, "done")
_mark_state(c, inst_id, "done")
try:
conn.execute(
c.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 0
@@ -386,18 +407,18 @@ def run_options_profit_exits(
)
except Exception:
pass
_commit(conn)
_commit(c)
continue
if not result.get("ok"):
_mark_state(conn, inst_id, "active")
_commit(conn)
_mark_state(c, inst_id, "active")
_commit(c)
continue
done = _result_fully_done(result)
_mark_state(conn, inst_id, "done" if done else "closing")
_mark_state(c, inst_id, "done" if done else "closing")
if done:
try:
conn.execute(
c.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 0
@@ -407,8 +428,7 @@ def run_options_profit_exits(
)
except Exception:
pass
# 先落库再推送:同一笔只推一次;closing 重试不再进本分支
_commit(conn)
_commit(c)
triggered += 1
_notify_profit_exit_close(
cfg,
@@ -419,6 +439,11 @@ def run_options_profit_exits(
premium_paid=prem,
recycle=recycle,
result=result,
conn=conn,
conn=c,
)
if get_db is not None and conn is not None:
try:
conn.close()
except Exception:
pass
return triggered