81 lines
3.0 KiB
Python

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()