From 27013bb5338b750841939c243dfe1dce228a0ecd Mon Sep 17 00:00:00 2001 From: Stefan Jansen Date: Tue, 3 Mar 2026 18:08:48 -0500 Subject: [PATCH] docs: rewrite README with Alpaca, data feeds, full risk config, safety system --- README.md | 186 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 150 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 43318c2..4362563 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,13 @@ Each library addresses a distinct stage: data infrastructure, feature engineerin Deploying a backtested strategy to live markets requires careful handling of async broker connections, risk limits, and testing infrastructure. ml4t-live provides: -- The same Strategy class used in ml4t-backtest works unchanged -- Shadow mode for testing without placing real orders -- Position and order limits with rate limiting -- Interactive Brokers integration via TWS/Gateway -- Thread-safe bridging between sync strategies and async brokers +- The same Strategy class used in ml4t-backtest works unchanged in production +- Two broker integrations: Interactive Brokers (TWS/Gateway) and Alpaca (stocks + crypto) +- Six data feeds: Alpaca, IB, Databento, CCXT (100+ crypto exchanges), OKX +- Shadow mode for testing without placing real orders (VirtualPortfolio tracking) +- 16-parameter risk configuration: position limits, order limits, loss limits, price protection +- Kill switch with crash-safe state persistence (atomic JSON writes) +- Async architecture with thread-safe sync bridge for strategy callbacks The goal is gradual deployment: shadow mode first, then paper trading, then live with small positions. @@ -38,8 +40,9 @@ pip install ml4t-live ```python from ml4t.backtest import Strategy, OrderSide -from ml4t.live import LiveEngine, LiveRiskConfig -from ml4t.live.brokers.ib import IBBroker +from ml4t.live import LiveEngine, LiveRiskConfig, SafeBroker +from ml4t.live.brokers.alpaca import AlpacaBroker +from ml4t.live.feeds.alpaca_feed import AlpacaDataFeed import asyncio # Same strategy class from backtesting @@ -49,49 +52,47 @@ class MyStrategy(Strategy): broker.submit_order('SPY', 10, side=OrderSide.BUY) async def main(): - broker = IBBroker(port=7497) # Paper trading port - await broker.connect() + broker = AlpacaBroker(api_key="...", secret_key="...", paper=True) + feed = AlpacaDataFeed(api_key="...", secret_key="...", symbols=["SPY"]) config = LiveRiskConfig( shadow_mode=True, # No real orders max_position_value=50_000, ) - engine = LiveEngine(broker, MyStrategy(), config) + safe = SafeBroker(broker, config) + + engine = LiveEngine(MyStrategy(), safe, feed) + await engine.connect() try: await engine.run() finally: - await broker.disconnect() + await engine.stop() asyncio.run(main()) ``` -Shadow mode output: +## Broker Integrations -``` -Bar 1: SPY close = $450.02 - -> Buying 10 shares of SPY (VIRTUAL - shadow mode) -Virtual position: +10 SPY @ $450.02 -No real orders placed (shadow mode active) -``` +### Alpaca -## Risk Configuration +Stocks and crypto with paper trading by default: ```python -config = LiveRiskConfig( - shadow_mode=True, # Virtual orders only - max_position_value=50_000, # Per-position limit - max_positions=10, # Total positions - max_order_value=10_000, # Per-order limit - max_orders_per_minute=10, # Rate limiting - max_daily_loss=5_000, # Stop trading limit +from ml4t.live.brokers.alpaca import AlpacaBroker + +broker = AlpacaBroker( + api_key="...", + secret_key="...", + paper=True, # Paper trading (default) ) +await broker.connect() ``` -## Broker Integration - ### Interactive Brokers +Full market access via TWS or IB Gateway: + ```python from ml4t.live.brokers.ib import IBBroker @@ -100,7 +101,6 @@ broker = IBBroker(port=7497) # TWS paper port await broker.connect() print(f"Connected: {broker.is_connected}") -print(f"Account: {broker.account_id}") ``` Requirements: @@ -108,10 +108,112 @@ Requirements: - API connections enabled in TWS settings - Paper trading account for initial testing +## Data Feeds + +| Feed | Source | Coverage | +|------|--------|----------| +| `AlpacaDataFeed` | Alpaca | US stocks + crypto, real-time bars/quotes/trades | +| `IBDataFeed` | Interactive Brokers | Multi-asset tick-by-tick data | +| `DataBentoFeed` | Databento | Historical replay + real-time streaming | +| `CryptoFeed` | CCXT | 100+ crypto exchanges (Binance, Coinbase, Kraken, ...) | +| `OKXFundingFeed` | OKX | Perpetual swaps with funding rates | +| `BarAggregator` | Any feed | Multi-feed aggregation + bar assembly | + +```python +from ml4t.live.feeds.alpaca_feed import AlpacaDataFeed +from ml4t.live.feeds.crypto_feed import CryptoFeed + +# Stock + crypto via Alpaca +feed = AlpacaDataFeed( + api_key="...", secret_key="...", + symbols=["AAPL", "BTC/USD"], + feed="iex", # "iex" (free) or "sip" (premium) +) + +# Crypto via CCXT (any of 100+ exchanges) +feed = CryptoFeed( + exchange="binance", + symbols=["BTC/USDT", "ETH/USDT"], + timeframe="1m", +) +``` + +## Risk Configuration + +`LiveRiskConfig` controls all safety parameters. Wrap any broker with `SafeBroker` to enforce them: + +```python +from ml4t.live import LiveRiskConfig, SafeBroker + +config = LiveRiskConfig( + # Shadow mode + shadow_mode=True, # Virtual orders only (no real execution) + + # Position limits + max_position_value=50_000, # Max $ per position + max_position_shares=1000, # Max shares per position + max_total_exposure=200_000, # Max total $ across all positions + max_positions=20, # Max number of positions + + # Order limits + max_order_value=10_000, # Max $ per order + max_order_shares=500, # Max shares per order + max_orders_per_minute=10, # Rate limiting + + # Loss limits + max_daily_loss=5_000, # Stop trading if exceeded + max_drawdown_pct=0.05, # Stop if 5% drawdown + + # Price protection + max_price_deviation_pct=0.05, # Fat finger: reject if >5% from market + max_data_staleness_seconds=60, # Reject if data older than 60s + dedup_window_seconds=1.0, # Block duplicate orders within 1s + + # Asset restrictions + allowed_assets={"SPY", "QQQ"}, # Whitelist (empty = allow all) +) + +safe_broker = SafeBroker(broker, config) +``` + +## Safety System + +### Kill Switch + +When drawdown exceeds `max_drawdown_pct`, the kill switch activates and blocks all new orders. The state persists across process restarts: + +```python +config = LiveRiskConfig( + kill_switch_enabled=True, + max_drawdown_pct=0.05, + state_file=".ml4t_risk_state.json", # Atomic JSON writes +) +``` + +### Virtual Portfolio + +Shadow mode tracks positions internally without broker interaction: + +```python +from ml4t.live import VirtualPortfolio + +portfolio = VirtualPortfolio(initial_cash=100_000) +# SafeBroker uses this automatically when shadow_mode=True +``` + +### State Persistence + +Risk state survives process crashes via atomic file writes: + +- `daily_loss` - Cumulative daily loss +- `orders_placed` - Orders placed today +- `high_water_mark` - Session high equity +- `kill_switch_activated` - Persists until manually reset + ## Deployment Progression 1. **Shadow Mode** (1-2 weeks): Verify logic without real orders -2. **Paper Trading** (2-4 weeks): Test with IB paper account +2. **Paper Trading** (2-4 weeks): Test with paper account 3. **Live Micro** (1-2 weeks): Small positions ($100-500) 4. **Live Small** (ongoing): Gradual size increase @@ -133,15 +235,26 @@ result = Engine(feed, MyStrategy(), config).run() # Live from ml4t.live import LiveEngine -await LiveEngine(broker, MyStrategy(), risk_config).run() +await LiveEngine(MyStrategy(), safe_broker, live_feed).run() ``` +## Documentation + +- [Installation](docs/getting-started/installation.md) — setup instructions +- [Quick Start](docs/getting-started/quickstart.md) — first live strategy +- [Brokers](docs/user-guide/brokers.md) — IB and Alpaca setup +- [Data Feeds](docs/user-guide/feeds.md) — 6 feed types +- [Risk Management](docs/user-guide/risk.md) — LiveRiskConfig and SafeBroker + ## Technical Characteristics -- **Async/sync bridge**: Sync strategy callbacks work with async broker connections -- **Thread-safe**: Safe to use across multiple event loops +- **Async/sync bridge**: Sync strategy callbacks work with async broker connections via `ThreadSafeBrokerWrapper` +- **Thread-safe**: Strategy runs in worker thread, broker I/O on async event loop +- **Protocol-based**: `BrokerProtocol`, `AsyncBrokerProtocol`, `DataFeedProtocol` for extensibility - **Virtual portfolio**: Shadow mode tracks positions without broker interaction +- **Atomic state**: Risk state persisted via POSIX-atomic file writes (crash-safe) - **Rate limiting**: Built-in protection against order flooding +- **Type-safe**: Full type annotations throughout ## Related Libraries @@ -153,7 +266,7 @@ await LiveEngine(broker, MyStrategy(), risk_config).run() ## Development ```bash -git clone https://github.com/applied-ai/ml4t-live.git +git clone https://github.com/ml4t/ml4t-live.git cd ml4t-live uv sync uv run pytest tests/ -q @@ -164,10 +277,11 @@ uv run ty check This library is designed for paper trading and educational purposes. When transitioning to live trading: -- Always start with shadow_mode=True +- Always start with `shadow_mode=True` - Set conservative position and order limits +- Enable `kill_switch_enabled=True` with a reasonable `max_drawdown_pct` - Monitor virtual vs real positions carefully -- Use stop-losses and position limits +- Use the deployment progression above ## License