Files
momentum-system/tradier_broker/factory.py
T
momentum devandClaude Fable 5 8c62804808 feat: broker-aware factory for reconcile/journal/health tools
eod_reconcile, backfill_journal and the admin health block were hardwired
to AlpacaPaperClient — on a BROKER=tradier stack they would silently
reconcile/report against the wrong broker. make_trading_client() honors
BROKER env with no silent cross-broker fallback (missing Tradier keys =>
skip with reason, never fall back to Alpaca). Admin UI shows the active
broker label; JSON key stays 'alpaca' for compatibility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:13:01 -05:00

47 lines
1.9 KiB
Python

"""Broker-aware trading-client factory.
Single place that honors BROKER env for every standalone tool that talks to
the EXECUTION broker (eod_reconcile, backfill_journal, admin health). The
live loop wires the same logic inline in run_live_paper (with its own boot
messaging); both must agree on the BROKER=tradier contract.
No silent cross-broker fallback: if BROKER=tradier but Tradier keys are
missing, callers get (None, reason) — reconciling or journaling against the
WRONG broker is worse than skipping.
"""
from __future__ import annotations
import os
from alpaca_paper.client import AlpacaPaperClient
from alpaca_paper.settings import AlpacaPaperSettings
from tradier_broker.client import TradierClient
from tradier_broker.settings import TradierSettings
def make_trading_client(data_client: AlpacaPaperClient | None = None):
"""Return (client, label) for the active execution broker, or
(None, reason) when its keys are missing. `label` is human-readable
('alpaca-paper', 'tradier-sandbox', 'tradier-LIVE')."""
broker = os.environ.get("BROKER", "alpaca").strip().lower()
alpaca = AlpacaPaperSettings.from_env()
if data_client is None and alpaca.is_configured:
data_client = AlpacaPaperClient(alpaca)
if broker == "tradier":
ts = TradierSettings.from_env()
if not ts.is_configured:
return None, ("BROKER=tradier but TRADIER_ACCESS_TOKEN/"
"TRADIER_ACCOUNT_ID missing")
label = "tradier-sandbox" if ts.is_sandbox else "tradier-LIVE"
return TradierClient(ts, data_client=data_client), label
if not alpaca.is_configured:
return None, "no Alpaca keys"
# empty APCA_API_BASE_URL means the client falls back to the paper host —
# never label that LIVE
host = alpaca.trading_base_url or "paper"
label = "alpaca-paper" if "paper" in host else "alpaca-LIVE"
return (data_client or AlpacaPaperClient(alpaca)), label