ad8dfa27d7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
1.6 KiB
Python
63 lines
1.6 KiB
Python
"""
|
||
Grid-search optimizer.
|
||
|
||
Iterates over a parameter grid, re-instantiates the strategy for each
|
||
combination, runs the backtest on the in-sample slice, and ranks by a
|
||
composite score (Sharpe × expectancy × return).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import itertools
|
||
from dataclasses import dataclass
|
||
from typing import Any, Type
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
from .backtest import BacktestEngine
|
||
from .metrics import Metrics, compute
|
||
|
||
|
||
@dataclass
|
||
class OptResult:
|
||
params: dict[str, Any]
|
||
metrics: Metrics
|
||
score: float
|
||
|
||
|
||
def grid_search(
|
||
StrategyClass: Type,
|
||
param_grid: dict[str, list],
|
||
df: pd.DataFrame,
|
||
engine: BacktestEngine,
|
||
*,
|
||
verbose: bool = False,
|
||
) -> list[OptResult]:
|
||
"""
|
||
Run every combination of parameters in `param_grid`.
|
||
Returns results sorted best-first by composite score.
|
||
"""
|
||
keys = list(param_grid.keys())
|
||
values = list(param_grid.values())
|
||
combos = list(itertools.product(*values))
|
||
|
||
results: list[OptResult] = []
|
||
|
||
for idx, combo in enumerate(combos):
|
||
params = dict(zip(keys, combo))
|
||
strategy = StrategyClass(**params)
|
||
|
||
trades, equity = engine.run(df, strategy)
|
||
m = compute(trades, equity, engine.initial_balance)
|
||
s = m.score()
|
||
|
||
results.append(OptResult(params=params, metrics=m, score=s))
|
||
|
||
if verbose and (idx + 1) % 50 == 0:
|
||
print(f" [{idx+1}/{len(combos)}] best so far: "
|
||
f"{max(results, key=lambda r: r.score).score:.4f}")
|
||
|
||
results.sort(key=lambda r: r.score, reverse=True)
|
||
return results
|