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>
40 lines
1.2 KiB
Python
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
|