Fix naked-stop regression: speed cache made the guard strip live brackets

The get_positions TTL cache (added for the speed fix) silently broke the
naked-stop guard: _broker_held_symbols read 2.5s-stale positions, so a
just-filled entry wasn't seen as held, the guard thought it was unfilled, and
cancelled its bracket on a price dip -> NAKED position (APWC bled -12.8%
unprotected today). Fixes:
- client.get_positions(fresh=True) bypasses the cache; the guard uses it.
- entry_grace_seconds (default 5): never cancel an entry within N seconds of
  arming, so a marketable fill can confirm before the bracket could be stripped.
Regression test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Opus 4.8 committed 2026-06-18 10:20:00 -04:00
1 parent 27b0748b71
commit 1cd82de6a8
5 files changed
+43 -4

No files matched your search

+8 -1
View File
@@ -151,7 +151,14 @@ class AlpacaPaperClient:
def get_account(self) -> dict:
return self._cached("account", lambda: self._trading("GET", "/account"))
def get_positions(self) -> list[dict]:
def get_positions(self, fresh: bool = False) -> list[dict]:
# fresh=True bypasses the TTL cache — REQUIRED for the naked-stop guard,
# which must never decide an order is unfilled off a stale snapshot.
if fresh:
val = self._trading("GET", "/positions")
with self._cache_lock:
self._cache["positions"] = (val, time.monotonic())
return val
return self._cached("positions", lambda: self._trading("GET", "/positions"))
def get_orders(