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.

Next