# Horizon SDK

Prediction market trading SDK. Rust core (PyO3) + Python interface.

## Build

```bash
maturin develop --features skip-auth   # build + install
cargo check                            # Rust compilation check
pytest tests/                          # Python test suite (~960+ tests)
```

## Architecture

- **Rust core** (`src/`): types, risk pipeline, orders, positions, paper exchange, Polymarket/Kalshi clients, feed system
- **Python SDK** (`python/horizon/`): `hz.run()`, pipeline composition, TUI dashboard, CLI, MCP server
- **No IPC**: direct PyO3 function calls between Python and Rust

### Key Files

| File | Purpose |
|------|---------|
| `src/types.rs` | Core types: Side, OrderSide, Market, Quote, OrderRequest, Order, Position, Fill, RiskConfig |
| `src/risk.rs` | 8-point risk pipeline |
| `src/engine.rs` | Engine orchestrator, owns risk + orders + positions + exchange + feed manager |
| `src/exchanges/paper.rs` | Paper exchange with tick-based matching |
| `src/exchanges/polymarket.rs` | Polymarket CLOB client |
| `src/exchanges/kalshi.rs` | Kalshi REST client |
| `src/feeds/mod.rs` | FeedSnapshot, FeedConfig enum, FeedManager |
| `src/feeds/chainlink.rs` | Chainlink on-chain price oracle via JSON-RPC eth_call |
| `src/feeds/binance.rs` | Binance WebSocket trade stream |
| `src/feeds/polymarket_book.rs` | Polymarket orderbook WebSocket |
| `src/feeds/kalshi_book.rs` | Kalshi orderbook polling |
| `src/feeds/rest_json_path.rs` | Enhanced REST feed with JSON path extraction |
| `python/horizon/strategy.py` | `hz.run()`, pipeline composition, feed wiring |
| `python/horizon/feeds.py` | Feed dataclasses (BinanceWS, ChainlinkFeed, etc.) |
| `python/horizon/mcp_server.py` | MCP server (44 tools, 2 resources) |
| `python/horizon/tools.py` | Shared tool functions for MCP server |
| `python/horizon/__init__.py` | Public API re-exports |

## Conventions

- **f64 at PyO3 boundary** (not rust_decimal)
- **Enums**: `#[pyclass(eq, eq_int, frozen, hash)]`
- **Feed pattern**: async function following `manifold.rs` pattern (loop + tokio::select! + metrics)
- **New feeds** use `config_json` kwarg: `engine.start_feed(name, feed_type, config_json='...')`
- **FeedConfig enum**: 10 variants (BinanceWS, PolymarketBook, KalshiBook, REST, PredictIt, Manifold, ESPN, NWS, RESTJsonPath, Chainlink)
- **Paper order IDs**: `p1`, `p2`... (AtomicU64)
- **Position has no `#[new]`** — tests build positions via `engine.process_fill()`

## Adding a New Feed

1. Create `src/feeds/<name>.rs` with `run_<name>_feed()` async function
2. Add `pub mod <name>;` to `src/feeds/mod.rs`
3. Add variant to `FeedConfig` enum in `src/feeds/mod.rs`
4. Add dispatch arm in `FeedManager::start_feed()` in `src/feeds/mod.rs`
5. Add `"<name>"` match arm in `Engine::start_feed()` in `src/engine.rs`
6. Add dataclass to `python/horizon/feeds.py`
7. Add `elif isinstance(feed_config, <Name>Feed)` to `_start_feeds()` in `python/horizon/strategy.py`
8. Add to imports/`__all__` in `python/horizon/__init__.py`
9. Add tests to `tests/test_new_feeds.py`
10. Add docs section to `docs/feeds.mdx`

## Testing

```bash
cargo check --tests          # Rust type-check (can't link PyO3 cdylib directly)
maturin develop              # build + install
pytest tests/ -v             # full suite
pytest tests/test_new_feeds.py -v  # feed tests only
```
