AdaptiveThresholds
Self-tuning confidence thresholds from realized outcomes
AdaptiveThresholds is v1’s self-calibration module. Strategies have confidence thresholds (e.g., “only trade when confidence > 0.6”). AdaptiveThresholds tunes those thresholds automatically from realized outcomes: if your 0.6 threshold has been producing 45% accuracy, it raises the threshold until accuracy hits target.
Import
python
from horizon.fund._adaptive_thresholds import AdaptiveThresholds, ThresholdConfig
The core idea
Strategy produces signals with confidence scores
E.g.,
Signal(confidence=0.65, ...). You filter to confidence > threshold.Record realized outcomes
As trades execute, record (confidence, outcome) pairs. Outcome is 1 if the trade was profitable, 0 otherwise.
Compute hit rate at current threshold
Among recent trades where
confidence > threshold, what fraction were profitable?Tune toward target
If hit rate is below target, raise threshold (be more selective). If above, lower threshold (trade more).
Bounded adjustment
Adjustments happen slowly (e.g., 1% per cycle) and are bounded to
[min_threshold, max_threshold] to prevent runaway tuning.API
python
class AdaptiveThresholds:
def __init__(
self,
config: ThresholdConfig | None = None,
) -> None:
...
def record_outcome(
self,
strategy_name: str,
confidence: float,
outcome: float, # 1.0 = profit, 0.0 = loss
) -> None:
"""Append a realized outcome."""
def current_threshold(self, strategy_name: str) -> float:
"""Get the current threshold for a strategy."""
def update_threshold(self, strategy_name: str) -> float:
"""Recalibrate based on recent outcomes. Returns new threshold."""
def hit_rate(self, strategy_name: str) -> float:
"""Current hit rate at the current threshold."""
def snapshot(self, strategy_name: str) -> ThresholdSnapshot:
"""Full state including threshold, hit rate, n observations, recent history."""
@dataclass
class ThresholdConfig:
initial_threshold: float = 0.55
target_hit_rate: float = 0.55
min_threshold: float = 0.40
max_threshold: float = 0.85
adjustment_rate: float = 0.01
min_observations: int = 30
Usage
python
from horizon.fund._adaptive_thresholds import AdaptiveThresholds, ThresholdConfig
at = AdaptiveThresholds(config=ThresholdConfig(
initial_threshold=0.60,
target_hit_rate=0.55,
min_threshold=0.45,
max_threshold=0.85,
adjustment_rate=0.02,
min_observations=50,
))
# In the signal generation loop
threshold = at.current_threshold("my_strategy")
if signal.confidence > threshold:
place_order(signal)
# In the fill/P&L loop
for trade in recent_trades:
outcome = 1.0 if trade.realized_pnl > 0 else 0.0
at.record_outcome(
strategy_name="my_strategy",
confidence=trade.signal_confidence,
outcome=outcome,
)
# Periodically recalibrate
new_threshold = at.update_threshold("my_strategy")
snapshot = at.snapshot("my_strategy")
print(f"Threshold: {snapshot.threshold:.3f}")
print(f"Hit rate: {snapshot.hit_rate:.1%}")
print(f"Observations: {snapshot.n_observations}")
Integration with Horizon
Wrap AdaptiveThresholds around a strategy to make it self-tuning:
python
from horizon import Strategy, Signal
from horizon.quant import BollingerMeanRev
from horizon.fund._adaptive_thresholds import AdaptiveThresholds
class AdaptiveBollinger(BollingerMeanRev):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.adaptive = AdaptiveThresholds()
def evaluate(self, f, universe):
signals = super().evaluate(f, universe)
threshold = self.adaptive.current_threshold(self.name)
return [sig for sig in signals if sig.confidence > threshold]
Then after each backtest or live session, feed outcomes back:
python
result = hz.run(..., strategies=[AdaptiveBollinger(window=20)])
strategy = ... # the instance
for trade in result.trades:
outcome = 1.0 if trade.realized_pnl > 0 else 0.0
strategy.adaptive.record_outcome(
strategy_name=strategy.name,
confidence=???, # needs to be tracked
outcome=outcome,
)
strategy.adaptive.update_threshold(strategy.name)
When to use
Long-running production When your system will run for months or years, adaptive thresholds gradually improve as data accumulates. For a 1-week backtest it's not useful.
Ensemble of similar strategies Run 10 parameter variants, let each self-tune its threshold, and the best ones emerge organically.
Drift adaptation Markets change. A fixed threshold calibrated to 2023 data won't be optimal in 2025. Adaptive thresholds follow the drift.
Pitfalls
Source
python/horizon/fund/_adaptive_thresholds.py. ~250 lines, thread-safe.