Cleanup: tidy the exit-manager / exits / db_pg changes (post-review)

Focused review pass on the session's safety-critical additions, applying the safe wins:
- exit_manager: drop the dead `_Tracked.last_stop` field (written, never read) and its
  assignment; guard `_rest_protective_stop` against a degenerate qty<1 (a 0-qty submit
  is rejected and would churn) + document the cur-falsy->breakeven fallback; reword the
  synthetic-R comment (the branch is also hit on a healthy trade once breakeven ratchets
  the stop to entry, not only the stripped-stop case).
- exits: surface default_trail_r_pct in ExitConfig.describe() (boot-log observability)
  and clamp it to [0, 0.95] in from_env (a >=1 / negative synthetic R would put the stop
  at/above entry).
- db_pg: comment the gate-vs-try-order distinction of _LAN_DB_HOSTS (superset incl.
  localhost) vs _LAN_DB_FALLBACKS so a future edit can't break the non-LAN-DSN guard.
- momentum_cli: drop an unused `import sys`.
Behavior-preserving; 29/29 exit/exits tests green. Deliberately did NOT extract a shared
flatten-vs-rest helper for the catastrophe/naked blocks — the explicit blocks read
clearer for this safety code and the tests guard against drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Opus 4.8 committed 2026-06-29 22:53:24 -04:00
1 parent 3076d22375
commit f5dc7a8faa
4 files changed
+20 -10

No files matched your search

