81 lines
3.0 KiB
Python
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()
|