Skip to main content
Horizon provides Kelly criterion functions implemented in Rust for zero-overhead position sizing. These compute the mathematically optimal fraction of your bankroll to risk on a binary prediction market trade, given your estimated probability and the current market price.

Full Code

Output:

The Math

For a binary prediction market with two outcomes (Yes at price P, No at price 1 - P):

Kelly for Yes (buying Yes)

When you believe the true probability p exceeds the market price P:
This is the fraction of your bankroll to risk. The formula maximizes the expected log-growth of your capital.

Kelly for No (buying No)

When you believe the market is overpriced (p < P):

Edge

The expected value per dollar risked:
Positive edge means you have an advantage. Negative edge means the market has it right (or you are wrong).

All Kelly Functions

hz.kelly(prob, market_price)

Full Kelly fraction for the Yes side. Returns 0.0 if no edge.

hz.kelly_no(prob, market_price)

Kelly fraction for the No side. Use when you think the market is too high.

hz.fractional_kelly(prob, market_price, fraction)

Multiplies the raw Kelly by a scaling factor. Using fractional Kelly reduces variance at the cost of slightly lower expected growth.
Most professional traders use quarter or half Kelly. Full Kelly is theoretically optimal for long-run growth but produces extreme drawdowns in practice. Half Kelly achieves 75% of the growth rate with substantially lower variance.

hz.kelly_size(prob, market_price, bankroll, fraction, max_size)

Converts the Kelly fraction into an actual contract count:
Capped at max_size.

hz.edge(prob, market_price)

Raw expected edge. Can be negative (no edge).

Kelly for the No Side

When you think a market is overpriced, bet on No:

Multi-Position Kelly

When you have edge across multiple markets simultaneously, use multi_kelly to prevent over-allocation:
The algorithm:
  1. Computes Kelly fractions independently for each market.
  2. If the sum exceeds max_total, proportionally scales all fractions down.
  3. Markets with no edge (Kelly = 0) stay at 0.

Liquidity-Adjusted Kelly

In thin prediction markets, placing your full Kelly size would eat through the book. liquidity_adjusted_kelly uses square-root scaling to dampen sizing as you approach available liquidity:
available_liquidity should reflect the actual depth near your target price, not the total book depth. Use feed data (bid/ask sizes) or the orderbook snapshot to estimate this.

Pipeline Integration

The kelly_sizer function creates a pipeline-compatible sizing stage for use with hz.run():

kelly_sizer parameters

The sizer reads the market price from the first available feed’s bid/ask midpoint. It returns 0.0 if no feed data is available or if there is no edge.

Liquidity-adjusted pipeline sizer

When NOT to Use Kelly

Kelly criterion assumes you know the true probability. In practice, several conditions make Kelly dangerous:

No edge

If your estimated probability equals the market price, Kelly returns 0. Do not override this. The market is efficient and you should not trade.

Bad calibration

If your probability estimates are systematically wrong (overconfident or underconfident), Kelly will oversize or undersize. Use hz.backtest() with outcomes to measure your Brier score before going live.

Correlated positions

multi_kelly treats markets as independent. If your positions are correlated (e.g., multiple BTC price markets), the true optimal sizing is lower. Use a smaller max_total:

Thin liquidity

Full Kelly in a thin market causes massive slippage. Always use liquidity_adjusted_kelly or set a low max_size cap.

Function Reference

All core functions (kelly, kelly_no, fractional_kelly, kelly_size, multi_kelly, liquidity_adjusted_kelly, edge) are implemented in Rust with #[inline] for zero-overhead calls from Python via PyO3.