diff --git a/app.py b/app.py index c0137a6..e1a63ce 100644 --- a/app.py +++ b/app.py @@ -1591,8 +1591,14 @@ def _purge_key_monitors_if_full_margin(): 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 + try: + conn.execute("PRAGMA busy_timeout=30000") + conn.execute("PRAGMA journal_mode=WAL") + except Exception: + pass return conn diff --git a/lib/hedge_plan/hedge_plan_orders_lib.py b/lib/hedge_plan/hedge_plan_orders_lib.py index 0ebcbf5..4c42025 100644 --- a/lib/hedge_plan/hedge_plan_orders_lib.py +++ b/lib/hedge_plan/hedge_plan_orders_lib.py @@ -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)