Fix close-path SQLite lock by releasing write lock before exchange/sim ops.

Also enable WAL and busy_timeout on get_db so concurrent sim writes wait instead of failing immediately.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 16:54:56 +08:00
parent 043b0aeded
commit d54bbdfcb3
2 changed files with 40 additions and 17 deletions
+33 -16
View File
@@ -11,6 +11,14 @@ def _now() -> str:
return datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S")
def _release_db_write(conn: Any) -> None:
"""提交并释放写锁,避免平仓时 sim/其它路径再开同库被 database is locked."""
try:
conn.commit()
except Exception:
pass
def _env_bool(key: str, default: bool = False) -> bool:
raw = (os.getenv(key) or "").strip().lower()
if not raw:
@@ -1538,7 +1546,7 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
perp_pnl = 0.0
close_detail: dict[str, Any] = {}
# 1) 先平期权
# 1) 先平期权(下单前先提交,避免握锁撞 sim 库)
if opt:
inst_id = str(opt.get("inst_id") or "")
sheets = float(opt.get("size") or 1)
@@ -1561,6 +1569,7 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
"plan_id": int(plan_id),
"need_liquidity": True,
}
_release_db_write(conn)
close_r = _sell_option(cfg, inst_id=inst_id, sheets=sheets, dry_run=False)
close_detail["option"] = close_r
if not close_r.get("already_flat") and (not close_r.get("ok") or not close_r.get("fully_closed", True)):
@@ -1590,6 +1599,7 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
realized_pnl=round(opt_pnl, 4),
)
notes.append(f"期权已平 {inst_id}")
_release_db_write(conn)
# 2) 再平永续
if perp:
@@ -1616,6 +1626,7 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
)
notes.append("永续无仓/已平")
else:
_release_db_write(conn)
perp_close = _close_perp(
cfg, symbol=symbol, direction=perp_dir, contracts=contracts, dry_run=False
)
@@ -1636,23 +1647,27 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
"close": close_detail,
"notes": notes,
}
# 粗估永续盈亏
# 粗估永续盈亏(优先用成交回报)
try:
entry = float(perp.get("avg_open") or plan.get("entry_mark") or 0)
get_m = cfg.get("get_live_position_exchange_metrics")
mark = entry
if callable(get_m):
m = get_m(symbol, perp_dir) or {}
mark = float(m.get("mark_price") or m.get("mark") or entry)
cs = 0.01
get_cs = cfg.get("get_contract_size")
if callable(get_cs):
cs = float(get_cs(symbol) or 0.01)
coins = contracts * cs
if perp_dir == "short":
perp_pnl = (entry - mark) * coins
info = (perp_close.get("order") or {}).get("info") or {}
if info.get("pnl") is not None:
perp_pnl = float(info.get("pnl") or 0)
else:
perp_pnl = (mark - entry) * coins
entry = float(perp.get("avg_open") or plan.get("entry_mark") or 0)
get_m = cfg.get("get_live_position_exchange_metrics")
mark = entry
if callable(get_m):
m = get_m(symbol, perp_dir) or {}
mark = float(m.get("mark_price") or m.get("mark") or entry)
cs = 0.01
get_cs = cfg.get("get_contract_size")
if callable(get_cs):
cs = float(get_cs(symbol) or 0.01)
coins = contracts * cs
if perp_dir == "short":
perp_pnl = (entry - mark) * coins
else:
perp_pnl = (mark - entry) * coins
except Exception:
perp_pnl = 0.0
update_leg(
@@ -1664,10 +1679,12 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
realized_pnl=round(perp_pnl, 4),
)
notes.append(f"永续已平 {symbol}")
_release_db_write(conn)
# 3) 卖币回 USDT
sell_r: dict[str, Any] = {"ok": True, "skipped": True}
try:
_release_db_write(conn)
sell_ex = cfg.get("exchange_options") or cfg.get("exchange")
if sell_ex is not None:
sell_r = spot_market_sell_coin_to_usdt(sell_ex, underlying=uly)