fix: mark Tradier positions off Alpaca real-time trades, not delayed quotes

Exit manager reads position current_price for breakeven/trail ratchets and
the stop-breach guard; Tradier sandbox quotes are 15-min delayed, so marks
lagged reality. Prefer the Alpaca data client's latest trades (real-time),
fall back to Tradier /markets/quotes per missing symbol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-27 17:39:46 -05:00
1 parent 8c62804808
commit bd7183c861
1 file changed
+24 -10
+24 -10
View File
@@ -339,18 +339,32 @@ class TradierClient:
raw = _one_or_many(payload.get("positions"), "position")
if not raw:
return []
# Tradier positions carry no mark — price them off /markets/quotes
# Tradier positions carry no mark. Price them off the Alpaca data
# client's REAL-TIME trades when available — the exit manager's
# breakeven/trail logic reads current_price, and Tradier sandbox
# quotes are 15-min delayed (stale marks = lagging trails). Tradier
# /markets/quotes is the fallback (real-time on funded live accounts).
symbols = sorted({str(p["symbol"]) for p in raw if p.get("symbol")})
lasts: dict[str, float] = {}
try:
qp = self._request("GET", "/markets/quotes",
params={"symbols": ",".join(symbols)}) or {}
for q in _one_or_many(qp.get("quotes"), "quote"):
last = q.get("last")
if q.get("symbol") and last is not None:
lasts[q["symbol"]] = float(last)
except Exception: # noqa: BLE001 — quotes down: positions still report, unmarked
logger.warning("tradier quotes lookup failed; positions unmarked")
if self.data_client is not None:
try:
for sym, t in (self.data_client.get_latest_trades(symbols) or {}).items():
px = t.get("p")
if px is not None:
lasts[sym] = float(px)
except Exception: # noqa: BLE001 — fall through to Tradier quotes
logger.warning("alpaca latest-trades mark failed; falling back to tradier quotes")
missing = [s for s in symbols if s not in lasts]
if missing:
try:
qp = self._request("GET", "/markets/quotes",
params={"symbols": ",".join(missing)}) or {}
for q in _one_or_many(qp.get("quotes"), "quote"):
last = q.get("last")
if q.get("symbol") and last is not None:
lasts[q["symbol"]] = float(last)
except Exception: # noqa: BLE001 — quotes down: positions still report, unmarked
logger.warning("tradier quotes lookup failed; positions unmarked")
return [translate_position(p, lasts.get(str(p.get("symbol") or ""))) for p in raw]
def get_positions(self, fresh: bool = False) -> list[dict]: