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:
@@ -1591,8 +1591,14 @@ def _purge_key_monitors_if_full_margin():
|
|||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
conn = sqlite3.connect(DB_PATH)
|
# timeout/busy_timeout: 并发写(平仓握锁 + sim 再开库)时等待而非立刻 database is locked
|
||||||
|
conn = sqlite3.connect(DB_PATH, timeout=30)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
try:
|
||||||
|
conn.execute("PRAGMA busy_timeout=30000")
|
||||||
|
conn.execute("PRAGMA journal_mode=WAL")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ def _now() -> str:
|
|||||||
return datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S")
|
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:
|
def _env_bool(key: str, default: bool = False) -> bool:
|
||||||
raw = (os.getenv(key) or "").strip().lower()
|
raw = (os.getenv(key) or "").strip().lower()
|
||||||
if not raw:
|
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
|
perp_pnl = 0.0
|
||||||
close_detail: dict[str, Any] = {}
|
close_detail: dict[str, Any] = {}
|
||||||
|
|
||||||
# 1) 先平期权
|
# 1) 先平期权(下单前先提交,避免握锁撞 sim 库)
|
||||||
if opt:
|
if opt:
|
||||||
inst_id = str(opt.get("inst_id") or "")
|
inst_id = str(opt.get("inst_id") or "")
|
||||||
sheets = float(opt.get("size") or 1)
|
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),
|
"plan_id": int(plan_id),
|
||||||
"need_liquidity": True,
|
"need_liquidity": True,
|
||||||
}
|
}
|
||||||
|
_release_db_write(conn)
|
||||||
close_r = _sell_option(cfg, inst_id=inst_id, sheets=sheets, dry_run=False)
|
close_r = _sell_option(cfg, inst_id=inst_id, sheets=sheets, dry_run=False)
|
||||||
close_detail["option"] = close_r
|
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)):
|
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),
|
realized_pnl=round(opt_pnl, 4),
|
||||||
)
|
)
|
||||||
notes.append(f"期权已平 {inst_id}")
|
notes.append(f"期权已平 {inst_id}")
|
||||||
|
_release_db_write(conn)
|
||||||
|
|
||||||
# 2) 再平永续
|
# 2) 再平永续
|
||||||
if perp:
|
if perp:
|
||||||
@@ -1616,6 +1626,7 @@ def execute_manual_close_plan(cfg: dict[str, Any], conn: Any, plan_id: int) -> d
|
|||||||
)
|
)
|
||||||
notes.append("永续无仓/已平")
|
notes.append("永续无仓/已平")
|
||||||
else:
|
else:
|
||||||
|
_release_db_write(conn)
|
||||||
perp_close = _close_perp(
|
perp_close = _close_perp(
|
||||||
cfg, symbol=symbol, direction=perp_dir, contracts=contracts, dry_run=False
|
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,
|
"close": close_detail,
|
||||||
"notes": notes,
|
"notes": notes,
|
||||||
}
|
}
|
||||||
# 粗估永续盈亏
|
# 粗估永续盈亏(优先用成交回报)
|
||||||
try:
|
try:
|
||||||
entry = float(perp.get("avg_open") or plan.get("entry_mark") or 0)
|
info = (perp_close.get("order") or {}).get("info") or {}
|
||||||
get_m = cfg.get("get_live_position_exchange_metrics")
|
if info.get("pnl") is not None:
|
||||||
mark = entry
|
perp_pnl = float(info.get("pnl") or 0)
|
||||||
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:
|
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:
|
except Exception:
|
||||||
perp_pnl = 0.0
|
perp_pnl = 0.0
|
||||||
update_leg(
|
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),
|
realized_pnl=round(perp_pnl, 4),
|
||||||
)
|
)
|
||||||
notes.append(f"永续已平 {symbol}")
|
notes.append(f"永续已平 {symbol}")
|
||||||
|
_release_db_write(conn)
|
||||||
|
|
||||||
# 3) 卖币回 USDT
|
# 3) 卖币回 USDT
|
||||||
sell_r: dict[str, Any] = {"ok": True, "skipped": True}
|
sell_r: dict[str, Any] = {"ok": True, "skipped": True}
|
||||||
try:
|
try:
|
||||||
|
_release_db_write(conn)
|
||||||
sell_ex = cfg.get("exchange_options") or cfg.get("exchange")
|
sell_ex = cfg.get("exchange_options") or cfg.get("exchange")
|
||||||
if sell_ex is not None:
|
if sell_ex is not None:
|
||||||
sell_r = spot_market_sell_coin_to_usdt(sell_ex, underlying=uly)
|
sell_r = spot_market_sell_coin_to_usdt(sell_ex, underlying=uly)
|
||||||
|
|||||||
Reference in New Issue
Block a user