Philosophy
Why Horizon is layered and signal-first
Three separate problems
Every trading system is solving three questions at once:
- Is my signal right? Does the thing I think will happen actually happen?
- Am I sizing correctly? Given a good signal, am I betting the right amount?
- Am I executing well? Given a target position, am I getting there at a good price?
Most trading libraries collapse all three into a single callback: “take prices, return orders.” That’s fine for a simple system. But when it loses money, you can’t tell which part is wrong. Was the signal bad? Was the sizing too aggressive? Did execution slippage eat the edge?
Horizon keeps them as separate layers. You can backtest signal quality against a perfect executor. You can test a sizer against a fixed signal. You can measure execution against pre-computed targets.
Signals, not orders
Strategies return Signal objects, not orders:
Signal(
market_id="AAPL",
direction=Direction.Increase,
confidence=0.65,
expected_edge_bps=30.0,
expected_expected_vol_bps=100.0,
horizon=timedelta(days=1),
)
No bid, no ask, no size, no order type. Those are portfolio and execution decisions, not signal decisions.
Direction.Increase is asset-class-neutral. The executor translates it: buy shares for equities, buy Yes for prediction markets, go long for perps. Your strategy never needs to know.
Pluggable sizers
“Given N signals, what positions should I hold?” is a research question by itself. Kelly is great when you trust your estimates. Carver is great for vol targeting across a basket. Equal-weight is great as a sanity-check baseline.
Horizon ships three working sizers and makes it easy to write your own. Swap with one argument.
Per-asset-class execution
A market order on the NYSE is fundamentally different from a limit order on a Polymarket CLOB, which is different from a multi-leg option strategy. The execution layer is the only place where asset-class branching happens. Everything upstream stays neutral.
Seven risk layers
Different failures happen at different timescales:
- Per-order: catches bad inputs before submission
- Stop losses: per-position, fires on unrealized P&L
- Drawdown guards: portfolio-wide, stacks daily/weekly/monthly
- Circuit breakers: vol spikes, feed staleness, reject streaks
- Margin watchdog: per-venue buying-power
- Scenario gates: stress-test shocks (declarative, runner in a future release)
- Kill switch: emergency flatten + halt
One big risk check isn’t enough. Different failures need different responses.
One code path
The pipeline doesn’t fork between backtest, paper, and live. The only things that change are the data source and the venue. If your backtest works, paper works. If paper works, live works.