Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mathematicalcompany.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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

Verification Status

Different exchanges have received different levels of audit. Verification status below reflects how rigorously each backend has been compared against its reference implementation or live API behavior.
ExchangeStatusNotes
Paperβœ… VerifiedIn-process simulation, exhaustively unit-tested
Polymarketβœ… VerifiedByte-for-byte HMAC parity with py_clob_client (regression-tested), live API endpoints confirmed, EOA + Gnosis Safe both supported
Hyperliquid🟑 Fixes pushedmsgpack ordering, cloid format, spot offset, error handling, cancelByCloid all fixed in v0.8.7. Needs live verification.
Kalshi🟑 Fixes pushedModern host + RSA-PSS auth + fixed-point dollar field format in v0.8.7. Needs live verification.
Coinbase🟑 Fixes pushedES256 JWT auth + correct list-orders endpoint in v0.8.7. Needs live verification.
Limitless🟑 Fixes pushedPolymarket fork β€” same salt/field name fixes applied in v0.8.7. Needs live verification.
Alpaca🟑 Fixes pushedv0.8.8: switched fill polling to /v2/account/activities/FILL (the previous /v2/orders?status=filled query was invalid and never returned fills). Added client_order_id for idempotent retries, fixed limit-price formatter to preserve precision for sub-dollar stocks, added Day TIF, signed-qty parsing for short positions, per-order status check on cancel-all. Needs live verification.
IBKR🟑 Fixes pushedv0.8.8: dropped fabricated paper-api.ibkr.com host (paper accounts use the same host), fixed positions endpoint to include the required {pageId} segment, added multi-step order-confirmation loop, account-id filtering on cancel_all and fill polling (was previously cross-account), commission string parsing ("1.00 USD"), epoch-ms timestamp via trade_time_r, contract multiplier inference for options, /iserver/auth/ssodh/init on session timeout, acctId in order body. Needs live verification β€” and remember the bearer token must come from IBKR’s private_key_jwt SSO flow or a local Client Portal Gateway.
Robinhood⚠ UnverifiedCode paths exist, never byte-for-byte audited (Bearer token may need replacement with Ed25519 for newer Crypto API)
For live trading with real money, Polymarket is the only fully verified backend. The 🟑 exchanges have all critical bugs fixed and should now work in principle, but have not been tested against real accounts. Test in paper or with small size first.

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"