File size: 2,889 Bytes
8f1601b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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.",
        ],
    }