---
title: "Horizon Exchanges"
description: "Horizon's exchange model: paper, Polymarket, and Kalshi with multi-exchange support."
icon: "building-columns"
---

Horizon supports three exchange backends that all implement the same `Exchange` trait. You can trade on a single exchange or multiple exchanges simultaneously.

## Supported Exchanges

<CardGroup cols={3}>
  <Card title="Paper" icon="file" href="/exchanges/paper">
    Local simulated exchange with tick-based matching. No credentials needed. Default for development.
  </Card>
  <Card title="Polymarket" icon="chart-line" href="/exchanges/polymarket">
    Largest prediction market on Polygon. EIP-712 signed orders via the CLOB API.
  </Card>
  <Card title="Kalshi" icon="scale-balanced" href="/exchanges/kalshi">
    US-regulated (CFTC) prediction market. REST API with bearer token auth.
  </Card>
</CardGroup>

## Exchange Model

All exchanges implement a common trait:

```rust
trait Exchange {
    fn submit_order(&self, request: &OrderRequest) -> Result<String>;
    fn cancel_order(&self, order_id: &str) -> Result<()>;
    fn cancel_all(&self) -> Result<usize>;
    fn cancel_for_market(&self, market_id: &str) -> Result<usize>;
    fn drain_fills(&self) -> Vec<Fill>;
    fn fetch_positions(&self) -> Result<Vec<Position>>;
}
```

The Engine wraps exchanges in an `ExchangeBackend` enum for dispatch without trait objects:

```
ExchangeBackend::Paper(PaperExchange)
ExchangeBackend::Polymarket(PolymarketExchange)
ExchangeBackend::Kalshi(KalshiExchange)
```

## Single vs Multi-Exchange

### Single exchange (default)

```python
# Paper (implicit)
hz.run(name="simple", markets=["test"], pipeline=[...])

# Single live exchange
hz.run(
    name="poly_mm",
    exchange=hz.Polymarket(private_key="0x..."),
    mode="live",
    ...
)
```

### Multi-exchange

```python
hz.run(
    name="cross_venue",
    exchanges=[
        hz.Polymarket(private_key="0x..."),
        hz.Kalshi(api_key="..."),
    ],
    mode="live",
    ...
)
```

See [Multi-Exchange](/multi-exchange) for details on routing, netting, and cross-venue strategies.

## Fill Model

All exchanges use a **pull model** for fills:

| Exchange | Fill Source | Method |
|----------|-----------|--------|
| **Paper** | `tick(market_id, mid_price)` | Matches orders against mid price |
| **Polymarket** | `drain_fills()` → `poll_fills_async()` | Polls `/trades` endpoint |
| **Kalshi** | `drain_fills()` → `poll_fills_async()` | Polls `/portfolio/fills` endpoint |

The strategy loop calls `poll_fills()` each cycle for live exchanges, and `tick()` for paper exchanges.

## Order Routing

When you submit an order via `hz.run()`, it's automatically routed to the correct exchange based on the `market.exchange` field:

- Markets resolved as `"polymarket"` → routed to the Polymarket backend
- Markets resolved as `"kalshi"` → routed to the Kalshi backend
- Markets with `"paper"` or empty exchange → routed to the primary exchange

You can also explicitly specify the exchange when using the Engine directly:

```python
engine.submit_order(request, exchange="polymarket")
engine.submit_quotes("market_id", quotes, Side.Yes, exchange="kalshi")
```

## Exchange Fields on Types

Orders, Fills, and Positions all carry an `exchange` field indicating which exchange they belong to:

```python
order.exchange      # "polymarket", "kalshi", or "paper"
fill.exchange       # "polymarket", "kalshi", or "paper"
position.exchange   # "polymarket", "kalshi", or "paper"
```
