Broker setup
Connecting a broker is optional — the free tier works fine on public Yahoo Finance data. Adding E*Trade unlocks live positions. Adding Tradier gives you better market data + Greeks. Both tokens live in your local .env file; they never reach our servers.
E*Trade
E*Trade uses OAuth 1.0a with two modes: sandbox (fake data, safe to experiment) and production (your real account). Always start in sandbox.
Step 1 — get your consumer key/secret
- Log into E*Trade.
- Go to Customer Service → Technical Support → Developer Platform.
- Apply for API access. Approval is typically 1-2 business days.
- Once approved you'll receive a Consumer Key and Consumer Secret for both sandbox and production.
Step 2 — add them to .env
# E*Trade API (Phase 1 — read-only by default)
ETRADE_CONSUMER_KEY=your_consumer_key_here
ETRADE_CONSUMER_SECRET=your_consumer_secret_here
ETRADE_MODE=sandbox # or "production"
ETRADE_ACCOUNT_ID=your_account_id # find in E*Trade portfolio URL
Step 3 — first authorization flow
Restart control_server.py. The first time you make an E*Trade call the dashboard opens a browser window asking you to approve access. After approval, tokens are cached in etrade_tokens.json (gitignored). The access token lasts until midnight ET; Swing Deck auto-refreshes.
ETRADE_ACCOUNT_ID to the specific account you want Swing Deck to read. Mismatched account ID is the #1 cause of "positions missing from dashboard".
Tradier
Tradier gives you better quote latency and full options chains (including Greeks). You don't need a Tradier brokerage account — a free developer account works fine.
- Sign up at tradier.com.
- Go to dash.tradier.com/settings/api and generate an access token.
- Add to
.env:
# Tradier — market data + Greeks (free tier works)
TRADIER_MODE=sandbox # or "prod"
TRADIER_ACCESS_TOKEN=your_bearer_token
Tradier is used for the options barbell scanner (CSP, covered call) and to upgrade quote cadence from 60s to 10s.
Data providers
The audit engine has a tiered data cascade:
| Tier | Providers | When used |
|---|---|---|
| 1 (primary) | Finnhub, MarketAux, News API | Price + news + sentiment. Keys are free-tier (60-250 req/day each). |
| 2 (secondary) | Alpha Vantage, Google RSS, Stooq | Falls through when Tier 1 quota exhausted. |
| 3 (fallback) | yfinance | No key required. Always available. Rate-limited by Yahoo. |
# Data provider keys (all optional — Tier 3 works without)
FINNHUB_API_KEY=your_key_here
MARKETAUX_API_KEY=your_key_here
NEWS_API_KEY=your_key_here
ALPHA_VANTAGE_API_KEY=your_key_here
Raise-Stop automation
The headline feature. Swing Deck can move your stop-loss orders up on E*Trade when a position hits a higher ATR-based trailing level. It never moves stops down. Ever.
Safety model — 4 guards
- Preview-confirm — every order goes through E*Trade's preview endpoint first. We compare the preview's computed stop against our logged stop. If they mismatch by more than $0.02, the place call is refused.
- Rate limiter — max 5 order modifications per minute per account. Prevents runaway loops.
- Staleness check — if the broker's view of the position is >300 seconds old, don't place. Better to miss a stop raise than act on stale data.
- Circuit breaker — 3 consecutive API failures → broker-writes auto-disabled for 30 minutes. Requires manual reset via
POST /broker/circuit/reset.
Enablement checklist
dryrun mode (preview only, no order placed). Do not flip to live until you've run 1 full week in dryrun with clean audit logs.
In .env:
# ── Broker writes (Raise-Stop automation) ─────
# Master kill switch — leave false until you are 100% ready
BROKER_WRITES_ENABLED=false
# dryrun = preview only (safe, no orders placed)
# live = preview + place (real order modifications)
BROKER_MODE=dryrun
# Safety throttles — don't change unless you know why
BROKER_MAX_ORDERS_PER_MIN=5
BROKER_STALE_MAX_SECONDS=300
BROKER_PARITY_TOLERANCE=0.02
BROKER_CIRCUIT_TRIP_COUNT=3
BROKER_CIRCUIT_COOLDOWN=1800
Verifying dryrun logs
Open broker_orders.log (created after the first run). Each line records one of:
preview_sent/preview_ok— E*Trade accepted our simulated raiseplace_skipped_dryrun— we did NOT place the order (because mode=dryrun)place_sent/place_ok— only appears inlivemodepreview_err/place_err— counts against the circuit breaker
Look for 1 week of clean preview_oks before flipping to live. Rate-limiter trips or parity mismatches → pause and investigate.