Stateful Strategies
Strategies that carry state across ticks. Kalman filters, online learners, regime trackers
Strategies that need memory across ticks (Kalman filters, online learning models, rolling feature caches) use Horizon’s state mechanism. Declare a mutable attribute on the class; the engine preserves it across calls to evaluate().
Pattern
python
from dataclasses import dataclass, field
from horizon import Strategy, Signal
from horizon.asset_classes import Equity
from horizon.features import Price
@dataclass
class KalmanState:
mean: float = 100.0
variance: float = 1.0
class KalmanMeanReversion(Strategy):
asset_classes = [Equity]
features = {"price": Price()}
state: KalmanState = field(default_factory=KalmanState)
def evaluate(self, f, universe):
signals = []
for m in universe:
current = f.price[m.id]
# Kalman update
kalman_gain = self.state.variance / (self.state.variance + 1.0)
self.state.mean += kalman_gain * (current - self.state.mean)
self.state.variance = (1 - kalman_gain) * self.state.variance + 0.01
# Compare to running mean
deviation = (current - self.state.mean) / max(self.state.mean, 0.01)
if deviation < -0.03:
signals.append(Signal.increase(m, edge_bps=40, reason=f"kalman dev={deviation:+.3f}"))
elif deviation > 0.03:
signals.append(Signal.decrease(m, edge_bps=40, reason=f"kalman dev={deviation:+.3f}"))
return signals
Per-market state
For per-market state (one Kalman filter per ticker), use a dict keyed by market id:
python
class PerMarketKalman(Strategy):
asset_classes = [Equity]
features = {"price": Price()}
state: dict = field(default_factory=dict)
def evaluate(self, f, universe):
for m in universe:
if m.id not in self.state:
self.state[m.id] = KalmanState(mean=f.price[m.id], variance=1.0)
# ...
Online ML
python
from river.linear_model import LogisticRegression
class OnlineML(Strategy):
asset_classes = [Equity]
features = {...}
def __init__(self):
super().__init__()
self.models = {} # per-market online model
def evaluate(self, f, universe, ctx):
# Update model with realized outcome (requires storing past predictions)
# Then predict for current tick
...
See river for online learning models. Install via horizon[ml].
Determinism
Stateful strategies are still deterministic as long as:
- Their state updates are pure functions of the previous state + current inputs
- They don’t use
random.random()without a seed
To preserve determinism across backtests, initialize state deterministically in __init__ or as a default dataclass.field.