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

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 

11 

12 

13class ExperimentRunner: 

14 def __init__(self, market_data: MarketDataProvider): 

15 self.market_data = market_data 

16 

17 def run(self, config: ExperimentConfig) -> BacktestResult: 

18 strategy = self._create_strategy(config) 

19 

20 bars = self.market_data.get_bars( 

21 symbols=list(config.symbols), 

22 start=config.start, 

23 end=config.end, 

24 timeframe="1Day", 

25 ) 

26 

27 for symbol in config.symbols: 

28 if not bars.get(symbol): 

29 raise RuntimeError(f"No historical bars returned for {symbol!r}") 

30 

31 broker = SimulatedBroker( 

32 portfolio=Portfolio(cash=config.initial_cash), 

33 fee_rate=config.fee_rate, 

34 slippage_rate=config.slippage_rate, 

35 ) 

36 

37 risk_limits = RiskLimits( 

38 max_position_pct_nav=config.allocation, 

39 max_gross_exposure=config.allocation, 

40 max_symbol_exposure=config.allocation, 

41 ) 

42 

43 engine = BacktestEngine( 

44 broker=broker, 

45 strategy=strategy, 

46 risk_manager=RiskManager(risk_limits), 

47 ) 

48 

49 return engine.run(bars) 

50 

51 @staticmethod 

52 def _create_strategy(config: ExperimentConfig) -> Strategy: 

53 if config.strategy == "sma_crossover": 

54 return SmaCrossoverStrategy(**config.strategy_params) 

55 

56 if config.strategy == "buy_once": 

57 if config.strategy_params: 

58 raise ValueError("buy_once does not accept strategy parameters") 

59 

60 return BuyOnceStrategy() 

61 

62 raise ValueError(f"Unknown strategy: {config.strategy!r}")