fix(risk): shorten cooloff on review save and when count was reset
Allow 1h reduction for any active 4h-tier cooloff, hook trade record review updates, and fix freeze label thresholds. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+32
-10
@@ -206,23 +206,23 @@ def _cooloff_until_ms(row) -> Optional[int]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _in_active_manual_cooloff(row, now_ms: int) -> bool:
|
def _journal_can_reduce_cooloff(row, pending, now_ms: int) -> bool:
|
||||||
if int(_row_get(row, "daily_frozen") or 0) == 1:
|
if int(_row_get(row, "daily_frozen") or 0) == 1:
|
||||||
return False
|
return False
|
||||||
if int(_row_get(row, "manual_close_count") or 0) < 1:
|
|
||||||
return False
|
|
||||||
until_ms = _cooloff_until_ms(row)
|
until_ms = _cooloff_until_ms(row)
|
||||||
return until_ms is not None and until_ms > now_ms
|
if until_ms is None or until_ms <= now_ms:
|
||||||
|
return False
|
||||||
|
journal_h = cooling_hours_manual_journal()
|
||||||
def _journal_can_reduce_cooloff(row, pending, now_ms: int) -> bool:
|
cooloff_h = float(_row_get(row, "cooloff_hours") or cooling_hours_manual())
|
||||||
|
if cooloff_h <= journal_h + 1e-6:
|
||||||
|
return False
|
||||||
if pending is not None:
|
if pending is not None:
|
||||||
try:
|
try:
|
||||||
if int(pending) != 0:
|
if int(pending) != 0:
|
||||||
return True
|
return True
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return True
|
return True
|
||||||
return _in_active_manual_cooloff(row, now_ms)
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _journal_cooloff_until_ms(row, now_ms: int, journal_hours: float) -> int:
|
def _journal_cooloff_until_ms(row, now_ms: int, journal_hours: float) -> int:
|
||||||
@@ -384,6 +384,27 @@ def on_journal_saved(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_manual_close_journal_cooloff(
|
||||||
|
conn,
|
||||||
|
*,
|
||||||
|
early_exit_note: str,
|
||||||
|
trading_day: str,
|
||||||
|
now: Optional[datetime] = None,
|
||||||
|
) -> None:
|
||||||
|
"""核对修改或复盘:手动平仓 + 说明后尝试将 4h 冷静期降为 1h。"""
|
||||||
|
note = (early_exit_note or "").strip()
|
||||||
|
if not note:
|
||||||
|
return
|
||||||
|
on_journal_saved(
|
||||||
|
conn,
|
||||||
|
early_exit_trigger="手动平仓",
|
||||||
|
early_exit_note=note,
|
||||||
|
mood_issues_raw="",
|
||||||
|
trading_day=trading_day,
|
||||||
|
now=now,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def compute_account_risk_status(
|
def compute_account_risk_status(
|
||||||
conn,
|
conn,
|
||||||
*,
|
*,
|
||||||
@@ -420,8 +441,9 @@ def compute_account_risk_status(
|
|||||||
status = STATUS_DAILY
|
status = STATUS_DAILY
|
||||||
reason = f"账户今日已冻结(手动平仓 {manual_close_count} 次或复盘情绪标签)"
|
reason = f"账户今日已冻结(手动平仓 {manual_close_count} 次或复盘情绪标签)"
|
||||||
elif cooloff_until_ms is not None and cooloff_until_ms > now_ms:
|
elif cooloff_until_ms is not None and cooloff_until_ms > now_ms:
|
||||||
h = int(cooloff_hours or cooling_hours_manual())
|
h = float(cooloff_hours or cooling_hours_manual())
|
||||||
status = STATUS_FREEZE_1H if h <= 1 else STATUS_FREEZE_4H
|
journal_h = cooling_hours_manual_journal()
|
||||||
|
status = STATUS_FREEZE_1H if h <= journal_h + 1e-6 else STATUS_FREEZE_4H
|
||||||
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
|
||||||
label = STATUS_LABELS[status]
|
label = STATUS_LABELS[status]
|
||||||
reason = f"账户{label}中"
|
reason = f"账户{label}中"
|
||||||
|
|||||||
@@ -9200,6 +9200,15 @@ def api_trade_record_review_update():
|
|||||||
WHERE id=?""",
|
WHERE id=?""",
|
||||||
tuple(base_params + [rec_id]),
|
tuple(base_params + [rec_id]),
|
||||||
)
|
)
|
||||||
|
if reviewed_result == "手动平仓" and reviewed_miss_reason:
|
||||||
|
from account_risk_lib import apply_manual_close_journal_cooloff
|
||||||
|
|
||||||
|
apply_manual_close_journal_cooloff(
|
||||||
|
conn,
|
||||||
|
early_exit_note=reviewed_miss_reason,
|
||||||
|
trading_day=get_trading_day(),
|
||||||
|
now=app_now(),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
||||||
|
|||||||
@@ -9144,6 +9144,15 @@ def api_trade_record_review_update():
|
|||||||
WHERE id=?""",
|
WHERE id=?""",
|
||||||
tuple(base_params + [rec_id]),
|
tuple(base_params + [rec_id]),
|
||||||
)
|
)
|
||||||
|
if reviewed_result == "手动平仓" and reviewed_miss_reason:
|
||||||
|
from account_risk_lib import apply_manual_close_journal_cooloff
|
||||||
|
|
||||||
|
apply_manual_close_journal_cooloff(
|
||||||
|
conn,
|
||||||
|
early_exit_note=reviewed_miss_reason,
|
||||||
|
trading_day=get_trading_day(),
|
||||||
|
now=app_now(),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
||||||
|
|||||||
@@ -9144,6 +9144,15 @@ def api_trade_record_review_update():
|
|||||||
WHERE id=?""",
|
WHERE id=?""",
|
||||||
tuple(base_params + [rec_id]),
|
tuple(base_params + [rec_id]),
|
||||||
)
|
)
|
||||||
|
if reviewed_result == "手动平仓" and reviewed_miss_reason:
|
||||||
|
from account_risk_lib import apply_manual_close_journal_cooloff
|
||||||
|
|
||||||
|
apply_manual_close_journal_cooloff(
|
||||||
|
conn,
|
||||||
|
early_exit_note=reviewed_miss_reason,
|
||||||
|
trading_day=get_trading_day(),
|
||||||
|
now=app_now(),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
||||||
|
|||||||
@@ -8682,6 +8682,15 @@ def api_trade_record_review_update():
|
|||||||
WHERE id=?""",
|
WHERE id=?""",
|
||||||
tuple(base_params + [rec_id]),
|
tuple(base_params + [rec_id]),
|
||||||
)
|
)
|
||||||
|
if reviewed_result == "手动平仓" and reviewed_miss_reason:
|
||||||
|
from account_risk_lib import apply_manual_close_journal_cooloff
|
||||||
|
|
||||||
|
apply_manual_close_journal_cooloff(
|
||||||
|
conn,
|
||||||
|
early_exit_note=reviewed_miss_reason,
|
||||||
|
trading_day=get_trading_day(),
|
||||||
|
now=app_now(),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
return jsonify({"ok": True, "id": rec_id, "actual_rr": actual_rr, "hold_minutes": hold_minutes})
|
||||||
|
|||||||
@@ -45,8 +45,10 @@
|
|||||||
|
|
||||||
**复盘缩短 1h 说明**:
|
**复盘缩短 1h 说明**:
|
||||||
- 复盘表单须选 **离场触发 = 手动平仓**,并在 **离场补充** 填写说明(不是下方「备注」栏)。
|
- 复盘表单须选 **离场触发 = 手动平仓**,并在 **离场补充** 填写说明(不是下方「备注」栏)。
|
||||||
- 中控全平/实例手动平仓后,只要账户仍在 4h 冷静期内,完成上述复盘即可降为 1h。
|
- 或在交易记录 **核对修改** 中:结果填 **手动平仓**,**备注** 填写说明(勾选「核对修改」后点按钮)。
|
||||||
|
- 中控全平/实例手动平仓后,只要账户仍在 4h 冷静期内,完成上述任一种即可降为 1h。
|
||||||
- 若在平仓后超过 1h 才复盘,则从复盘保存时刻起再计 1h(不会延长原 4h 窗口)。
|
- 若在平仓后超过 1h 才复盘,则从复盘保存时刻起再计 1h(不会延长原 4h 窗口)。
|
||||||
|
- 保存后需 **重启对应实例**(`pm2 restart crypto_gate` 等)并刷新页面,状态才会更新。
|
||||||
|
|
||||||
## 环境变量
|
## 环境变量
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,34 @@ class AccountRiskLibTests(unittest.TestCase):
|
|||||||
st = compute_account_risk_status(conn, trading_day="2026-06-14", now=now)
|
st = compute_account_risk_status(conn, trading_day="2026-06-14", now=now)
|
||||||
self.assertEqual(st["status"], STATUS_FREEZE_1H)
|
self.assertEqual(st["status"], STATUS_FREEZE_1H)
|
||||||
|
|
||||||
|
def test_journal_reduces_when_manual_count_cleared_but_cooloff_active(self):
|
||||||
|
conn = _mem_conn()
|
||||||
|
now = datetime(2026, 6, 15, 10, 0, 0)
|
||||||
|
now_ms = int(now.replace(tzinfo=timezone.utc).timestamp() * 1000)
|
||||||
|
close_ms = now_ms - 3600 * 1000
|
||||||
|
until_ms = close_ms + 4 * 3600 * 1000
|
||||||
|
conn.execute(
|
||||||
|
"""UPDATE account_risk_state SET
|
||||||
|
trading_day='2026-06-15',
|
||||||
|
manual_close_count=0,
|
||||||
|
cooloff_until_ms=?,
|
||||||
|
cooloff_hours=4,
|
||||||
|
last_close_at_ms=?,
|
||||||
|
daily_frozen=0
|
||||||
|
WHERE id=1""",
|
||||||
|
(until_ms, close_ms),
|
||||||
|
)
|
||||||
|
on_journal_saved(
|
||||||
|
conn,
|
||||||
|
early_exit_trigger="手动平仓",
|
||||||
|
early_exit_note="切日后补复盘",
|
||||||
|
mood_issues_raw="",
|
||||||
|
trading_day="2026-06-15",
|
||||||
|
now=now,
|
||||||
|
)
|
||||||
|
st = compute_account_risk_status(conn, trading_day="2026-06-15", now=now)
|
||||||
|
self.assertEqual(st["status"], STATUS_FREEZE_1H)
|
||||||
|
|
||||||
def test_journal_late_save_still_gets_1h_from_now(self):
|
def test_journal_late_save_still_gets_1h_from_now(self):
|
||||||
conn = _mem_conn()
|
conn = _mem_conn()
|
||||||
close_at = datetime(2026, 6, 14, 12, 0, 0)
|
close_at = datetime(2026, 6, 14, 12, 0, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user