HypothesisManager

SQLite-backed Bayesian hypothesis lifecycle: prior → evidence → posterior → promote or retire

HypothesisManager is v1’s research-workflow module. Each trading idea is a hypothesis with a prior, accumulated evidence, a computed posterior, and a lifecycle state (proposed, testing, promoted, retired). It persists to SQLite so hypotheses survive process restarts and can be audited after the fact.

Import

python
from horizon.fund._hypothesis import HypothesisManager

Lifecycle states

  • proposed: registered but not yet actively tested
  • testing: collecting evidence, updating posterior
  • promoted: posterior exceeds confidence threshold; strategy goes live
  • retired: posterior dropped below threshold or lifetime expired

Bayesian updating

Register a hypothesis

Set a prior (e.g., "I believe this strategy has 55% hit rate") and a target (what evidence would confirm/deny it).

Record evidence

As trades execute, record outcomes. Each observation updates the posterior using Bayesian math.

Check the posterior

At any time, query the posterior probability that the hypothesis is true. Compare to your confidence threshold.

Promote or retire

If the posterior exceeds the threshold, promote the hypothesis (start live trading). If it drops below, retire it. Otherwise keep testing.

API

python
class HypothesisManager:
    def __init__(
        self,
        db_path: str = "hypotheses.db",
        confidence_threshold: float = 0.80,
    ) -> None:
        ...

    def register(
        self,
        name: str,
        description: str,
        prior: float,
        target_evidence: int = 100,
    ) -> str:
        """Register a new hypothesis. Returns its ID."""

    def record_evidence(
        self,
        hypothesis_id: str,
        outcome: float,                  # 1.0 = confirming, 0.0 = contradicting
        metadata: dict = None,
    ) -> None:
        """Append a new observation and update the posterior."""

    def posterior(self, hypothesis_id: str) -> float:
        """Current posterior probability."""

    def should_promote(self, hypothesis_id: str) -> bool:
        """True iff posterior exceeds confidence_threshold."""

    def promote(self, hypothesis_id: str) -> None:
        """Mark hypothesis as promoted."""

    def retire(self, hypothesis_id: str, reason: str) -> None:
        """Mark hypothesis as retired."""

    def list_all(self, state: str = None) -> list[Hypothesis]:
        """List hypotheses, optionally filtered by state."""

    def snapshot(self, hypothesis_id: str) -> HypothesisSnapshot:
        """Full state including prior, posterior, evidence count, age."""

Usage

python
from horizon.fund._hypothesis import HypothesisManager

mgr = HypothesisManager(db_path="hypotheses.db", confidence_threshold=0.85)

# Register a hypothesis
h_id = mgr.register(
    name="tech_mean_reversion_2025",
    description="Mean reversion on NASDAQ-100 stocks with 20-day z-score",
    prior=0.55,                     # starting belief: 55% hit rate
    target_evidence=200,             # collect 200 observations before promoting
)

# As trades execute, record outcomes
for trade in my_trades:
    outcome = 1.0 if trade.realized_pnl > 0 else 0.0
    mgr.record_evidence(h_id, outcome, metadata={
        "trade_id": trade.id,
        "market_id": trade.market_id,
        "realized_pnl": trade.realized_pnl,
    })

# Check current posterior
posterior = mgr.posterior(h_id)
print(f"Posterior: {posterior:.3f}")

# Promote or retire
if mgr.should_promote(h_id):
    mgr.promote(h_id)
    print("Hypothesis promoted. go live!")
elif posterior < 0.20:
    mgr.retire(h_id, reason="posterior collapsed")
    print("Hypothesis retired.")

Integration with Horizon

Wrap the hypothesis manager around your Horizon strategies to drive systematic research:

python
import horizon as hz
from horizon.fund._hypothesis import HypothesisManager
from horizon.quant import BollingerMeanRev

mgr = HypothesisManager()

h_id = mgr.register(
    name="bollinger_mr_window20",
    description="Bollinger mean reversion, 20-day window, 2σ entry",
    prior=0.55,
)

result = hz.run(
    mode="backtest",
    strategies=[BollingerMeanRev(window=20, entry_z=2.0)],
    ...
)

# Feed backtest trade outcomes into the hypothesis
for trade in result.trades:
    outcome = 1.0 if trade.realized_pnl > 0 else 0.0
    mgr.record_evidence(h_id, outcome)

# Check if the hypothesis is supported
if mgr.should_promote(h_id):
    print(f"Backtest confirms hypothesis with posterior {mgr.posterior(h_id):.3f}")

You can cycle through parameter variants and register each as its own hypothesis:

python
for window in [10, 15, 20, 25, 30]:
    for entry_z in [1.5, 2.0, 2.5]:
        h_id = mgr.register(
            name=f"bollinger_w{window}_z{entry_z}",
            description=f"BollingerMR window={window} entry_z={entry_z}",
            prior=0.50,
        )
        # Run backtest, record evidence...

After the sweep, mgr.list_all(state="testing") gives you all variants sorted by posterior.

SQLite persistence

Hypotheses are stored in a SQLite database with schema:

sql
CREATE TABLE hypotheses (
    id TEXT PRIMARY KEY,
    name TEXT,
    description TEXT,
    state TEXT,
    prior REAL,
    posterior REAL,
    evidence_count INTEGER,
    successes INTEGER,
    failures INTEGER,
    created_at REAL,
    updated_at REAL,
    ...
);

CREATE TABLE evidence (
    id INTEGER PRIMARY KEY,
    hypothesis_id TEXT,
    timestamp REAL,
    outcome REAL,
    metadata TEXT,        -- JSON-encoded
    FOREIGN KEY (hypothesis_id) REFERENCES hypotheses(id)
);

Because it’s SQLite, you can query hypothesis state from outside the trading process:

bash
sqlite3 hypotheses.db "SELECT name, posterior, evidence_count FROM hypotheses WHERE state='testing' ORDER BY posterior DESC;"

When to use

Systematic research loops When you're running many parameter sweeps and need to track which configurations are supported by the evidence.
Live calibration Register hypotheses at deploy time, record every trade's outcome, and get automatic promotion/demotion.
Audit trails The SQLite log gives you a permanent record of what you believed, when, and what evidence changed your mind.

Pitfalls

Source

python/horizon/fund/_hypothesis.py. ~500 lines, SQLite-backed, thread-safe.

Next