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
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
Record evidence
Check the posterior
Promote or retire
API
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
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:
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:
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:
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:
sqlite3 hypotheses.db "SELECT name, posterior, evidence_count FROM hypotheses WHERE state='testing' ORDER BY posterior DESC;"
When to use
Pitfalls
Source
python/horizon/fund/_hypothesis.py. ~500 lines, SQLite-backed, thread-safe.