feat: phase 1 — data downloader, backtrader runner, SMA strategy
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""BacktestRunner: orchestrates data loading, cerebro setup, and execution."""
|
||||
import logging
|
||||
import backtrader as bt
|
||||
from typing import Optional
|
||||
|
||||
from backtest.config import BacktestConfig
|
||||
from backtest.feed import df_to_bt_feed
|
||||
from data.downloader import download_daily
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BacktestRunner:
|
||||
"""Run backtrader backtests with Chinese equity data."""
|
||||
|
||||
def __init__(self, config: BacktestConfig):
|
||||
self.config = config
|
||||
self.cerebro = bt.Cerebro()
|
||||
self._results: Optional[list] = None
|
||||
|
||||
def add_data(self, symbol: str) -> None:
|
||||
"""Download data for a symbol and add to cerebro as a feed."""
|
||||
df = download_daily(
|
||||
symbol=symbol,
|
||||
start=self.config.start_date,
|
||||
end=self.config.end_date,
|
||||
adjust=self.config.adjust,
|
||||
)
|
||||
feed = df_to_bt_feed(df)
|
||||
self.cerebro.adddata(feed, name=symbol)
|
||||
logger.info(f"Added {symbol}: {len(df)} bars")
|
||||
|
||||
def add_strategy(self, strategy_cls, **kwargs) -> None:
|
||||
"""Add a strategy class to cerebro."""
|
||||
self.cerebro.addstrategy(strategy_cls, **kwargs)
|
||||
|
||||
def setup(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> None:
|
||||
"""Full setup: load data for all symbols, configure cerebro, add strategy."""
|
||||
# Load data for all symbols
|
||||
for sym in self.config.symbols:
|
||||
self.add_data(sym)
|
||||
|
||||
# Configure cerebro
|
||||
self.cerebro.broker.setcash(self.config.initial_cash)
|
||||
self.cerebro.broker.setcommission(commission=self.config.commission)
|
||||
|
||||
# Add analyzers
|
||||
self.cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe", riskfreerate=0.02)
|
||||
self.cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")
|
||||
self.cerebro.addanalyzer(bt.analyzers.Returns, _name="returns")
|
||||
self.cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trades")
|
||||
|
||||
# Add strategy
|
||||
strategy_kwargs = strategy_kwargs or {}
|
||||
self.cerebro.addstrategy(strategy_cls, **strategy_kwargs)
|
||||
|
||||
def run(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> list:
|
||||
"""Setup and run the backtest. Returns cerebro run results."""
|
||||
self.setup(strategy_cls, strategy_kwargs)
|
||||
start_val = self.cerebro.broker.getvalue()
|
||||
logger.info(f"Starting portfolio value: {start_val:,.2f}")
|
||||
self._results = self.cerebro.run()
|
||||
end_val = self.cerebro.broker.getvalue()
|
||||
logger.info(f"Ending portfolio value: {end_val:,.2f}")
|
||||
return self._results
|
||||
|
||||
def get_results(self) -> Optional[list]:
|
||||
return self._results
|
||||
Reference in New Issue
Block a user