Skip to main content
Horizon supports ten exchange backends that all implement the same Exchange trait. You can trade on a single exchange or multiple exchanges simultaneously.

Prediction Markets

Paper

Local simulated exchange with tick-based matching. No credentials needed. Default for development.

Polymarket

Largest prediction market on Polygon. EIP-712 signed orders via the CLOB API.

Kalshi

US-regulated (CFTC) prediction market. REST API with bearer token auth.

Limitless

Base chain prediction market. REST client for on-chain trading.

Traditional & Crypto

Alpaca

Commission-free stock and ETF trading. Paper and live modes.

Coinbase

Crypto exchange with deep liquidity. HMAC-SHA256 auth via Advanced Trade API.

Robinhood

Commission-free crypto trading via REST API.

Hyperliquid

Decentralized perpetuals + spot on its own L1 chain. Wallet-based EIP-712 auth.

Interactive Brokers

Multi-asset broker: stocks, options, futures, and ForecastEx prediction markets. Bridges equities/options and prediction market trading.

Exchange Model

All exchanges implement a common trait:
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)
ExchangeBackend::Alpaca(AlpacaExchange)
ExchangeBackend::Coinbase(CoinbaseExchange)
ExchangeBackend::Hyperliquid(HyperliquidExchange)
ExchangeBackend::Robinhood(RobinhoodExchange)
ExchangeBackend::IBKR(IBKRExchange)

Single vs Multi-Exchange

Single exchange (default)

# 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

hz.run(
    name="cross_venue",
    exchanges=[
        hz.Polymarket(private_key="0x..."),
        hz.InteractiveBrokers(paper=True),
        hz.Coinbase(),
    ],
    mode="live",
    ...
)
See Multi-Exchange for details on routing, netting, and cross-venue strategies.

Fill Model

All exchanges use a pull model for fills:
ExchangeFill SourceMethod
Papertick(market_id, mid_price)Matches orders against mid price
Polymarketdrain_fills()poll_fills_async()Polls /trades endpoint
Kalshidrain_fills()poll_fills_async()Polls /portfolio/fills endpoint
Alpacadrain_fills()poll_fills_async()Polls /v2/orders?status=filled
Coinbasedrain_fills()poll_fills_async()Polls /orders/historical/fills
Robinhooddrain_fills()poll_fills_async()Polls /crypto/trading/orders/?status=filled
IBKRdrain_fills()poll_fills_async()Polls /iserver/account/trades
Hyperliquiddrain_fills()poll_fills_async()Polls /info with userFillsByTime
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 "alpaca" → routed to the Alpaca backend
  • Markets with "ibkr" → routed to the IBKR backend
  • Markets with "coinbase" → routed to the Coinbase backend
  • Markets with "robinhood" → routed to the Robinhood backend
  • Markets with "hyperliquid" → routed to the Hyperliquid backend
  • Markets with "paper" or empty exchange → routed to the primary exchange
You can also explicitly specify the exchange when using the Engine directly:
engine.submit_order(request, exchange="polymarket")
engine.submit_quotes("market_id", quotes, Side.Yes, exchange="kalshi")

Side Mapping

ExchangeSide UsedNotes
PolymarketSide.Yes / Side.NoBinary prediction contracts
KalshiSide.Yes / Side.NoBinary prediction contracts
LimitlessSide.Yes / Side.NoBinary prediction contracts
AlpacaSide.LongEquities — no Yes/No concept
CoinbaseSide.LongCrypto — no Yes/No concept
HyperliquidSide.LongPerps/spot — no Yes/No concept
RobinhoodSide.LongCrypto — no Yes/No concept
IBKRSide.LongMulti-asset — no Yes/No concept
PaperAnyCopies the side from the order request

Exchange Fields on Types

Orders, Fills, and Positions all carry an exchange field indicating which exchange they belong to:
order.exchange      # "polymarket", "kalshi", "alpaca", "coinbase", "hyperliquid", "robinhood", "ibkr", or "paper"
fill.exchange       # "polymarket", "kalshi", "alpaca", "coinbase", "hyperliquid", "robinhood", "ibkr", or "paper"
position.exchange   # "polymarket", "kalshi", "alpaca", "coinbase", "hyperliquid", "robinhood", "ibkr", or "paper"