lowkey_backtest/backtest.py

71 lines
2.7 KiB
Python
Raw Normal View History

from __future__ import annotations
import pandas as pd
from pathlib import Path
from trade import TradeState, enter_long, exit_long, maybe_trailing_stop
from indicators import add_supertrends, compute_meta_trend
from metrics import compute_metrics
from logging_utils import write_trade_log
DEFAULT_ST_SETTINGS = [(12, 3.0), (10, 1.0), (11, 2.0)]
def backtest(
df: pd.DataFrame,
df_1min: pd.DataFrame,
timeframe_minutes: int,
stop_loss: float,
exit_on_bearish_flip: bool,
fee_bps: float,
slippage_bps: float,
log_path: Path | None = None,
):
df = add_supertrends(df, DEFAULT_ST_SETTINGS)
df["meta_bull"] = compute_meta_trend(df, DEFAULT_ST_SETTINGS)
state = TradeState(stop_loss_frac=stop_loss, fee_bps=fee_bps, slippage_bps=slippage_bps)
equity, trades = [], []
for i, row in df.iterrows():
price = float(row["Close"])
ts = pd.Timestamp(row["Timestamp"])
if state.qty <= 0 and row["meta_bull"] == 1:
evt = enter_long(state, price)
if evt:
evt.update({"t": ts.isoformat(), "reason": "bull_flip"})
trades.append(evt)
start = ts
end = df["Timestamp"].iat[i + 1] if i + 1 < len(df) else ts + pd.Timedelta(minutes=timeframe_minutes)
if state.qty > 0:
win = df_1min[(df_1min["Timestamp"] >= start) & (df_1min["Timestamp"] < end)]
for _, m in win.iterrows():
hi = float(m["High"])
lo = float(m["Low"])
state.max_px = max(state.max_px or hi, hi)
trail = state.max_px * (1.0 - state.stop_loss_frac)
if lo <= trail:
evt = exit_long(state, trail)
if evt:
prev = trades[-1]
pnl = (evt["price"] - (prev.get("price") or evt["price"])) * (prev.get("qty") or 0.0)
evt.update({"t": pd.Timestamp(m["Timestamp"]).isoformat(), "reason": "stop", "pnl": pnl})
trades.append(evt)
break
if state.qty > 0 and exit_on_bearish_flip and row["meta_bull"] == 0:
evt = exit_long(state, price)
if evt:
prev = trades[-1]
pnl = (evt["price"] - (prev.get("price") or evt["price"])) * (prev.get("qty") or 0.0)
evt.update({"t": ts.isoformat(), "reason": "bearish_flip", "pnl": pnl})
trades.append(evt)
equity.append(state.cash + state.qty * price)
equity_curve = pd.Series(equity, index=df["Timestamp"])
if log_path:
write_trade_log(trades, log_path)
perf = compute_metrics(equity_curve, trades)
return perf, equity_curve, trades