Coverage for src/finbot/backtest/runner.py: 100%
32 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 17:12 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 17:12 +0000
1from finbot.backtest.experiment import ExperimentConfig
2from finbot.backtest.models import BacktestResult
3from finbot.engine.backtest_engine import BacktestEngine
4from finbot.execution.broker import SimulatedBroker
5from finbot.market.data_provider import MarketDataProvider
6from finbot.portfolio.portfolio import Portfolio
7from finbot.risk import RiskLimits, RiskManager
8from finbot.strategy.base import Strategy
9from finbot.strategy.sma_crossover import SmaCrossoverStrategy
10from finbot.strategy.strategies import BuyOnceStrategy
13class ExperimentRunner:
14 def __init__(self, market_data: MarketDataProvider):
15 self.market_data = market_data
17 def run(self, config: ExperimentConfig) -> BacktestResult:
18 strategy = self._create_strategy(config)
20 bars = self.market_data.get_bars(
21 symbols=list(config.symbols),
22 start=config.start,
23 end=config.end,
24 timeframe="1Day",
25 )
27 for symbol in config.symbols:
28 if not bars.get(symbol):
29 raise RuntimeError(f"No historical bars returned for {symbol!r}")
31 broker = SimulatedBroker(
32 portfolio=Portfolio(cash=config.initial_cash),
33 fee_rate=config.fee_rate,
34 slippage_rate=config.slippage_rate,
35 )
37 risk_limits = RiskLimits(
38 max_position_pct_nav=config.allocation,
39 max_gross_exposure=config.allocation,
40 max_symbol_exposure=config.allocation,
41 )
43 engine = BacktestEngine(
44 broker=broker,
45 strategy=strategy,
46 risk_manager=RiskManager(risk_limits),
47 )
49 return engine.run(bars)
51 @staticmethod
52 def _create_strategy(config: ExperimentConfig) -> Strategy:
53 if config.strategy == "sma_crossover":
54 return SmaCrossoverStrategy(**config.strategy_params)
56 if config.strategy == "buy_once":
57 if config.strategy_params:
58 raise ValueError("buy_once does not accept strategy parameters")
60 return BuyOnceStrategy()
62 raise ValueError(f"Unknown strategy: {config.strategy!r}")