Venues Overview

Where orders go. Paper, Alpaca, IBKR, Polymarket, Kalshi, Hyperliquid, CCXT.

A venue is “a place orders go and fills come back.” Every venue implements the same Venue protocol. The Paper venue runs in-memory; every live venue is a Python wrapper over a broker-specific SDK or REST client.

Built-in venues

All built-in venues are usable from the core hz.connect(...) entry point — no professional-tier import needed:

python
import horizon as hz

# Research / defaults
ex = hz.connect("paper", initial_cash_usd=100_000)

# Equities + options
ex = hz.connect("alpaca", paper=True, api_key="...", api_secret="...")
ex = hz.connect("ibkr", host="127.0.0.1", port=4002, client_id=1)

# Prediction markets
ex = hz.connect("polymarket", private_key="0x...", funder="0x...")
ex = hz.connect("kalshi", api_key_id="...", private_key_pem=b"...")

# Crypto
ex = hz.connect("hyperliquid", testnet=True, private_key="0x...")
ex = hz.connect("ccxt", exchange_id="binance", sandbox=True,
                api_key="...", api_secret="...")

ex.buy("AAPL", qty=10, limit=180.0)

Optional dependencies are lazy-loaded: hz.connect("polymarket") without py-clob-client installed raises a clear ImportError with the pip-install hint — you only pay the dep cost for venues you actually use.

Extending the registry

Third-party venues plug in with one decorator:

python
from horizon.venues import register_venue

@register_venue("mybroker")
def _build(**kwargs):
    from mybroker_integration import MyBrokerVenue
    return MyBrokerVenue(**kwargs)

# Immediately usable
ex = hz.connect("mybroker", api_key="...")

Inspect what’s currently registered with hz.available_venues().

The Venue protocol

python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Venue(Protocol):
    venue_name: str
    supported_classes: list[AssetClass]
    budget_usd: float

    def connect(self) -> None: ...
    def close(self) -> None: ...
    def is_connected(self) -> bool: ...

    def submit(self, action: OrderAction) -> VenueOrder: ...
    def cancel(self, order_id: str) -> bool: ...
    def cancel_all(self, market_id: str | None = None) -> int: ...
    def amend(
        self,
        order_id: str,
        new_quantity: float | None = None,
        new_price: float | None = None,
    ) -> VenueOrder: ...

    def positions(self) -> list[VenuePosition]: ...
    def open_orders(self, market_id: str | None = None) -> list[VenueOrder]: ...
    def balance(self) -> VenueCapital: ...
    def drain_fills(self) -> list[VenueFill]: ...

    def list_markets(self, filter: Any = None) -> list[Any]: ...
    def orderbook(self, market_id: str) -> Any: ...
    def market_info(self, market_id: str) -> Any: ...

Result types

python
@dataclass(frozen=True)
class VenueOrder:
    id: str
    market_id: str
    side: str
    quantity: float
    filled_quantity: float
    price: float | None
    order_type: str
    status: str          # "new" | "partial" | "filled" | "canceled" | "rejected"
    submitted_at: datetime | None
    venue_name: str
    metadata: dict

@dataclass(frozen=True)
class VenueFill:
    order_id: str
    market_id: str
    side: str
    quantity: float
    price: float
    fee: float
    timestamp: datetime | None
    venue_name: str
    multiplier: float = 1.0

@dataclass(frozen=True)
class VenueCapital:
    venue_name: str
    total_equity_usd: float
    cash_usd: float
    buying_power_usd: float
    used_buying_power_usd: float
    free_buying_power_usd: float

Multi-venue backtests

python
from horizon.venues import Paper

hz.run(
    venues={
        "paper_equity": Paper(initial_cash_usd=60_000, supported_classes=[Equity]),
        "paper_options": Paper(initial_cash_usd=40_000, supported_classes=[Option]),
    },
    ...
)

The run loop routes orders to the venue whose supported_classes matches the market’s asset_class.

Imperative API

For direct order placement without the full pipeline, use hz.connect():

python
import horizon as hz

with hz.connect("paper", initial_cash_usd=100_000) as ex:
    ex.buy("AAPL", qty=10, limit=180.0)
    print(ex.positions())

See Imperative API for details.

Next