quant-web/strategies/builtin/rsi_reversion.py
epistemophiliac 627b2326df Add Python strategy engine, parameter optimization, and faster Docker builds.
Support builtin and custom generate_signals strategies with SQLite persistence, exhaustive grid scans (VectorBT comb optimization for MA crossover), professional backtest/optimize UI, and split harvester/app requirements with BuildKit pip cache.
2026-06-19 01:29:28 -04:00

79 lines
2 KiB
Python

"""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")