Skip to main content

Equity Trading

Horizon supports equities as a first-class asset class. The same engine, risk pipeline, feed system, and persistence layer work for stocks — the only differences are Side.Long instead of Side.Yes/Side.No, and price ranges that aren’t limited to 0-1.

Quick Start

import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    return ctx.feeds["aapl"].price

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    spread = fair * 0.001  # 10bps spread
    return [hz.Quote(bid=fair - spread/2, ask=fair + spread/2, size=10)]

hz.run(
    name="aapl_mm",
    exchange=hz.Alpaca(paper=True),
    markets=[hz.Market(id="AAPL", exchange="alpaca", ticker="AAPL", asset_class="equity")],
    feeds={"aapl": hz.AlpacaFeed(symbols=["AAPL"])},
    pipeline=[fair_value, quoter],
    risk=hz.Risk.equity(max_position=100, max_notional=50_000),
    mode="live",
)

What’s Different from Prediction Markets

Prediction MarketsEquities
SideSide.Yes / Side.NoSide.Long
Price range0.01 - 0.990.01 - 100,000
Risk presetRisk()Risk.equity()
Token IDsYes (Polymarket)None
ExchangesPolymarket, Kalshi, LimitlessAlpaca, IBKR
Discoverydiscover_markets("polymarket", ...)discover_markets("alpaca", ...)
Everything else is the same: pipeline composition, risk checks, order management, position tracking, persistence, monitoring, backtesting.

Side.Long

Equities don’t have Yes/No outcomes. The Side.Long variant represents a directional position:
from horizon import Side, OrderSide, OrderRequest

# Buy 10 shares of AAPL at $175
req = OrderRequest(
    market_id="AAPL",
    side=Side.Long,
    order_side=OrderSide.Buy,
    size=10,
    price=175.50,
)

# Sell to close
req = OrderRequest(
    market_id="AAPL",
    side=Side.Long,
    order_side=OrderSide.Sell,
    size=10,
    price=180.00,
)
The position tracker, risk pipeline, and P&L calculations all handle Side.Long the same way they handle Side.Yes — buy increases position, sell decreases it.

Risk Configuration

Risk.equity() provides defaults appropriate for stock trading:
risk = hz.Risk.equity(
    max_position=1000,       # max shares per symbol (default: 1000)
    max_notional=100_000,    # max portfolio value (default: 100,000)
    max_drawdown_pct=5,      # kill switch trigger (default: 5%)
    max_order_size=500,      # max single order size (default: 500)
    price_min=0.01,          # minimum valid price (default: 0.01)
    price_max=100_000,       # maximum valid price (default: 100,000)
)
The key difference from Risk() is price_max. Prediction market prices are bounded 0-1; stock prices aren’t. The Rust risk pipeline uses price_min/price_max from the config, so the same validation code works for both.

Market Discovery

Find stocks via Alpaca or IBKR:
import horizon as hz

# Search Alpaca for equities
stocks = hz.discover_markets(exchange="alpaca", query="AAPL", limit=10)
for s in stocks:
    print(f"{s.ticker}: {s.name}")

# Search IBKR
contracts = hz.discover_markets(exchange="ibkr", query="AAPL", limit=10)

CLI Discovery

horizon discover "AAPL" --exchange alpaca
horizon discover "apple" --exchange ibkr

Market Object

Equity markets use optional fields for asset class metadata:
market = hz.Market(
    id="AAPL",
    name="Apple Inc.",
    exchange="alpaca",
    ticker="AAPL",
    asset_class="equity",
)

# Options contracts use additional fields
option = hz.Market(
    id="AAPL230120C00150000",
    name="AAPL Jan 2023 150 Call",
    exchange="ibkr",
    ticker="AAPL",
    asset_class="option",
    underlying="AAPL",
    strike_price=150.0,
    option_type="call",
    multiplier=100.0,
)
FieldUsed ForExample
asset_classInstrument type"equity", "option", "crypto"
underlyingDerivatives"AAPL" for an AAPL option
strike_priceOptions150.0
option_typeOptions"call" or "put"
multiplierContracts100.0 for US equity options
All fields are optional and default to None. Prediction markets ignore them.

Feeds

Alpaca provides real-time equity data via WebSocket:
feeds = {
    "aapl": hz.AlpacaFeed(symbols=["AAPL"]),
    "spy": hz.AlpacaFeed(symbols=["SPY", "QQQ"]),
}
Multi-symbol feeds create per-symbol snapshots keyed as {feed_name}:{SYMBOL}:
ctx.feeds["spy:SPY"].price   # SPY last trade
ctx.feeds["spy:QQQ"].price   # QQQ last trade
ctx.feeds["spy:SPY"].bid     # SPY best bid
ctx.feeds["spy:SPY"].ask     # SPY best ask

CLI Tools

The equity CLI group provides market data tools:
horizon equity quote AAPL              # stock quote
horizon equity search "tech"           # search stocks
horizon equity options-chain AAPL      # options chain
horizon equity greeks SPY              # Greek exposure
horizon equity iv-surface TSLA         # IV term structure
horizon equity screener --sector tech  # stock screener
These use Unusual Whales as the data source when UNUSUAL_WHALES_API_KEY is set.

Multi-Asset Strategies

Combine prediction markets and equities in one strategy:
hz.run(
    name="cross_asset",
    exchanges=[
        hz.Polymarket(private_key="0x..."),
        hz.Alpaca(paper=True),
    ],
    markets=[
        hz.Market(id="will-fed-cut-rates", exchange="polymarket"),
        hz.Market(id="TLT", exchange="alpaca", asset_class="equity"),
    ],
    feeds={
        "rates": hz.PolymarketBook("will-fed-cut-rates"),
        "tlt": hz.AlpacaFeed(symbols=["TLT"]),
    },
    pipeline=[cross_asset_strategy],
    risk=hz.Risk(max_position=100, price_min=0.01, price_max=1000),
)
Orders route to the correct exchange based on market.exchange. The engine tracks positions and P&L across all exchanges in a single book.

What Works the Same

These features work identically for equities and prediction markets:
  • Pipeline composition: chain functions, automatic signature introspection
  • Risk pipeline: all 8 checks (kill switch, price/size, position, notional, drawdown, rate limit, dedup)
  • Order management: submit, cancel, amend, bracket orders, stop-loss, take-profit
  • Position tracking: real-time P&L, exposure calculations
  • Persistence: SQLite crash recovery, fill journal, position snapshots
  • Monitoring: Prometheus metrics, alerts, TUI dashboard
  • Backtesting: hz.backtest() with the same pipeline
  • Autonomous fund: FundManager, strategy lifecycle, oversight loop