CiscsoPonce's picture
Initial Deploy (Clean)
a2cbcac
from __future__ import annotations
from typing import Any, Dict, Tuple, Type
import backtrader as bt
DEFAULT_CASH = 100000
DEFAULT_COMMISSION = 0
def create_cerebro(cash: float = DEFAULT_CASH, commission: float = DEFAULT_COMMISSION) -> bt.Cerebro:
"""Create and configure Backtrader Cerebro engine with analyzers."""
cerebro = bt.Cerebro()
cerebro.broker.setcash(cash)
cerebro.broker.setcommission(commission=commission)
cerebro.addanalyzer(bt.analyzers.PyFolio, _name="pyfolio")
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trades")
return cerebro
def run_backtest(
ohlc_data,
strategy_class: Type[bt.Strategy],
strategy_name: str,
**strategy_params: Any,
) -> Tuple[Dict[str, Any], bt.Cerebro]:
"""Run a backtest with given strategy; return metrics and the Cerebro instance."""
cerebro = create_cerebro()
cerebro.addstrategy(strategy_class, **strategy_params)
data_params = {
"dataname": ohlc_data.set_index("Date"),
"datetime": None,
"open": "Open",
"high": "High",
"low": "Low",
"close": "Close",
"volume": "Volume",
"openinterest": None,
}
data = bt.feeds.PandasData(**data_params)
cerebro.adddata(data)
results = cerebro.run()
final_value = cerebro.broker.getvalue()
strat = results[0]
pyfolio_analyzer = strat.analyzers.pyfolio
pf_items = pyfolio_analyzer.get_pf_items()
# Some versions may return 3 or 4 items; we only need returns
returns_series = pf_items[0]
cumulative_return = (returns_series + 1).prod() - 1
cumulative_return_pct = cumulative_return * 100
# Manual metrics
annual_volatility = returns_series.std() * (252 ** 0.5) * 100
running_max = (1 + returns_series).cumprod().cummax()
drawdown = (1 + returns_series).cumprod() / running_max - 1
max_drawdown = abs(drawdown.min()) * 100
excess_returns = returns_series - 0.02 / 252 # assume 2% risk-free
sharpe_ratio = (
excess_returns.mean() / returns_series.std() * (252 ** 0.5)
if returns_series.std() != 0
else 0
)
trades = strat.analyzers.trades.get_analysis()
total_closed_trades = trades.get("total", {}).get("closed", 0) if isinstance(trades.get("total", {}), dict) else 0
total_trades = total_closed_trades if total_closed_trades > 0 else getattr(strat, "order_count", 0)
results_dict: Dict[str, Any] = {
"Final Value": final_value,
"Cumulative Return [%]": cumulative_return_pct,
"Annual Volatility [%]": annual_volatility,
"Max Drawdown [%]": max_drawdown,
"Sharpe Ratio": sharpe_ratio,
"Total Trades": total_trades,
"Strategy": strategy_name,
}
return results_dict, cerebro