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>
47 lines
1.9 KiB
Python
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
|