Files
momentum-system/docs/TRADIER-FALLBACK.md

3.9 KiB

Tradier broker fallback

Redundancy path if the Alpaca live account stays blocked. tradier_broker/ mirrors every trading call the live loop makes on AlpacaPaperClient — same method names, signatures, and Alpaca-shaped return dicts — so trading_execution, runtime/exit_manager, runtime/flatten, alpaca_paper.execution and alpaca_paper.sync run unchanged.

Switch (config only)

BROKER=tradier
TRADIER_ACCESS_TOKEN=...       # sandbox or live token
TRADIER_ACCOUNT_ID=...         # e.g. VA71549758
TRADIER_ENV=sandbox            # 'live' for real money — explicit opt-in

BROKER unset/alpaca = today's behavior, byte-for-byte. Alpaca keys stay required either way: market data (bars, screeners, quotes, news) always stays on Alpaca — the Tradier client only replaces order execution and account/position state, delegating all data calls to the Alpaca client.

Verify before trusting

python3 -m tradier_broker.smoke                   # balances/clock/positions/orders
python3 -m tradier_broker.smoke --preview AAPL    # broker-validates bracket form, never submits
python3 -m tradier_broker.smoke --place-cancel AAPL  # full submit -> tag lookup -> cancel

Smoke refuses order exercises against the live base URL.

Mapping notes

  • Alpaca bracket -> Tradier OTOCO (entry + OCO take-profit limit / stop); oto -> OTO; standalone gtc stop sells map directly.
  • client_order_id -> Tradier tag (lost-submit reconcile works via tag lookup).
  • Tradier positions carry no mark; get_positions prices them off /markets/quotes (sandbox quotes are 15-min delayed — fine for redundancy checks, and live tokens get real-time).
  • replace_order maps to PUT (moves stop/limit without cancelling — OCO stays intact); Tradier cannot change qty on a working order (raises, cancel+resubmit).
  • get_orders is session-scoped at Tradier (today's orders) and status/symbol filters are client-side; the live loop only queries today + held symbols, so behavior matches.

Parallel shakeout stack (TrueNAS)

deploy/docker/truenas-compose.tradier.yaml is the committed template; the filled copy lives (git-excluded) at truenas-compose.tradier.local.yaml. Runs beside the paper stack, fully isolated:

  • own postgres (host 5434, fresh pgdata-tradier volume), own dashboard (host 8768), loop with BROKER=tradier against the sandbox account
  • market data still Alpaca; Telegram + deadman OFF (main stack keeps alerts)
  • main data dir mounted read-only — inherits nightly learned_params.json / overrides.env, cannot clobber them; if a service ever crashes on a read-only-filesystem error, switch that mount to a writable copy
  • fresh DB starts empty (knobs at code defaults). For research-baseline + knob parity with the paper stack, reuse the live-staging playbook: ./bootstrap_live_db.sh <nightly-backup.sql.gz> <tradier-postgres-container>
  • fresh DB also lacks the research tables the dashboard polls (experiment_verdicts etc. — postgres logs relation ... does not exist on every poll). Harmless, but either bootstrap as above or create the missing tables schema-only; done by hand on the first deploy 2026-07-27 (18 tables), plus a copy of the paper stack's config_overrides rows (75 knobs — without them the stack runs factory defaults and entries pile up in the manual approval queue)
  • needs an image built from >= 1cefb51 (tradier_broker/ in image)
  • sandbox fills are simulated and can be slow or absent — an order parked in pending_new is usually the sandbox, not the code

Before flipping live money to Tradier

  1. Rotate the sandbox token (it transited chat) and mint a live token.
  2. Run a full paper week on sandbox with BROKER=tradier in the paper stack.
  3. Re-run the naked-stop drill from docs/LIVE-CUTOVER.md — leg-stripping semantics differ per broker; verify a filled OTOCO's stop leg survives an entry-order cancel attempt.