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:
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:
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
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
@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
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():
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.