Files
momentum-system/alpaca_paper/settings.py
T
momentum devandClaude Opus 4.8 e74539d449 Resiliency + risk + monitoring: SIP backfill, freshness watchdog, gross cap, naked alarm
From the multi-agent audit (ship-now, low-overfit-risk cluster):

INGESTION RESILIENCY
- client._request: 429/5xx now retry with Retry-After + exponential backoff
  (previously hard-failed, feeding silent-stale ingest).
- SIP backfill feed (settings.backfill_feed='sip'): live realtime stays IEX, but
  backtest/backfill ingestion uses the full consolidated tape — IEX sees only
  ~2-5% of true volume (verified: AAPL 22,797 vs 452,704), which was silently
  corrupting every rvol/$-volume decision and backtest. eod_replay wired to it.
- step_ingest freshness watchdog: emits a STALE event + board note when the
  newest bar is older than INGEST_STALE_SECONDS during RTH (45% of passes
  historically returned 0 rows with no error). Optional hard arm-block
  (INGEST_STALE_BLOCK, off by default).

RISK
- Portfolio gross-notional cap (TRADING_MAX_GROSS_NOTIONAL_PCT=0.60): both sizing
  paths now cap each entry to the remaining gross budget, so 6 x 16% can't pile
  the book into ~96% gross of correlated small-caps. Blocks/shrinks late entries.
- Fixed the duplicate TRADING_MAX_CONCURRENT_POSITIONS in .env.example.

MONITORING (UI)
- Naked/unprotected-position alarm: _unprotected_positions joins the latest
  position + order snapshots (order sync now flattens legs) and the TUI board
  shows a red UNPROTECTED banner the instant a held long lacks stop coverage —
  the exact blind spot when APWC/NOWL went naked. Verified it flags today's
  3 overnight positions.

Tests: gross-notional cap blocks when budget full; 50 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:18:36 -04:00

48 lines
1.7 KiB
Python

"""Alpaca paper-trading settings."""
from __future__ import annotations
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class AlpacaPaperSettings:
api_key: str = ""
secret_key: str = ""
trading_base_url: str = "https://paper-api.alpaca.markets"
data_base_url: str = "https://data.alpaca.markets"
# live realtime feed. IEX is free + realtime but THIN (~2-5% of true volume),
# which corrupts every rvol / $-volume decision computed off it.
feed: str = "iex"
# historical/backfill feed. SIP is the consolidated tape (full volume) — use
# it for backtests/backfill so rvol & $-volume reflect reality. SIP realtime
# has a ~15-min embargo on free plans, which is why live stays on `feed`.
backfill_feed: str = "sip"
@classmethod
def from_env(cls, env: dict[str, str] | None = None) -> "AlpacaPaperSettings":
values = dict(os.environ)
if env is not None:
values.update(env)
api_key = values.get("ALPACA_API_KEY") or values.get("APCA_API_KEY_ID") or ""
secret = (
values.get("ALPACA_SECRET_KEY")
or values.get("APCA_API_SECRET_KEY")
or ""
)
base = values.get("APCA_API_BASE_URL", "https://paper-api.alpaca.markets")
feed = values.get("ALPACA_DATA_FEED", "iex")
backfill_feed = values.get("ALPACA_BACKFILL_FEED", "sip")
return cls(
api_key=api_key,
secret_key=secret,
trading_base_url=base.rstrip("/"),
feed=feed,
backfill_feed=backfill_feed,
)
@property
def is_configured(self) -> bool:
return bool(self.api_key and self.secret_key)