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:
1 parent
8c62804808
commit
bd7183c861
1 file changed
+24
-10
+24
-10
@@ -339,18 +339,32 @@ class TradierClient:
|
|||||||
raw = _one_or_many(payload.get("positions"), "position")
|
raw = _one_or_many(payload.get("positions"), "position")
|
||||||
if not raw:
|
if not raw:
|
||||||
return []
|
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")})
|
symbols = sorted({str(p["symbol"]) for p in raw if p.get("symbol")})
|
||||||
lasts: dict[str, float] = {}
|
lasts: dict[str, float] = {}
|
||||||
try:
|
if self.data_client is not None:
|
||||||
qp = self._request("GET", "/markets/quotes",
|
try:
|
||||||
params={"symbols": ",".join(symbols)}) or {}
|
for sym, t in (self.data_client.get_latest_trades(symbols) or {}).items():
|
||||||
for q in _one_or_many(qp.get("quotes"), "quote"):
|
px = t.get("p")
|
||||||
last = q.get("last")
|
if px is not None:
|
||||||
if q.get("symbol") and last is not None:
|
lasts[sym] = float(px)
|
||||||
lasts[q["symbol"]] = float(last)
|
except Exception: # noqa: BLE001 — fall through to Tradier quotes
|
||||||
except Exception: # noqa: BLE001 — quotes down: positions still report, unmarked
|
logger.warning("alpaca latest-trades mark failed; falling back to tradier quotes")
|
||||||
logger.warning("tradier quotes lookup failed; positions unmarked")
|
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]
|
return [translate_position(p, lasts.get(str(p.get("symbol") or ""))) for p in raw]
|
||||||
|
|
||||||
def get_positions(self, fresh: bool = False) -> list[dict]:
|
def get_positions(self, fresh: bool = False) -> list[dict]:
|
||||||
|
|||||||
Reference in new issue
Block a user