-1
View File
@@ -17,7 +17,6 @@ exits non-zero if any check FAILs, so it is scriptable / CI-able.
from __future__ import annotations from __future__ import annotations
import json import json
import sys
from datetime import datetime, timezone from datetime import datetime, timezone
import typer import typer
+11 -8
View File
@@ -30,7 +30,6 @@ class _Tracked:
entry_price: float entry_price: float
init_stop: float # original opening-range-low stop (for the R math) init_stop: float # original opening-range-low stop (for the R math)
entry_ts: object # timestamp we first saw the position (~entry) entry_ts: object # timestamp we first saw the position (~entry)
last_stop: float # last stop we set at the broker
scaled: bool = False scaled: bool = False
@@ -120,6 +119,10 @@ class LiveExitManager:
below market when underwater (a sell-stop can't sit above the last price). below market when underwater (a sell-stop can't sit above the last price).
Returns the stop price placed.""" Returns the stop price placed."""
qty = int(abs(float(p.get("qty") or 0))) qty = int(abs(float(p.get("qty") or 0)))
if qty < 1: # degenerate qty: a 0-qty submit is
return round(entry, 2) # rejected and would churn — bail
# cur falsy (price missing / 0 after-hours) falls to the breakeven arm; a stop
# at entry is below market when in profit and a safe floor otherwise.
if cur and cur <= entry: if cur and cur <= entry:
pct = self.cfg.catastrophe_pct or 0.10 pct = self.cfg.catastrophe_pct or 0.10
stop_px = round(cur * (1 - pct), 2) stop_px = round(cur * (1 - pct), 2)
@@ -276,16 +279,17 @@ class LiveExitManager:
# the move since entry, not since we first noticed the position) # the move since entry, not since we first noticed the position)
entry_ts = self._entry_time(sym, orders) or _last_ts(bars) entry_ts = self._entry_time(sym, orders) or _last_ts(bars)
if cur_stop >= entry: if cur_stop >= entry:
# already at BREAKEVEN+ with no real R to trail from (the original # stop is at/above entry (breakeven already reached on a healthy
# stop was stripped/invalid). Trail on a SYNTHETIC R so the step-trail # trade, OR the original R was lost — stop stripped/invalid), so there
# / profit-lock still ratchet the broker stop UP as it rises (only ever # is no real risk distance to trail from. Trail on a SYNTHETIC R so the
# up, never below the live breakeven stop). 0 = leave it frozen. # step-trail / profit-lock still ratchet the broker stop UP as it rises
# (only ever up, never below the live stop). 0 = leave it frozen.
if not self.cfg.default_trail_r_pct: if not self.cfg.default_trail_r_pct:
continue continue
init = entry * (1.0 - self.cfg.default_trail_r_pct) init = entry * (1.0 - self.cfg.default_trail_r_pct)
self._tracked[sym] = _Tracked(entry, init, entry_ts, cur_stop) self._tracked[sym] = _Tracked(entry, init, entry_ts)
else: else:
self._tracked[sym] = _Tracked(entry, cur_stop, entry_ts, cur_stop) self._tracked[sym] = _Tracked(entry, cur_stop, entry_ts)
tr = self._tracked[sym] tr = self._tracked[sym]
since = _bars_since(bars, tr.entry_ts) since = _bars_since(bars, tr.entry_ts)
@@ -338,7 +342,6 @@ class LiveExitManager:
elif leg_id: elif leg_id:
try: try:
self.client.replace_order(leg_id, stop_price=round(d.desired_stop, 2)) self.client.replace_order(leg_id, stop_price=round(d.desired_stop, 2))
tr.last_stop = d.desired_stop
self._emit(sym, f"stop {sym} {cur_stop:.2f} -> {d.desired_stop:.2f}", self._emit(sym, f"stop {sym} {cur_stop:.2f} -> {d.desired_stop:.2f}",
"stop_moved", {"from": cur_stop, "to": d.desired_stop}) "stop_moved", {"from": cur_stop, "to": d.desired_stop})
actions.append(f"{sym} stop->{d.desired_stop:.2f}") actions.append(f"{sym} stop->{d.desired_stop:.2f}")
+4
View File
@@ -180,6 +180,10 @@ class PgConnection:
# at a known LAN endpoint, try the configured host first and then the alternate, so the # at a known LAN endpoint, try the configured host first and then the alternate, so the
# loop self-heals on boot regardless of the current tunnel state (this is the recurring # loop self-heals on boot regardless of the current tunnel state (this is the recurring
# crash-loop landmine). "connection refused" returns instantly, so the fallback is cheap. # crash-loop landmine). "connection refused" returns instantly, so the fallback is cheap.
# _LAN_DB_HOSTS is the GATE (superset, incl. localhost): only a DSN pointing at one of
# these gets host-swapped, so a non-LAN DSN (e.g. a cloud RDS host) is never redirected
# to the wrong database. _LAN_DB_FALLBACKS is the TRY-ORDER (the two real endpoints).
# Keep the gate a superset of the fallbacks.
_LAN_DB_HOSTS = ("127.0.0.1", "localhost", "192.168.1.5") _LAN_DB_HOSTS = ("127.0.0.1", "localhost", "192.168.1.5")
_LAN_DB_FALLBACKS = ("192.168.1.5", "127.0.0.1") _LAN_DB_FALLBACKS = ("192.168.1.5", "127.0.0.1")
+5 -1
View File
@@ -127,7 +127,9 @@ class ExitConfig:
catastrophe_pct=f("TRADING_CATASTROPHE_STOP_PCT", "0.10"), catastrophe_pct=f("TRADING_CATASTROPHE_STOP_PCT", "0.10"),
catastrophe_risk_mult=f("TRADING_CATASTROPHE_RISK_MULT", "1.5"), catastrophe_risk_mult=f("TRADING_CATASTROPHE_RISK_MULT", "1.5"),
enforce_stop_grace_passes=int(f("TRADING_ENFORCE_STOP_GRACE_PASSES", "2")), enforce_stop_grace_passes=int(f("TRADING_ENFORCE_STOP_GRACE_PASSES", "2")),
default_trail_r_pct=f("TRADING_EXIT_DEFAULT_TRAIL_R_PCT", "0.10"), # clamp to [0, 1): a synthetic risk distance must be a positive fraction of
# entry below it; >=1 or negative would put the synthetic stop at/above entry.
default_trail_r_pct=min(max(f("TRADING_EXIT_DEFAULT_TRAIL_R_PCT", "0.10"), 0.0), 0.95),
) )
def describe(self) -> str: def describe(self) -> str:
@@ -152,6 +154,8 @@ class ExitConfig:
parts.append(f"catastrophe@-{self.catastrophe_pct:.0%}/{self.catastrophe_risk_mult:g}xR") parts.append(f"catastrophe@-{self.catastrophe_pct:.0%}/{self.catastrophe_risk_mult:g}xR")
if self.enforce_stop_grace_passes: if self.enforce_stop_grace_passes:
parts.append(f"enforce-stop({self.enforce_stop_grace_passes}p)") parts.append(f"enforce-stop({self.enforce_stop_grace_passes}p)")
if self.default_trail_r_pct:
parts.append(f"synth-trail {self.default_trail_r_pct:.0%}")
return ", ".join(parts) return ", ".join(parts)