First_agent_template / backtest /vol_backtest.py
mathidot's picture
build option trading agent modules
8f1601b
Raw
History Blame Contribute Delete
2.89 kB
from __future__ import annotations
import math
import pandas as pd
def max_drawdown(equity: pd.Series) -> float:
if equity.empty:
return 0.0
running_max = equity.cummax()
drawdown = equity / running_max - 1
return float(drawdown.min())
def backtest_realized_vol_signal(
prices: pd.Series,
short_window: int = 10,
long_window: int = 30,
holding_days: int = 5,
signal: str = "long_vol",
) -> dict:
close = prices.dropna().astype(float)
returns = close.pct_change().dropna()
short_rv = returns.rolling(short_window).std() * math.sqrt(252)
long_rv = returns.rolling(long_window).std() * math.sqrt(252)
trades = []
equity = [1.0]
index = 0
dates = list(returns.index)
while index + holding_days < len(returns):
current_date = dates[index]
if pd.isna(short_rv.iloc[index]) or pd.isna(long_rv.iloc[index]):
index += 1
equity.append(equity[-1])
continue
vol_expanding = short_rv.iloc[index] > long_rv.iloc[index]
enter = vol_expanding if signal == "long_vol" else not vol_expanding
if not enter:
index += 1
equity.append(equity[-1])
continue
period_returns = returns.iloc[index + 1:index + 1 + holding_days]
realized_move = float(period_returns.abs().sum())
signed_pnl = realized_move if signal == "long_vol" else -realized_move
equity.append(equity[-1] * (1 + signed_pnl))
trades.append(
{
"entry_date": str(current_date),
"exit_date": str(dates[index + holding_days]),
"short_rv": float(short_rv.iloc[index]),
"long_rv": float(long_rv.iloc[index]),
"realized_abs_move": realized_move,
"pnl_proxy": signed_pnl,
}
)
index += holding_days
equity_series = pd.Series(equity)
wins = [trade for trade in trades if trade["pnl_proxy"] > 0]
return {
"signal": signal,
"short_window": short_window,
"long_window": long_window,
"holding_days": holding_days,
"trade_count": len(trades),
"win_rate": len(wins) / len(trades) if trades else 0.0,
"total_return_proxy": float(equity_series.iloc[-1] - 1) if not equity_series.empty else 0.0,
"max_drawdown_proxy": max_drawdown(equity_series),
"avg_trade_pnl_proxy": (
sum(trade["pnl_proxy"] for trade in trades) / len(trades)
if trades
else 0.0
),
"trades": trades[:100],
"limitations": [
"This is an underlying-price realized-volatility signal backtest, not a true option PnL backtest.",
"It does not use historical option-chain prices, bid/ask spreads, margin, assignment, or delta hedging costs.",
],
}