进一步修复 SQLite 并发锁冲突,统一连接与重试机制。
新增 db_conn 模块、缓存 schema 初始化、positions 页 commit,风控读库自动重试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+77
-54
@@ -2,10 +2,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Callable, Optional, TypeVar
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
STATUS_NORMAL = "normal"
|
||||
STATUS_FREEZE_1H = "freeze_1h"
|
||||
STATUS_FREEZE_4H = "freeze_4h"
|
||||
@@ -79,6 +83,21 @@ def trading_day_reset_hour() -> int:
|
||||
_SCHEMA_READY = False
|
||||
|
||||
|
||||
def _db_retry(action: Callable[[], T], *, retries: int = 8, base_delay: float = 0.03) -> T:
|
||||
last: sqlite3.OperationalError | None = None
|
||||
for i in range(retries):
|
||||
try:
|
||||
return action()
|
||||
except sqlite3.OperationalError as exc:
|
||||
if "locked" not in str(exc).lower():
|
||||
raise
|
||||
last = exc
|
||||
time.sleep(base_delay * (2 ** i))
|
||||
if last is not None:
|
||||
raise last
|
||||
raise RuntimeError("db retry failed")
|
||||
|
||||
|
||||
def ensure_account_risk_schema(conn) -> None:
|
||||
global _SCHEMA_READY
|
||||
if _SCHEMA_READY:
|
||||
@@ -209,67 +228,71 @@ def reduce_cooloff_after_journal(conn, *, trading_day: str, now: Optional[dateti
|
||||
|
||||
|
||||
def get_risk_status(conn, *, now: Optional[datetime] = None) -> dict:
|
||||
ensure_account_risk_schema(conn)
|
||||
row = conn.execute("SELECT * FROM account_risk_state WHERE id=1").fetchone()
|
||||
td = trading_day_label(now)
|
||||
stored = str(_row_get(row, "trading_day") or "")
|
||||
if stored != td:
|
||||
conn.execute(
|
||||
"UPDATE account_risk_state SET trading_day=?, manual_close_count=0, daily_frozen=0 WHERE id=1 AND trading_day<>?",
|
||||
(td, td),
|
||||
)
|
||||
def _load() -> dict:
|
||||
ensure_account_risk_schema(conn)
|
||||
row = conn.execute("SELECT * FROM account_risk_state WHERE id=1").fetchone()
|
||||
td = trading_day_label(now)
|
||||
stored = str(_row_get(row, "trading_day") or "")
|
||||
if stored != td:
|
||||
conn.execute(
|
||||
"UPDATE account_risk_state SET trading_day=?, manual_close_count=0, daily_frozen=0 WHERE id=1 AND trading_day<>?",
|
||||
(td, td),
|
||||
)
|
||||
conn.commit()
|
||||
row = conn.execute("SELECT * FROM account_risk_state WHERE id=1").fetchone()
|
||||
|
||||
now_ms = _now_ms(now)
|
||||
daily = int(_row_get(row, "daily_frozen") or 0) == 1
|
||||
until = _row_get(row, "cooloff_until_ms")
|
||||
active = count_active_trade_monitors(conn)
|
||||
mx = max_active_positions()
|
||||
pos_limit = active >= mx
|
||||
now_ms = _now_ms(now)
|
||||
daily = int(_row_get(row, "daily_frozen") or 0) == 1
|
||||
until = _row_get(row, "cooloff_until_ms")
|
||||
active = count_active_trade_monitors(conn)
|
||||
mx = max_active_positions()
|
||||
pos_limit = active >= mx
|
||||
|
||||
if daily:
|
||||
if daily:
|
||||
return {
|
||||
"status": STATUS_DAILY,
|
||||
"status_label": STATUS_LABELS[STATUS_DAILY],
|
||||
"can_trade": False,
|
||||
"can_roll": False,
|
||||
"reason": "当日日冻结,禁止新开仓",
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
if until and int(until) > now_ms:
|
||||
rem = int((int(until) - now_ms) / 1000)
|
||||
hours = float(_row_get(row, "cooloff_hours") or cooling_hours_manual())
|
||||
st = STATUS_FREEZE_1H if hours <= cooling_hours_manual_journal() + 0.01 else STATUS_FREEZE_4H
|
||||
return {
|
||||
"status": st,
|
||||
"status_label": STATUS_LABELS[st],
|
||||
"can_trade": False,
|
||||
"can_roll": pos_limit,
|
||||
"reason": f"冷静期中,剩余约 {rem // 3600}h {(rem % 3600) // 60}m",
|
||||
"freeze_remaining_sec": rem,
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
if pos_limit:
|
||||
return {
|
||||
"status": STATUS_FREEZE_POSITION,
|
||||
"status_label": STATUS_LABELS[STATUS_FREEZE_POSITION],
|
||||
"can_trade": False,
|
||||
"can_roll": True,
|
||||
"reason": f"已达仓位上限 {active}/{mx}",
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
return {
|
||||
"status": STATUS_DAILY,
|
||||
"status_label": STATUS_LABELS[STATUS_DAILY],
|
||||
"can_trade": False,
|
||||
"can_roll": False,
|
||||
"reason": "当日日冻结,禁止新开仓",
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
if until and int(until) > now_ms:
|
||||
rem = int((int(until) - now_ms) / 1000)
|
||||
hours = float(_row_get(row, "cooloff_hours") or cooling_hours_manual())
|
||||
st = STATUS_FREEZE_1H if hours <= cooling_hours_manual_journal() + 0.01 else STATUS_FREEZE_4H
|
||||
return {
|
||||
"status": st,
|
||||
"status_label": STATUS_LABELS[st],
|
||||
"can_trade": False,
|
||||
"can_roll": pos_limit,
|
||||
"reason": f"冷静期中,剩余约 {rem // 3600}h {(rem % 3600) // 60}m",
|
||||
"freeze_remaining_sec": rem,
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
if pos_limit:
|
||||
return {
|
||||
"status": STATUS_FREEZE_POSITION,
|
||||
"status_label": STATUS_LABELS[STATUS_FREEZE_POSITION],
|
||||
"can_trade": False,
|
||||
"status": STATUS_NORMAL,
|
||||
"status_label": STATUS_LABELS[STATUS_NORMAL],
|
||||
"can_trade": True,
|
||||
"can_roll": True,
|
||||
"reason": f"已达仓位上限 {active}/{mx}",
|
||||
"reason": "可新开仓",
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
return {
|
||||
"status": STATUS_NORMAL,
|
||||
"status_label": STATUS_LABELS[STATUS_NORMAL],
|
||||
"can_trade": True,
|
||||
"can_roll": True,
|
||||
"reason": "可新开仓",
|
||||
"active_count": active,
|
||||
"max_active_positions": mx,
|
||||
}
|
||||
|
||||
return _db_retry(_load)
|
||||
|
||||
|
||||
def assert_can_open(conn) -> Optional[str]:
|
||||
|
||||
Reference in New Issue
Block a user