修复期权币本位高危缺陷:开仓估量、卖回封顶、握库超时与条件单嵌套锁。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user