feat: Tradier broker mirror — config-switch redundancy for Alpaca execution

tradier_broker/ mirrors every trading call the live loop makes on
AlpacaPaperClient (account, clock, positions, orders, submit incl.
bracket->OTOCO, cancel, replace, close) with identical signatures and
Alpaca-shaped returns, so trading_execution / exit_manager / flatten /
execution / sync run unchanged. Market data always delegates to Alpaca.

- BROKER=tradier (+ TRADIER_* env) swaps execution client in run_live_paper;
  default path untouched
- client_order_id -> tag preserves the lost-submit reconcile
- TradierApiError subclasses AlpacaApiError so the executor's
  rejection-vs-transport fork works as-is
- sandbox-verified: balances/clock/positions/orders, OTOCO preview
  (result:true), place -> tag lookup -> cancel round trip
- 18 unit tests (translation + client paths); executor integration checked
- docs/TRADIER-FALLBACK.md switch runbook

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-26 11:01:50 -05:00
1 parent 48d75816b6
commit 1cefb51c63
8 files changed
+960 -1

No files matched your search

+16
View File
@@ -246,6 +246,22 @@ def build_runtime(args: argparse.Namespace) -> dict:
has_keys = alpaca_settings.is_configured
client = AlpacaPaperClient(alpaca_settings) if has_keys else None
# Broker failover: BROKER=tradier swaps the EXECUTION client for the
# Tradier mirror (same surface/shapes); market data stays on Alpaca via
# delegation. Default (unset/alpaca) is untouched.
if os.environ.get("BROKER", "alpaca").strip().lower() == "tradier":
from tradier_broker.client import TradierClient
from tradier_broker.settings import TradierSettings
tradier_settings = TradierSettings.from_env()
if tradier_settings.is_configured:
client = TradierClient(tradier_settings, data_client=client)
print(f"[boot] BROKER=tradier: executing via Tradier "
f"({'SANDBOX' if tradier_settings.is_sandbox else 'LIVE'}), "
f"data via Alpaca", flush=True)
else:
print("[boot] WARNING: BROKER=tradier but TRADIER_ACCESS_TOKEN/"
"TRADIER_ACCOUNT_ID missing — staying on Alpaca", flush=True)
# Datastore is Postgres (DATABASE_URL); WATCHER_EVENT_DB_PATH is a legacy
# fallback the connection layer ignores.
event_db = os.environ.get("WATCHER_EVENT_DB_PATH", "momentum")