Code Along: Full System
Put it all together: custom strategy, real sizing, layered risk, validation
This is the capstone. Everything from the previous walkthroughs — strategy, portfolio sizing, risk, data — combined into one system, then validated with a bootstrap confidence interval.
Step 1: Write a custom strategy
This strategy combines momentum and mean-reversion signals, scaling confidence by volatility. It also checks portfolio state before trading.
import math
from horizon import Signal, Strategy
from horizon.asset_classes import Equity
from horizon.features import RealizedVol, Return, Zscore
class AdaptiveDual(Strategy):
"""Dual-signal strategy: momentum + mean reversion, vol-scaled."""
name = "adaptive_dual"
asset_classes = [Equity]
features = {
"z": Zscore(window=20),
"r20": Return(window=20),
"vol": RealizedVol(window=60),
}
def evaluate(self, f, universe, ctx):
if ctx.portfolio.drawdown_pct > 0.04:
return [] # strategy-level pause
signals = []
for m in universe:
z, r20, vol = f.z[m.id], f.r20[m.id], f.vol[m.id]
if any(math.isnan(v) for v in [z, r20, vol]):
continue
# Mean-reversion: fade z-score extremes in range-bound markets
mr = -z * 0.5 if abs(z) > 2.0 and abs(r20) < 0.04 else 0.0
# Momentum: follow strong recent returns
mom = math.copysign(1.0, r20) * 0.5 if abs(r20) > 0.03 else 0.0
combined = mr + mom
if abs(combined) < 0.3:
continue
vol_scale = max(0.5, min(2.0, 0.20 / max(vol, 0.05)))
signals.append(Signal.from_score(
market=m, score=combined,
edge_per_stdev=int(40 * vol_scale), horizon="5d",
expected_expected_vol_bps=max(vol * 10_000, 100),
reason=f"z={z:.1f} r20={r20:+.1%} vol={vol:.1%}",
features={"z": z, "r20": r20, "vol": vol},
))
return signals
The two sub-signals can agree or conflict. Vol scaling adjusts edge estimates — low vol means more conviction. The features={...} dict stores raw values for post-hoc IC analysis.
Step 2: Kelly sizing with transaction costs
from horizon.portfolio import KellyOptimizer
portfolio = KellyOptimizer(
kelly_fraction=0.20,
max_gross_leverage=1.2,
transaction_cost_bps=8,
)
One-fifth Kelly. Full Kelly assumes perfect edge estimates, which you never have. The 8 bps cost drag prevents the optimizer from taking tiny-edge trades that lose money to fees.
Step 3: Three tiers of risk protection
from horizon.risk import DrawdownGuard, OrderRisk, RiskConfig, StopLoss
from horizon.risk.drawdown import DrawdownAction
risk = RiskConfig(
per_order=OrderRisk(max_order_notional_usd=25_000, rate_limit_per_sec=20),
max_gross_leverage=1.2,
stop_loss=StopLoss(per_position_pct=0.05, trailing_pct=0.03),
drawdown=[
DrawdownGuard(daily_pct=0.03, action=DrawdownAction.HaltNew),
DrawdownGuard(weekly_pct=0.08, action=DrawdownAction.ReduceHalf),
DrawdownGuard(monthly_pct=0.15, action=DrawdownAction.Flatten),
],
)
The stop has two parts: 5% hard stop plus 3% trailing (locks in gains). The drawdown tiers escalate: pause at 3% daily, halve at 8% weekly, flatten at 15% monthly.
Step 4: Regime-switching stress data
from horizon.data import SyntheticRegimes
data = SyntheticRegimes(
market_ids=["AAPL", "MSFT", "NVDA", "GOOGL", "AMZN"],
n_bars=504,
regimes=[
(0.30, 0.20, 0.15), # uptrend, low vol
(0.25, 0.00, 0.30), # choppy, high vol
(0.20, -0.35, 0.40), # CRASH
(0.25, 0.15, 0.20), # recovery
],
seed=2025,
)
Each tuple: (weight, annualized_drift, annualized_vol). The crash regime is the real test of whether your risk config works.
Step 5: Run the backtest
import horizon as hz
from horizon.asset_classes import AssetClass, Equity
from horizon.discovery import StaticUniverse
from horizon.discovery.base import Market
tickers = ["AAPL", "MSFT", "NVDA", "GOOGL", "AMZN"]
universe = StaticUniverse([
Market(id=t, asset_class=AssetClass.Equity) for t in tickers
])
result = hz.run(
mode="backtest",
strategies=[AdaptiveDual()],
asset_classes=[Equity],
universe=universe,
portfolio=portfolio,
risk=risk,
data_source=data,
backtest=hz.BacktestConfig(initial_cash_usd=100_000),
)
print(f"Trades: {result.n_trades}")
print(f"Total return: {result.total_return:+.2%}")
print(f"Sharpe: {result.sharpe:+.3f}")
print(f"Max drawdown: {result.max_drawdown:.2%}")
Step 6: Validate with Bootstrap
A single Sharpe number means nothing without a confidence interval.
from horizon.validate import Bootstrap
equity = [e for _, e in result.equity_curve]
returns = [(equity[i] - equity[i-1]) / equity[i-1]
for i in range(1, len(equity))]
bs = Bootstrap(metrics=["sharpe"], n_samples=500, seed=42)
bs_result = bs.run(returns=returns)
lo, hi = bs_result.ci("sharpe", conf=0.95)
print(f"\nSharpe 95% CI: [{lo:+.3f}, {hi:+.3f}]")
If the CI contains zero, your strategy might not have real edge. If the entire interval is positive, there’s evidence the signal is real.
Step 7: The decision chain
Three questions, three answers:
- Is my signal real? Bootstrap CI. If
lo > 0, statistically positive. - Is my sizing right? Compare Kelly vs
EqualWeight(). If Kelly doesn’t win, your edge estimates are noisy. - Is my risk working? Max drawdown should be bounded by the 15% monthly guard.
from horizon.portfolio import EqualWeight
result_ew = hz.run(
mode="backtest",
strategies=[AdaptiveDual()],
asset_classes=[Equity],
universe=universe,
portfolio=EqualWeight(),
risk=risk,
data_source=data,
backtest=hz.BacktestConfig(initial_cash_usd=100_000),
)
print(f"\nKelly: Sharpe={result.sharpe:+.3f} MaxDD={result.max_drawdown:.2%}")
print(f"EqualWeight: Sharpe={result_ew.sharpe:+.3f} MaxDD={result_ew.max_drawdown:.2%}")
Full file
# codealong_full_system.py
import math
import horizon as hz
from horizon import Signal, Strategy
from horizon.asset_classes import AssetClass, Equity
from horizon.data import SyntheticRegimes
from horizon.discovery import StaticUniverse
from horizon.discovery.base import Market
from horizon.features import RealizedVol, Return, Zscore
from horizon.portfolio import EqualWeight, KellyOptimizer
from horizon.risk import DrawdownGuard, OrderRisk, RiskConfig, StopLoss
from horizon.risk.drawdown import DrawdownAction
from horizon.validate import Bootstrap
class AdaptiveDual(Strategy):
name = "adaptive_dual"
asset_classes = [Equity]
features = {"z": Zscore(window=20), "r20": Return(window=20), "vol": RealizedVol(window=60)}
def evaluate(self, f, universe, ctx):
if ctx.portfolio.drawdown_pct > 0.04:
return []
signals = []
for m in universe:
z, r20, vol = f.z[m.id], f.r20[m.id], f.vol[m.id]
if any(math.isnan(v) for v in [z, r20, vol]):
continue
mr = -z * 0.5 if abs(z) > 2.0 and abs(r20) < 0.04 else 0.0
mom = math.copysign(1.0, r20) * 0.5 if abs(r20) > 0.03 else 0.0
combined = mr + mom
if abs(combined) < 0.3:
continue
vol_scale = max(0.5, min(2.0, 0.20 / max(vol, 0.05)))
signals.append(Signal.from_score(
market=m, score=combined, edge_per_stdev=int(40 * vol_scale),
horizon="5d", expected_expected_vol_bps=max(vol * 10_000, 100),
reason=f"z={z:.1f} r20={r20:+.1%} vol={vol:.1%}",
features={"z": z, "r20": r20, "vol": vol},
))
return signals
def main():
tickers = ["AAPL", "MSFT", "NVDA", "GOOGL", "AMZN"]
universe = StaticUniverse([Market(id=t, asset_class=AssetClass.Equity) for t in tickers])
data = SyntheticRegimes(market_ids=tickers, n_bars=504, regimes=[
(0.30, 0.20, 0.15), (0.25, 0.00, 0.30),
(0.20, -0.35, 0.40), (0.25, 0.15, 0.20),
], seed=2025)
portfolio = KellyOptimizer(kelly_fraction=0.20, max_gross_leverage=1.2, transaction_cost_bps=8)
risk = RiskConfig(
per_order=OrderRisk(max_order_notional_usd=25_000, rate_limit_per_sec=20),
max_gross_leverage=1.2,
stop_loss=StopLoss(per_position_pct=0.05, trailing_pct=0.03),
drawdown=[
DrawdownGuard(daily_pct=0.03, action=DrawdownAction.HaltNew),
DrawdownGuard(weekly_pct=0.08, action=DrawdownAction.ReduceHalf),
DrawdownGuard(monthly_pct=0.15, action=DrawdownAction.Flatten),
],
)
result = hz.run(
mode="backtest", strategies=[AdaptiveDual()], asset_classes=[Equity],
universe=universe, portfolio=portfolio, risk=risk,
data_source=data, backtest=hz.BacktestConfig(initial_cash_usd=100_000),
)
print(f"Trades: {result.n_trades} Return: {result.total_return:+.2%} "
f"Sharpe: {result.sharpe:+.3f} MaxDD: {result.max_drawdown:.2%}")
equity = [e for _, e in result.equity_curve]
returns = [(equity[i] - equity[i-1]) / equity[i-1] for i in range(1, len(equity))]
bs = Bootstrap(metrics=["sharpe"], n_samples=500, seed=42)
lo, hi = bs.run(returns=returns).ci("sharpe", conf=0.95)
print(f"Sharpe 95% CI: [{lo:+.3f}, {hi:+.3f}]")
result_ew = hz.run(
mode="backtest", strategies=[AdaptiveDual()], asset_classes=[Equity],
universe=universe, portfolio=EqualWeight(), risk=risk,
data_source=data, backtest=hz.BacktestConfig(initial_cash_usd=100_000),
)
print(f"Kelly: {result.sharpe:+.3f} EqualWeight: {result_ew.sharpe:+.3f}")
if __name__ == "__main__":
main()
Run it
PYTHONPATH=. python3 codealong_full_system.py