Horizon Kelly Criterion
Horizon includes a complete suite of Kelly criterion functions for optimal position sizing in prediction markets, equities, options, and crypto. All functions are implemented in Rust for maximum performance and exposed to Python via PyO3.The Kelly criterion tells you the theoretically optimal fraction of your bankroll to wager given your edge. In practice, fractional Kelly (typically 0.25x to 0.5x) is preferred to reduce variance and account for estimation error.
Kelly works for any binary outcome with a known probability and price — prediction market contracts, stock directional bets, option payoffs, or crypto positions. The math is the same: estimate the true probability, compare to the market-implied price, and size accordingly.
Overview
Edge Calculation
hz.edge() computes the expected value of a bet given your probability estimate and the market price.Full Kelly
hz.kelly() and hz.kelly_no() compute the optimal fraction for Yes and No sides.Fractional Kelly
hz.fractional_kelly() scales the Kelly fraction to reduce variance and risk of ruin.Position Sizing
hz.kelly_size() converts the fraction into a concrete position size in contract units.Core Functions
hz.edge
Compute the expected edge (expected value) of a Yes bet.fair_prob - market_price. A positive edge on Yes means the market underprices the event.
hz.kelly
Full Kelly fraction for the Yes side.(fair_prob * (1 - market_price) - (1 - fair_prob) * market_price) / (1 - market_price)
If the result is negative, there is no edge on the Yes side.
hz.kelly_no
Full Kelly fraction for the No side.hz.fractional_kelly
Scale the Kelly fraction by a conservative multiplier.| Parameter | Type | Description |
|---|---|---|
prob | float | Your estimated true probability |
market_price | float | Current market price |
fraction | float | Kelly multiplier (0.0 to 1.0) |
hz.kelly_size
Convert a Kelly fraction into a concrete position size in contract units.| Parameter | Type | Description |
|---|---|---|
prob | float | Your estimated true probability |
market_price | float | Current market price |
bankroll | float | Total available capital |
fraction | float | Kelly multiplier (0.0 to 1.0) |
max_size | float | Hard cap on position size (contracts) |
hz.multi_kelly
Optimal sizing across multiple simultaneous positions.| Parameter | Type | Description |
|---|---|---|
probs | list[float] | Your estimated probabilities for each market |
prices | list[float] | Current market prices for each market |
max_total | float | Maximum total Kelly fraction across all positions |
multi_kelly returns Kelly fractions (not contract sizes). The sum of fractions will not exceed max_total. This is preferred over calling kelly independently for each position, which could over-allocate capital.hz.liquidity_adjusted_kelly
Adjusts the Kelly size for market impact based on available liquidity.| Parameter | Type | Description |
|---|---|---|
prob | float | Your estimated true probability |
market_price | float | Current market price |
bankroll | float | Total available capital |
fraction | float | Kelly multiplier (0.0 to 1.0) |
available_liquidity | float | Contracts available at/near the market price |
max_size | float | Hard cap on position size (contracts) |
max_size and available_liquidity.
Pipeline Helpers
Horizon provides two helper functions designed for use insidehz.run() pipeline functions.
hz.kelly_sizer
Factory function that returns a pipeline-compatible sizing function for use withhz.run().
| Parameter | Type | Default | Description |
|---|---|---|---|
fraction | float | 0.25 | Kelly multiplier |
bankroll | float | 1000.0 | Total available capital |
max_size | float | 100.0 | Hard cap on position size |
(Context, float) -> float that extracts the market price from ctx.feeds and calls hz.kelly_size() internally.
hz.kelly_sizer_with_liquidity
Likekelly_sizer but adjusts for available liquidity using hz.liquidity_adjusted_kelly().
| Parameter | Type | Default | Description |
|---|---|---|---|
fraction | float | 0.25 | Kelly multiplier |
bankroll | float | 1000.0 | Total available capital |
max_size | float | 100.0 | Hard cap on position size |
(Context, float) -> float that extracts both market price and available liquidity from ctx and calls hz.liquidity_adjusted_kelly() internally.
Examples
Basic Kelly Sizing
Multi-Position Portfolio
Liquidity-Aware Sizing
Kelly in a Pipeline
The most common usage is inside anhz.run() pipeline where Kelly determines order size dynamically.
Comparing Kelly Fractions
Mathematical Background
Kelly Formula for Binary Markets
Kelly Formula for Binary Markets
For a binary outcome (Yes/No) with market price
p and your estimated probability q:Yes Kelly fraction = (q * (1-p) - (1-q) * p) / (1-p)No Kelly fraction = ((1-q) * p - q * (1-p)) / pThis maximizes the expected logarithm of wealth (geometric growth rate).Why Fractional Kelly?
Why Fractional Kelly?
Full Kelly is optimal only if:
- Your probability estimates are perfectly calibrated
- You have infinite time horizon
- You can tolerate extreme drawdowns
f < 1.0) provides:- Lower variance (proportional to f^2)
- Lower drawdowns
- Robustness to estimation error
- Only marginally lower expected growth (at half-Kelly, growth is 75% of full Kelly)
Multi-Position Kelly
Multi-Position Kelly
When holding multiple positions, independent Kelly calculations can over-allocate capital.
hz.multi_kelly() solves the joint optimization problem, ensuring total allocation respects the bankroll constraint.