Files
momentum devandClaude Fable 5 1cefb51c63 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>
2026-07-26 11:01:50 -05:00

40 lines
1.2 KiB
Python

"""Tradier brokerage settings (sandbox by default — live is an explicit opt-in)."""
from __future__ import annotations
import os
from dataclasses import dataclass
SANDBOX_BASE_URL = "https://sandbox.tradier.com/v1"
LIVE_BASE_URL = "https://api.tradier.com/v1"
@dataclass(frozen=True)
class TradierSettings:
access_token: str = ""
account_id: str = ""
base_url: str = SANDBOX_BASE_URL
@classmethod
def from_env(cls, env: dict[str, str] | None = None) -> "TradierSettings":
values = dict(os.environ)
if env is not None:
values.update(env)
env_name = values.get("TRADIER_ENV", "sandbox").strip().lower()
base = values.get("TRADIER_BASE_URL") or (
LIVE_BASE_URL if env_name == "live" else SANDBOX_BASE_URL
)
return cls(
access_token=values.get("TRADIER_ACCESS_TOKEN", "").strip(),
account_id=values.get("TRADIER_ACCOUNT_ID", "").strip(),
base_url=base.rstrip("/"),
)
@property
def is_configured(self) -> bool:
return bool(self.access_token and self.account_id)
@property
def is_sandbox(self) -> bool:
return "sandbox" in self.base_url