"""RSI mean-reversion — predefined Python strategy.""" from __future__ import annotations import itertools import pandas as pd import vectorbt as vbt STRATEGY_KEY = "rsi_reversion" DISPLAY_NAME = "RSI Mean Reversion" DESCRIPTION = "Buy when RSI is oversold; sell when RSI is overbought." PARAM_GRID = { "rsi_period": list(range(7, 22, 2)), "oversold": list(range(20, 36, 5)), "overbought": list(range(65, 81, 5)), } DEFAULT_PARAMS = { "rsi_period": 14, "oversold": 30, "overbought": 70, } def generate_signals( close: pd.Series, high: pd.Series, low: pd.Series, volume: pd.Series, rsi_period: int = 14, oversold: float = 30, overbought: float = 70, **_kwargs, ) -> tuple[pd.Series, pd.Series]: if oversold >= overbought: raise ValueError("oversold must be less than overbought") rsi = vbt.RSI.run(close, window=rsi_period).rsi entries = (rsi < oversold).fillna(False) exits = (rsi > overbought).fillna(False) return entries, exits def optimize_grid( close: pd.Series, param_grid: dict | None = None, init_cash: float = 10_000.0, fees: float = 0.001, metric: str = "sharpe_ratio", ) -> pd.DataFrame: """Exhaustive grid over RSI parameter space.""" from metrics import run_from_signals grid = param_grid or PARAM_GRID keys = list(grid.keys()) rows = [] for values in itertools.product(*(grid[k] for k in keys)): params = dict(zip(keys, values)) if params["oversold"] >= params["overbought"]: continue entries, exits = generate_signals(close, close, close, close, **params) result = run_from_signals( close=close, entries=entries, exits=exits, init_cash=init_cash, fees=fees, params=params, metric=metric, ) rows.append(result) frame = pd.DataFrame(rows) if frame.empty: return frame return frame.sort_values("score", ascending=False, na_position="last")