fix(trading): close five risk gaps surfaced by pre-launch audit
1. hyperliquid.open_position now returns effective_leverage. HL clips the requested leverage to each asset's max (e.g. memes capped at 3×) and silently applied the lower value; we were discarding that. For System-2 this meant sys2_protective_stop_pct was computed against the REQUESTED leverage, so the "inside-liquidation" full-close rung was actually well inside an 8.5% stop while the real liquidation sat ~33% away — strategy intent broken. bot_engine now reads the effective value back, recomputes the protective stop + derisk ladder against it, and freezes the correct values into BotTrade.eff_* / .leverage. 2. circuit_breaker.clear_trip now clears BOTH systems by default. The sys2 column was never reachable from any reset path — a sys2 trip forced users to wait the full 24h lockout even after explicit human re-arm. 3. /api/user/.../manual-window and /auto-trade re-arm endpoints now clear sys1 AND sys2 CB on the same human ack. Symmetry with check_and_trip / is_tripped which already supported both systems. 4. telegram.py deep-link map: btc_bottom_reversal + funding_reversal alerts now point at /en/macro instead of the now-404 /en/btc. 5. (Already landed: _sys2_mode UnboundLocalError fix in bot_engine — restated here for the audit trail.) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+23
-11
@@ -339,13 +339,18 @@ async def set_manual_window(
|
||||
until = _dt.now(_tz.utc).replace(tzinfo=None) + _td(hours=hours_raw)
|
||||
sub.manual_window_until = until
|
||||
new_until_iso = iso_utc(until)
|
||||
# Explicit re-arm clears any active circuit-breaker trip — human in
|
||||
# the loop has acknowledged the risk and chosen to resume.
|
||||
# Explicit re-arm clears any active circuit-breaker trip on BOTH
|
||||
# systems — the human ack covers the whole wallet. (Earlier only the
|
||||
# sys1 breaker was cleared, leaving the sys2 book locked for 24h.)
|
||||
cb_cleared = False
|
||||
if sub.circuit_breaker_tripped_at is not None:
|
||||
sub.circuit_breaker_tripped_at = None
|
||||
sub.circuit_breaker_reason = None
|
||||
cb_cleared = True
|
||||
for _col_at, _col_reason in (
|
||||
("circuit_breaker_tripped_at", "circuit_breaker_reason"),
|
||||
("sys2_cb_tripped_at", "sys2_cb_reason"),
|
||||
):
|
||||
if getattr(sub, _col_at) is not None:
|
||||
setattr(sub, _col_at, None)
|
||||
setattr(sub, _col_reason, None)
|
||||
cb_cleared = True
|
||||
logger.info("Manual window armed for %s: until %s (%dh)%s",
|
||||
wallet, until, hours_raw, " — CB cleared" if cb_cleared else "")
|
||||
|
||||
@@ -402,11 +407,18 @@ async def set_auto_trade(
|
||||
|
||||
sub.auto_trade = enabled
|
||||
cb_cleared = False
|
||||
if enabled and sub.circuit_breaker_tripped_at is not None:
|
||||
# Turning Auto-Trade ON = explicit human ack → clear the breaker.
|
||||
sub.circuit_breaker_tripped_at = None
|
||||
sub.circuit_breaker_reason = None
|
||||
cb_cleared = True
|
||||
if enabled:
|
||||
# Turning Auto-Trade ON = explicit human ack → clear BOTH breakers
|
||||
# (sys1 Trump + sys2 reversal). Previously only sys1 was cleared,
|
||||
# leaving the reversal book locked even after the user re-armed.
|
||||
for _col_at, _col_reason in (
|
||||
("circuit_breaker_tripped_at", "circuit_breaker_reason"),
|
||||
("sys2_cb_tripped_at", "sys2_cb_reason"),
|
||||
):
|
||||
if getattr(sub, _col_at) is not None:
|
||||
setattr(sub, _col_at, None)
|
||||
setattr(sub, _col_reason, None)
|
||||
cb_cleared = True
|
||||
await db.commit()
|
||||
logger.info("Auto-Trade %s for %s%s",
|
||||
"ON" if enabled else "OFF", wallet,
|
||||
|
||||
Reference in New Issue
Block a user