Venues
Venue protocol, Paper venue, imperative API
Venue protocol
Every venue (paper, live, custom) implements this interface:
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
VenueOrder / VenueFill / VenuePosition / VenueCapital
python
@dataclass(frozen=True)
class VenueOrder:
id: str
market_id: str
side: str # "buy" | "sell"
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[str, Any]
@property
def remaining: float # max(0, quantity - filled_quantity)
@property
def is_open: bool # status in ("new", "partial")
@dataclass(frozen=True)
class VenueFill:
order_id: str
market_id: str
side: str
quantity: float
price: float
fee: float = 0.0
timestamp: datetime | None = None
venue_name: str = ""
multiplier: float = 1.0
@dataclass(frozen=True)
class VenuePosition:
market_id: str
quantity: float
avg_cost: float
unrealized_pnl: float
realized_pnl: float
notional_usd: float
capital_used_usd: float
multiplier: float = 1.0
venue_name: str = ""
metadata: dict[str, Any]
@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
maintenance_margin_usd: float = 0.0
maintenance_ratio: float = 0.0
currency: str = "USD"
Paper venue
In-memory paper exchange with tick-based matching.
python
from horizon.venues import Paper
paper = Paper(
initial_cash_usd=100_000.0,
supported_classes=None, # defaults to all asset classes
fee_bps=0.0,
slippage_bps=0.0,
bar_fill_mode="cross", # "cross" | "touch"
)
paper.connect()
Tick matching
python
paper.tick("AAPL", 99.0, timestamp=datetime(2024, 1, 1))
- Updates
_last_prices["AAPL"] = 99.0 - For every resting limit order on AAPL, if the price crosses, fill at the limit price
- Returns a list of new
VenueFillevents
Market orders
Market orders fill immediately at the last known price plus slippage:
python
paper.tick("AAPL", 100.0) # establish last price
order = paper.submit(
OrderAction.place(market_id="AAPL", side="buy", quantity=10, order_type="market")
)
# fills immediately at 100 + slippage
If no tick has been seen yet, market orders are rejected cleanly (status="rejected").
Queries
python
paper.positions() # list[VenuePosition]
paper.open_orders() # list[VenueOrder]
paper.balance() # VenueCapital
paper.drain_fills() # list[VenueFill]. drains the pending queue
paper.cash() # float. convenience
paper.realized_pnl() # float. convenience
Tests
16 tests in tests/test_paper_venue.py covering limit matching on cross, market fills, slippage, cancels, amends, cash/position tracking, fee application, and fill draining.
Imperative API (level 1)
Use hz.connect() to get an ImperativeClient wrapped around any venue:
python
import horizon as hz
with hz.connect("paper", initial_cash_usd=100_000) as ex:
ex.buy("AAPL", qty=10, limit=180.0)
ex.sell("MSFT", qty=5, market=True)
print(ex.positions())
print(ex.balance())
ImperativeClient methods
python
# Place orders
ex.place_order(market_id, side, quantity, order_type="limit", price=None, ...)
ex.buy(market_id, qty, limit=None, market=False, **kwargs)
ex.sell(market_id, qty, limit=None, market=False, **kwargs)
ex.short(market_id, qty, limit=None, **kwargs)
ex.cover(market_id, qty, limit=None, **kwargs)
# Asset-class helpers
ex.buy_option(underlying, strike, expiry, right, qty, limit=None)
ex.buy_prediction(market, qty, limit, side="yes")
ex.buy_perp(market_id, qty, leverage=1.0, limit=None, reduce_only=False)
# Modify / cancel
ex.amend(order_id, new_quantity=None, new_price=None)
ex.cancel(order_id)
ex.cancel_all(market_id=None)
ex.flatten(market_id) # close all positions in a market
ex.flatten_all() # close everything
# Queries
ex.positions() # list[VenuePosition]
ex.open_orders() # list[VenueOrder]
ex.balance() # VenueCapital
ex.fills() # drain pending fills
ex.realized_pnl() # float
Context-manager support is built in:
python
with hz.connect("paper", initial_cash_usd=100_000) as ex:
ex.buy("AAPL", 10, limit=180)
# ex.close() called automatically
Writing a custom venue
python
from horizon.asset_classes import AssetClass
from horizon.venues.base import Venue, VenueCapital
class MyBroker:
venue_name = "my_broker"
supported_classes = [AssetClass.Equity]
def __init__(self, api_key_env: str, budget_usd: float):
self._api_key = os.environ[api_key_env]
self.budget_usd = budget_usd
def connect(self): ...
def close(self): ...
def is_connected(self): ...
def submit(self, action): ...
def cancel(self, order_id): ...
# ... etc
Drop into hz.run(venues={"mine": MyBroker(...)}, ...).