Fix false freeze after restart from stale account_risk_state.
Clear expired cooloff on read, never restart timer from invalid future anchors, and reconcile with journaled manual closes when the 1h window already ended. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,6 +33,16 @@ def _mem_conn():
|
||||
return conn
|
||||
|
||||
|
||||
def _mem_conn_with_journal():
|
||||
conn = _mem_conn()
|
||||
conn.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS journal_entries (
|
||||
close_datetime TEXT, early_exit_trigger TEXT, early_exit_note TEXT
|
||||
)"""
|
||||
)
|
||||
return conn
|
||||
|
||||
|
||||
def _local_ms(dt_naive: datetime) -> int:
|
||||
return int(dt_naive.replace(tzinfo=APP_TZ).timestamp() * 1000)
|
||||
|
||||
@@ -257,6 +267,58 @@ class AccountRiskLibTests(unittest.TestCase):
|
||||
st = compute_account_risk_status(conn, trading_day="2026-06-18", now=now)
|
||||
self.assertEqual(st["status"], STATUS_NORMAL)
|
||||
self.assertTrue(st["can_trade"])
|
||||
row = conn.execute(
|
||||
"SELECT cooloff_until_ms, cooloff_hours, last_close_at_ms FROM account_risk_state WHERE id=1"
|
||||
).fetchone()
|
||||
self.assertIsNone(row["cooloff_until_ms"])
|
||||
self.assertIsNone(row["last_close_at_ms"])
|
||||
|
||||
def test_corrupted_anchor_cleared_when_journaled_manual_expired(self):
|
||||
"""上一版误把 last_close 写成近期时刻时,已复盘且 1h 已过的仍应显示正常。"""
|
||||
conn = _mem_conn_with_journal()
|
||||
now = datetime(2026, 6, 18, 22, 30, 0)
|
||||
now_ms = _local_ms(now)
|
||||
bad_last = now_ms - 60 * 1000
|
||||
conn.execute(
|
||||
"""UPDATE account_risk_state SET
|
||||
trading_day='2026-06-18',
|
||||
manual_close_count=1,
|
||||
cooloff_until_ms=?,
|
||||
cooloff_hours=1,
|
||||
last_close_at_ms=?,
|
||||
pending_journal_trade_id=NULL,
|
||||
daily_frozen=0
|
||||
WHERE id=1""",
|
||||
(bad_last + 3600 * 1000, bad_last),
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO journal_entries (close_datetime, early_exit_trigger, early_exit_note) VALUES (?,?,?)",
|
||||
("2026-06-18 17:56:00", "手动平仓", "按计划离场"),
|
||||
)
|
||||
st = compute_account_risk_status(conn, trading_day="2026-06-18", now=now)
|
||||
self.assertEqual(st["status"], STATUS_NORMAL)
|
||||
self.assertTrue(st["can_trade"])
|
||||
|
||||
def test_future_last_close_does_not_restart_cooloff(self):
|
||||
"""脏数据 last_close 在未来时,不应重启 1h/4h 冻结。"""
|
||||
conn = _mem_conn()
|
||||
now = datetime(2026, 6, 18, 22, 30, 0)
|
||||
now_ms = _local_ms(now)
|
||||
future_close = now_ms + 49 * 60 * 1000
|
||||
conn.execute(
|
||||
"""UPDATE account_risk_state SET
|
||||
trading_day='2026-06-18',
|
||||
manual_close_count=1,
|
||||
cooloff_until_ms=?,
|
||||
cooloff_hours=1,
|
||||
last_close_at_ms=?,
|
||||
daily_frozen=0
|
||||
WHERE id=1""",
|
||||
(future_close + 3600 * 1000, future_close),
|
||||
)
|
||||
st = compute_account_risk_status(conn, trading_day="2026-06-18", now=now)
|
||||
self.assertEqual(st["status"], STATUS_NORMAL)
|
||||
self.assertTrue(st["can_trade"])
|
||||
|
||||
def test_active_4h_countdown_matches_tier(self):
|
||||
conn = _mem_conn()
|
||||
@@ -313,11 +375,8 @@ class AccountRiskLibTests(unittest.TestCase):
|
||||
(future_close + 4 * 3600 * 1000, future_close),
|
||||
)
|
||||
st = compute_account_risk_status(conn, trading_day="2026-06-18", now=now)
|
||||
self.assertEqual(st["status"], STATUS_FREEZE_4H)
|
||||
self.assertLessEqual(st["freeze_remaining_sec"], 4 * 3600 + 2)
|
||||
self.assertGreater(st["freeze_remaining_sec"], 3 * 3600 + 58 * 60)
|
||||
row = conn.execute("SELECT last_close_at_ms, cooloff_until_ms FROM account_risk_state WHERE id=1").fetchone()
|
||||
self.assertLessEqual(int(row["last_close_at_ms"]), now_ms + 60 * 1000)
|
||||
self.assertEqual(st["status"], STATUS_NORMAL)
|
||||
self.assertTrue(st["can_trade"])
|
||||
|
||||
def test_legacy_naive_utc_ms_countdown_normalized(self):
|
||||
conn = _mem_conn()
|
||||
@@ -366,6 +425,8 @@ class AccountRiskLibTests(unittest.TestCase):
|
||||
later = datetime(2026, 6, 14, 13, 0, 0)
|
||||
st = compute_account_risk_status(conn, trading_day="2026-06-14", now=later)
|
||||
self.assertEqual(st["status"], STATUS_NORMAL)
|
||||
row = conn.execute("SELECT cooloff_until_ms FROM account_risk_state WHERE id=1").fetchone()
|
||||
self.assertIsNone(row["cooloff_until_ms"])
|
||||
|
||||
def test_trading_day_reset_clears_daily_frozen(self):
|
||||
conn = _mem_conn()
|
||||
|
||||
Reference in New Issue
Block a user