from __future__ import annotations import argparse from pathlib import Path import pandas as pd from config import CLIConfig from data import load_data from backtest import backtest def parse_args() -> CLIConfig: p = argparse.ArgumentParser(prog="bt", description="Simple supertrend backtester") p.add_argument("start") p.add_argument("end") p.add_argument("--timeframe-minutes", type=int, default=15) # single TF p.add_argument("--timeframes-minutes", nargs="+", type=int) # multi TF: e.g. 5 15 60 240 p.add_argument("--stop-loss", dest="stop_losses", type=float, nargs="+", default=[0.02, 0.05]) p.add_argument("--exit-on-bearish-flip", action="store_true") p.add_argument("--csv", dest="csv_path", type=Path, required=True) p.add_argument("--out-csv", type=Path, default=Path("summary.csv")) p.add_argument("--log-dir", type=Path, default=Path("./logs")) p.add_argument("--fee-bps", type=float, default=10.0) p.add_argument("--slippage-bps", type=float, default=2.0) a = p.parse_args() return CLIConfig( start=a.start, end=a.end, timeframe_minutes=a.timeframe_minutes, timeframes_minutes=a.timeframes_minutes, stop_losses=a.stop_losses, exit_on_bearish_flip=a.exit_on_bearish_flip, csv_path=a.csv_path, out_csv=a.out_csv, log_dir=a.log_dir, fee_bps=a.fee_bps, slippage_bps=a.slippage_bps, ) def main(): cfg = parse_args() frames = cfg.timeframes_minutes or [cfg.timeframe_minutes] rows: list[dict] = [] for tfm in frames: df_1min, df = load_data(cfg.start, cfg.end, tfm, cfg.csv_path) for sl in cfg.stop_losses: log_path = cfg.log_dir / f"{tfm}m_sl{sl:.2%}.csv" perf, equity, _ = backtest( df=df, df_1min=df_1min, timeframe_minutes=tfm, stop_loss=sl, exit_on_bearish_flip=cfg.exit_on_bearish_flip, fee_bps=cfg.fee_bps, slippage_bps=cfg.slippage_bps, log_path=log_path, ) rows.append({ "timeframe": f"{tfm}min", "stop_loss": sl, "exit_on_bearish_flip": cfg.exit_on_bearish_flip, "total_return": f"{perf.total_return:.2%}", "max_drawdown": f"{perf.max_drawdown:.2%}", "sharpe_ratio": f"{perf.sharpe_ratio:.2f}", "win_rate": f"{perf.win_rate:.2%}", "num_trades": perf.num_trades, "final_equity": f"${perf.final_equity:.2f}", "initial_equity": f"${perf.initial_equity:.2f}", "num_stop_losses": perf.num_stop_losses, "total_fees": perf.total_fees, "total_slippage_usd": perf.total_slippage_usd, "avg_slippage_bps": perf.avg_slippage_bps, }) out = pd.DataFrame(rows) out.to_csv(cfg.out_csv, index=False) print(out.to_string(index=False)) if __name__ == "__main__": main()