An event-driven algorithmic trading framework with live paper-trading on Alpaca Markets.
Paper trading only. This system will refuse to boot if pointed at the live Alpaca endpoint.
cp .env.example .env # add your Alpaca paper API keys
make install # uv sync all dependencies
make backtest # run mean-reversion backtest- Add Alpaca paper API keys to
.env make up— starts the live runner + Streamlit dashboard- Open http://localhost:8501
Historical mode:
yfinance → Parquet → HistoricalDataHandler → EventRunner → Reports
Live mode:
Alpaca WebSocket → AlpacaStreamHandler
│
▼
EventRunner (journal-backed)
│
Strategy (MeanReversion / XsMomentum / Pairs / VolBreakout)
│
Portfolio (position tracking + Kelly sizing)
│
RiskManager (exposure + drawdown gates)
│
AlpacaExecutionHandler (idempotent orders, paper only)
│
Alpaca REST (paper-api.alpaca.markets)
│
Reconciler (every 5 min) + ntfy alerts + DuckDB journal
│
Streamlit Dashboard
Key design choices:
| Decision | Choice | Reason |
|---|---|---|
| Storage | DuckDB + Parquet | Embedded, zero-config, fast analytical queries |
| DataFrames | Polars | 10-50x faster than pandas for large bar data |
| Event journal | DuckDB WAL | Append-only, replay on crash, no orphaned positions |
| Order IDs | sha256(strategy_id:signal_uuid:attempt)[:32] |
Deterministic, idempotent on retry |
| Sizing | Half-Kelly, capped at 5% | Variance of Kelly estimator + path dependency |
| Strategy | Frequency | Expected Sharpe (OOS) |
|---|---|---|
| Cross-Sectional Momentum | Monthly | 0.4 – 0.7 |
| Mean Reversion (Bollinger %B) | Daily | 0.3 – 0.6 |
| Pairs Trading (Engle-Granger) | Daily | 0.3 – 0.5 |
| Volatility Breakout (ORB) | Intraday | 0.2 – 0.5 |
# Backtest each strategy
make backtest # mean_reversion (default)
uv run python -m trader.cli backtest --strategy xs_momentum
uv run python -m trader.cli backtest --strategy pairs_trading
uv run python -m trader.cli backtest --strategy vol_breakout
# Download historical data first
make backfill
# Live paper trading
make upalgo-trader/
├── src/trader/
│ ├── config.py # pydantic-settings, paper-only assertion
│ ├── events/ # Pydantic v2 event types + in-memory queue
│ ├── engine/ # Event loop, DuckDB journal, crash recovery
│ ├── data/ # Historical (Parquet/yfinance) + Alpaca stream
│ ├── strategies/ # 4 classical strategies
│ ├── portfolio/ # Position tracking + Kelly sizing
│ ├── risk/ # Exposure + drawdown gates
│ ├── execution/ # Simulated (backtest) + Alpaca (live)
│ ├── reconcile/ # 5-min broker vs local position diff
│ ├── eval/ # Metrics, walk-forward, deflated Sharpe
│ ├── alerts/ # ntfy push notifications
│ └── app/ # Streamlit dashboard
├── tests/ # unit + integration (≥85% coverage)
├── scripts/backfill.py # yfinance historical downloader
├── docker-compose.yml
└── Makefile
rufflint + formatmypy --stricttype checkingpytest ≥ 85%coverage on engine, strategies, risk, execution- 3 named integration tests: idempotency, crash recovery, reconciliation
- Smoke backtest completes in < 2 minutes (CI gated)
Copy .env.example to .env and fill in:
| Variable | Description |
|---|---|
ALPACA_API_KEY |
Alpaca paper account API key |
ALPACA_SECRET |
Alpaca paper account secret |
ALPACA_BASE_URL |
Must contain paper — enforced at startup |
NTFY_TOPIC |
Your ntfy.sh topic for push alerts |
FRED_API_KEY |
Optional, for VIX data in vol-breakout strategy |
make test # full suite with coverage
make lint # ruff
make typecheck # mypy --strictSee docker-compose.yml. The live-runner service uses supervisord for automatic restart on crash.
For public dashboard: deploy the dashboard service to Render free tier with a caddy reverse proxy. See docs/adr/dashboard_deployment.md.
docs/strategy_book.md— Full methodology, walk-forward results, what didn't workdocs/adr/— Architecture decision records