修复页面握库拉余额导致 database is locked 整页 500。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1619,22 +1619,38 @@ def account_risk_status(conn):
|
|||||||
ensure_account_risk_schema,
|
ensure_account_risk_schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
ensure_account_risk_schema(conn)
|
|
||||||
now = app_now()
|
now = app_now()
|
||||||
st = compute_account_risk_status(
|
try:
|
||||||
conn,
|
ensure_account_risk_schema(conn)
|
||||||
trading_day=get_trading_day(),
|
st = compute_account_risk_status(
|
||||||
now=now,
|
conn,
|
||||||
fmt_local_ms=ms_to_app_local_str,
|
trading_day=get_trading_day(),
|
||||||
)
|
now=now,
|
||||||
st = enrich_risk_status_countdown(st, now=now, daily_reset_hour=TRADING_DAY_RESET_HOUR)
|
fmt_local_ms=ms_to_app_local_str,
|
||||||
from lib.trade.trade_labels_lib import count_position_limit_active_monitors
|
)
|
||||||
|
st = enrich_risk_status_countdown(st, now=now, daily_reset_hour=TRADING_DAY_RESET_HOUR)
|
||||||
|
from lib.trade.trade_labels_lib import count_position_limit_active_monitors
|
||||||
|
|
||||||
return apply_position_limit_risk(
|
return apply_position_limit_risk(
|
||||||
st,
|
st,
|
||||||
count_position_limit_active_monitors(conn),
|
count_position_limit_active_monitors(conn),
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[account_risk_status] {e}", flush=True)
|
||||||
|
return {
|
||||||
|
"enabled": True,
|
||||||
|
"status": "normal",
|
||||||
|
"status_label": "正常",
|
||||||
|
"can_trade": True,
|
||||||
|
"reason": "",
|
||||||
|
"cooloff_until_ms": None,
|
||||||
|
"cooloff_until": None,
|
||||||
|
"manual_close_count": 0,
|
||||||
|
"daily_loss_count": 0,
|
||||||
|
"daily_frozen": False,
|
||||||
|
"freeze_remaining_sec": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def hub_user_initiated_close(
|
def hub_user_initiated_close(
|
||||||
@@ -3863,6 +3879,11 @@ def render_main_page(page="options", embed_mode=None):
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
session_row = ensure_session(conn, trading_day)
|
session_row = ensure_session(conn, trading_day)
|
||||||
local_current_capital = float(session_row["current_capital"])
|
local_current_capital = float(session_row["current_capital"])
|
||||||
|
try:
|
||||||
|
conn.commit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
conn.close()
|
||||||
from lib.instance.instance_embed_context_lib import (
|
from lib.instance.instance_embed_context_lib import (
|
||||||
embed_render_plan,
|
embed_render_plan,
|
||||||
minimal_stats_bundle,
|
minimal_stats_bundle,
|
||||||
@@ -3875,6 +3896,7 @@ def render_main_page(page="options", embed_mode=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
plan = embed_render_plan(page, embed_mode)
|
plan = embed_render_plan(page, embed_mode)
|
||||||
|
# 拉交易所余额前不持库连接,避免 database is locked → 页面 500
|
||||||
if plan.exchange_capitals:
|
if plan.exchange_capitals:
|
||||||
funding_capital, trading_capital = get_exchange_capitals()
|
funding_capital, trading_capital = get_exchange_capitals()
|
||||||
else:
|
else:
|
||||||
@@ -3938,6 +3960,7 @@ def render_main_page(page="options", embed_mode=None):
|
|||||||
options_margin_mode = "coin"
|
options_margin_mode = "coin"
|
||||||
options_underly = "ETH"
|
options_underly = "ETH"
|
||||||
recommended_capital = get_recommended_capital(capital_for_sizing)
|
recommended_capital = get_recommended_capital(capital_for_sizing)
|
||||||
|
conn = get_db()
|
||||||
key_list = (
|
key_list = (
|
||||||
conn.execute("SELECT * FROM key_monitors").fetchall() if plan.key_list else []
|
conn.execute("SELECT * FROM key_monitors").fetchall() if plan.key_list else []
|
||||||
)
|
)
|
||||||
@@ -4328,7 +4351,13 @@ def api_account_snapshot():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
session_row = ensure_session(conn, trading_day)
|
session_row = ensure_session(conn, trading_day)
|
||||||
local_current_capital = float(session_row["current_capital"])
|
local_current_capital = float(session_row["current_capital"])
|
||||||
|
try:
|
||||||
|
conn.commit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
conn.close()
|
||||||
force_refresh = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
force_refresh = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
||||||
|
# 拉交易所余额前不持库连接
|
||||||
funding_capital, trading_capital = get_exchange_capitals(force=force_refresh)
|
funding_capital, trading_capital = get_exchange_capitals(force=force_refresh)
|
||||||
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
||||||
# 顶栏只展示交易所余额;勿用会话资金回退(避免模拟盘 30U 残留到实盘)
|
# 顶栏只展示交易所余额;勿用会话资金回退(避免模拟盘 30U 残留到实盘)
|
||||||
@@ -4372,6 +4401,7 @@ def api_account_snapshot():
|
|||||||
options_margin_mode = "coin"
|
options_margin_mode = "coin"
|
||||||
options_underly = "ETH"
|
options_underly = "ETH"
|
||||||
recommended_capital = get_recommended_capital(capital_for_sizing)
|
recommended_capital = get_recommended_capital(capital_for_sizing)
|
||||||
|
conn = get_db()
|
||||||
from lib.trade.trade_labels_lib import count_position_limit_active_monitors
|
from lib.trade.trade_labels_lib import count_position_limit_active_monitors
|
||||||
|
|
||||||
position_limit_count = count_position_limit_active_monitors(conn)
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
|
|||||||
+116
-99
@@ -275,33 +275,39 @@ def _load_state(conn):
|
|||||||
|
|
||||||
|
|
||||||
def _sync_trading_day(conn, trading_day: str, now: Optional[datetime] = None) -> Any:
|
def _sync_trading_day(conn, trading_day: str, now: Optional[datetime] = None) -> Any:
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
row = _load_state(conn)
|
row = _load_state(conn)
|
||||||
td = (trading_day or "").strip()
|
td = (trading_day or "").strip()
|
||||||
stored = str(_row_get(row, "trading_day") or "").strip()
|
stored = str(_row_get(row, "trading_day") or "").strip()
|
||||||
if stored != td:
|
if stored != td:
|
||||||
now_ms = _now_ms(now)
|
now_ms = _now_ms(now)
|
||||||
cooloff_active = _resolved_cooloff_until_ms(row, now_ms)
|
cooloff_active = _resolved_cooloff_until_ms(row, now_ms)
|
||||||
conn.execute(
|
try:
|
||||||
"""UPDATE account_risk_state SET
|
conn.execute(
|
||||||
trading_day=?,
|
"""UPDATE account_risk_state SET
|
||||||
manual_close_count=0,
|
trading_day=?,
|
||||||
daily_loss_count=0,
|
manual_close_count=0,
|
||||||
daily_frozen=0,
|
daily_loss_count=0,
|
||||||
cooloff_until_ms=?,
|
daily_frozen=0,
|
||||||
cooloff_hours=?,
|
cooloff_until_ms=?,
|
||||||
last_close_at_ms=?,
|
cooloff_hours=?,
|
||||||
pending_journal_trade_id=NULL,
|
last_close_at_ms=?,
|
||||||
updated_at=?
|
pending_journal_trade_id=NULL,
|
||||||
WHERE id=1""",
|
updated_at=?
|
||||||
(
|
WHERE id=1""",
|
||||||
td,
|
(
|
||||||
cooloff_active,
|
td,
|
||||||
_row_get(row, "cooloff_hours") if cooloff_active else None,
|
cooloff_active,
|
||||||
_row_get(row, "last_close_at_ms") if cooloff_active else None,
|
_row_get(row, "cooloff_hours") if cooloff_active else None,
|
||||||
(now or datetime.now()).strftime("%Y-%m-%d %H:%M:%S"),
|
_row_get(row, "last_close_at_ms") if cooloff_active else None,
|
||||||
),
|
(now or datetime.now()).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
),
|
||||||
row = _load_state(conn)
|
)
|
||||||
|
row = _load_state(conn)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# 页面渲染勿因跨日写库锁死而 500;下次再同步
|
||||||
|
pass
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
@@ -800,6 +806,24 @@ def apply_position_limit_risk(
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _disabled_or_fallback_risk_status(*, enabled: bool = False) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"enabled": bool(enabled),
|
||||||
|
"status": STATUS_NORMAL,
|
||||||
|
"status_label": STATUS_LABELS[STATUS_NORMAL],
|
||||||
|
"can_trade": True,
|
||||||
|
"reason": "",
|
||||||
|
"cooloff_until_ms": None,
|
||||||
|
"cooloff_until": None,
|
||||||
|
"manual_close_count": 0,
|
||||||
|
"daily_loss_count": 0,
|
||||||
|
"daily_frozen": False,
|
||||||
|
"daily_loss_limit": daily_loss_limit() if enabled else 0,
|
||||||
|
"pending_journal_trade_id": None,
|
||||||
|
"freeze_remaining_sec": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def compute_account_risk_status(
|
def compute_account_risk_status(
|
||||||
conn,
|
conn,
|
||||||
*,
|
*,
|
||||||
@@ -807,88 +831,81 @@ def compute_account_risk_status(
|
|||||||
now: Optional[datetime] = None,
|
now: Optional[datetime] = None,
|
||||||
fmt_local_ms: Optional[Callable[[int], str]] = None,
|
fmt_local_ms: Optional[Callable[[int], str]] = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
if not risk_control_enabled():
|
if not risk_control_enabled():
|
||||||
return {
|
return _disabled_or_fallback_risk_status(enabled=False)
|
||||||
"enabled": False,
|
try:
|
||||||
"status": STATUS_NORMAL,
|
row = _sync_trading_day(conn, trading_day, now=now)
|
||||||
"status_label": STATUS_LABELS[STATUS_NORMAL],
|
now_ms = _now_ms(now)
|
||||||
"can_trade": True,
|
daily_frozen = int(_row_get(row, "daily_frozen") or 0) == 1
|
||||||
"reason": "",
|
pending = _row_get(row, "pending_journal_trade_id")
|
||||||
"cooloff_until_ms": None,
|
|
||||||
"cooloff_until": None,
|
|
||||||
"manual_close_count": 0,
|
|
||||||
"daily_loss_count": 0,
|
|
||||||
"daily_frozen": False,
|
|
||||||
}
|
|
||||||
row = _sync_trading_day(conn, trading_day, now=now)
|
|
||||||
now_ms = _now_ms(now)
|
|
||||||
daily_frozen = int(_row_get(row, "daily_frozen") or 0) == 1
|
|
||||||
pending = _row_get(row, "pending_journal_trade_id")
|
|
||||||
cooloff_until_ms = _resolved_cooloff_until_ms(row, now_ms)
|
|
||||||
if (
|
|
||||||
not daily_frozen
|
|
||||||
and cooloff_until_ms is not None
|
|
||||||
and _journaled_manual_cooloff_expired(
|
|
||||||
conn, trading_day=trading_day, now_ms=now_ms, pending=pending
|
|
||||||
)
|
|
||||||
):
|
|
||||||
cooloff_until_ms = None
|
|
||||||
if not daily_frozen:
|
|
||||||
_repair_stale_cooloff_row(
|
|
||||||
conn, row, now_ms=now_ms, resolved_until_ms=cooloff_until_ms, now=now
|
|
||||||
)
|
|
||||||
row = _load_state(conn)
|
|
||||||
cooloff_until_ms = _resolved_cooloff_until_ms(row, now_ms)
|
cooloff_until_ms = _resolved_cooloff_until_ms(row, now_ms)
|
||||||
manual_close_count = int(_row_get(row, "manual_close_count") or 0)
|
if (
|
||||||
daily_loss_count = int(_row_get(row, "daily_loss_count") or 0)
|
not daily_frozen
|
||||||
loss_limit = daily_loss_limit()
|
and cooloff_until_ms is not None
|
||||||
|
and _journaled_manual_cooloff_expired(
|
||||||
|
conn, trading_day=trading_day, now_ms=now_ms, pending=pending
|
||||||
|
)
|
||||||
|
):
|
||||||
|
cooloff_until_ms = None
|
||||||
|
if not daily_frozen:
|
||||||
|
_repair_stale_cooloff_row(
|
||||||
|
conn, row, now_ms=now_ms, resolved_until_ms=cooloff_until_ms, now=now
|
||||||
|
)
|
||||||
|
row = _load_state(conn)
|
||||||
|
cooloff_until_ms = _resolved_cooloff_until_ms(row, now_ms)
|
||||||
|
manual_close_count = int(_row_get(row, "manual_close_count") or 0)
|
||||||
|
daily_loss_count = int(_row_get(row, "daily_loss_count") or 0)
|
||||||
|
loss_limit = daily_loss_limit()
|
||||||
|
|
||||||
status = STATUS_NORMAL
|
status = STATUS_NORMAL
|
||||||
reason = ""
|
reason = ""
|
||||||
if daily_frozen:
|
if daily_frozen:
|
||||||
status = STATUS_DAILY
|
status = STATUS_DAILY
|
||||||
parts = []
|
parts = []
|
||||||
if loss_limit > 0 and daily_loss_count >= loss_limit:
|
if loss_limit > 0 and daily_loss_count >= loss_limit:
|
||||||
parts.append(f"日亏损 {daily_loss_count}/{loss_limit} 次")
|
parts.append(f"日亏损 {daily_loss_count}/{loss_limit} 次")
|
||||||
if manual_close_count >= manual_close_daily_limit():
|
if manual_close_count >= manual_close_daily_limit():
|
||||||
parts.append(f"手动平仓 {manual_close_count} 次")
|
parts.append(f"手动平仓 {manual_close_count} 次")
|
||||||
if not parts:
|
if not parts:
|
||||||
parts.append("手动平仓/日亏损达限或复盘情绪标签")
|
parts.append("手动平仓/日亏损达限或复盘情绪标签")
|
||||||
reason = "账户今日已冻结(" + "、".join(parts) + ")"
|
reason = "账户今日已冻结(" + "、".join(parts) + ")"
|
||||||
elif cooloff_until_ms is not None:
|
elif cooloff_until_ms is not None:
|
||||||
remaining_ms = cooloff_until_ms - now_ms
|
remaining_ms = cooloff_until_ms - now_ms
|
||||||
hours = _cooloff_hours_value(row)
|
hours = _cooloff_hours_value(row)
|
||||||
status = _freeze_tier_from_remaining_ms(remaining_ms, hours)
|
status = _freeze_tier_from_remaining_ms(remaining_ms, hours)
|
||||||
status_label = _freeze_status_label(hours, status)
|
until_str = _ms_to_local_str(cooloff_until_ms, fmt_local_ms) if fmt_local_ms else None
|
||||||
until_str = _ms_to_local_str(cooloff_until_ms, fmt_local_ms) if fmt_local_ms else None
|
reason = f"账户{_freeze_status_label(hours, status)}中"
|
||||||
label = status_label
|
if until_str:
|
||||||
reason = f"账户{label}中"
|
reason += f",至 {until_str}"
|
||||||
if until_str:
|
|
||||||
reason += f",至 {until_str}"
|
|
||||||
|
|
||||||
can_trade = status == STATUS_NORMAL
|
can_trade = status == STATUS_NORMAL
|
||||||
freeze_remaining_sec = (
|
freeze_remaining_sec = (
|
||||||
max(0, (cooloff_until_ms - now_ms) // 1000) if cooloff_until_ms is not None else 0
|
max(0, (cooloff_until_ms - now_ms) // 1000) if cooloff_until_ms is not None else 0
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"status": status,
|
"status": status,
|
||||||
"status_label": _freeze_status_label(_cooloff_hours_value(row), status)
|
"status_label": _freeze_status_label(_cooloff_hours_value(row), status)
|
||||||
if status in (STATUS_FREEZE_1H, STATUS_FREEZE_4H)
|
if status in (STATUS_FREEZE_1H, STATUS_FREEZE_4H)
|
||||||
else STATUS_LABELS[status],
|
else STATUS_LABELS[status],
|
||||||
"can_trade": can_trade,
|
"can_trade": can_trade,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
"cooloff_until_ms": cooloff_until_ms,
|
"cooloff_until_ms": cooloff_until_ms,
|
||||||
"cooloff_until": _ms_to_local_str(cooloff_until_ms, fmt_local_ms)
|
"cooloff_until": _ms_to_local_str(cooloff_until_ms, fmt_local_ms)
|
||||||
if fmt_local_ms and cooloff_until_ms
|
if fmt_local_ms and cooloff_until_ms
|
||||||
else None,
|
else None,
|
||||||
"manual_close_count": manual_close_count,
|
"manual_close_count": manual_close_count,
|
||||||
"daily_loss_count": daily_loss_count,
|
"daily_loss_count": daily_loss_count,
|
||||||
"daily_loss_limit": loss_limit,
|
"daily_loss_limit": loss_limit,
|
||||||
"daily_frozen": daily_frozen,
|
"daily_frozen": daily_frozen,
|
||||||
"pending_journal_trade_id": pending,
|
"pending_journal_trade_id": pending,
|
||||||
"freeze_remaining_sec": freeze_remaining_sec if not can_trade else 0,
|
"freeze_remaining_sec": freeze_remaining_sec if not can_trade else 0,
|
||||||
}
|
}
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# 渲染路径遇 database is locked 时降级为正常,避免整页 500
|
||||||
|
return _disabled_or_fallback_risk_status(enabled=True)
|
||||||
|
|
||||||
|
|
||||||
def account_risk_blocks_trading(
|
def account_risk_blocks_trading(
|
||||||
|
|||||||
Reference in New Issue
Block a user