Add initial implementation of backtesting framework with CLI interface. Introduce core modules for data loading, trade management, performance metrics, and logging. Include Supertrend indicator calculations and slippage estimation. Update .gitignore to exclude logs and CSV files.
This commit is contained in:
80
cli.py
Normal file
80
cli.py
Normal file
@@ -0,0 +1,80 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user