Skip to content

Commit 8cdf66c

Browse files
committed
[Strategy] Add RSI Reversion Strategy implementation
1 parent 5b9c2e4 commit 8cdf66c

3 files changed

Lines changed: 90 additions & 1 deletion

File tree

src/quanteval/strategies/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from quanteval.strategies.bollinger_mean_reversion import BollingerMeanReversionStrategy
55
from quanteval.strategies.buy_hold import BuyAndHoldStrategy
66
from quanteval.strategies.dual_thrust import DualThrustStrategy
7+
from quanteval.strategies.rsi_reversion import RSIStrategy
78

89
__all__ = [
910
'DualMAStrategy',
1011
'BollingerMeanReversionStrategy',
1112
'BuyAndHoldStrategy',
1213
'DualThrustStrategy',
14+
'RSIStrategy',
1315
]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
RSI Reversion Strategy - RSI 反转策略
3+
Classic mean-reversion strategy using RSI overbought/oversold signals.
4+
"""
5+
6+
import pandas as pd
7+
import numpy as np
8+
from quanteval.core.strategy import Strategy
9+
10+
11+
class RSIStrategy(Strategy):
12+
"""
13+
RSI 反转策略 - 经典均值回归策略
14+
15+
RSI Mean Reversion Strategy.
16+
17+
Strategy Logic:
18+
- Buy: When RSI < oversold (market oversold)
19+
- Sell: When RSI > overbought (market overbought)
20+
21+
Args:
22+
window: RSI 计算窗口 (RSI window, default 14)
23+
oversold: 超卖阈值 (Oversold level, default 30)
24+
overbought: 超买阈值 (Overbought level, default 70)
25+
26+
Example:
27+
>>> strategy = RSIStrategy(window=14, oversold=30, overbought=70)
28+
>>> bt = Backtester(strategy, data)
29+
>>> results = bt.run()
30+
"""
31+
32+
def __init__(self, window: int = 14, oversold: float = 30, overbought: float = 70):
33+
super().__init__(
34+
name=f'RSI({window})',
35+
window=window,
36+
oversold=oversold,
37+
overbought=overbought,
38+
)
39+
40+
def generate_signals(self, data: pd.DataFrame) -> pd.Series:
41+
"""
42+
生成交易信号
43+
44+
Generate trading signals based on RSI levels.
45+
46+
Returns:
47+
Series with values:
48+
1: Long position (RSI < oversold)
49+
0: No position (RSI >= oversold)
50+
"""
51+
52+
window = self.params['window']
53+
oversold = self.params['oversold']
54+
overbought = self.params['overbought']
55+
56+
close = data['Close']
57+
58+
# Calculate price change
59+
delta = close.diff()
60+
61+
# Separate gains and losses
62+
gain = delta.clip(lower=0)
63+
loss = -delta.clip(upper=0)
64+
65+
# Calculate rolling averages
66+
avg_gain = gain.rolling(window=window).mean()
67+
avg_loss = loss.rolling(window=window).mean()
68+
69+
# Calculate RS
70+
rs = avg_gain / avg_loss
71+
72+
# Calculate RSI
73+
rsi = 100 - (100 / (1 + rs))
74+
75+
signal = pd.Series(np.nan, index=data.index, name='Signal')
76+
signal[rsi < oversold] = 1
77+
signal[rsi > overbought] = 0
78+
79+
return signal.ffill().fillna(0)

tests/test_strategies.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
BuyAndHoldStrategy,
66
DualMAStrategy,
77
DualThrustStrategy,
8+
RSIStrategy,
89
)
910

1011

@@ -24,10 +25,17 @@ def test_bollinger_strategy_returns_position_series(sample_market_data: pd.DataF
2425
sample_market_data
2526
)
2627
assert signal.index.equals(sample_market_data.index)
27-
assert set(signal.unique()).issubset({0.0, 1.0})
28+
assert set(signal.unique()).issubset({0, 1})
2829

2930

3031
def test_dual_thrust_returns_binary_signal(sample_market_data: pd.DataFrame) -> None:
3132
signal = DualThrustStrategy(k1=0.5, k2=0.5, window=5).generate_signals(sample_market_data)
3233
assert isinstance(signal, pd.Series)
3334
assert set(signal.dropna().unique()).issubset({0, 1})
35+
36+
37+
def test_rsi_returns_binary_signal(sample_market_data: pd.DataFrame) -> None:
38+
signal = RSIStrategy(window=14, oversold=30, overbought=70).generate_signals(sample_market_data)
39+
assert isinstance(signal, pd.Series)
40+
assert signal.index.equals(sample_market_data.index)
41+
assert set(signal.dropna().unique()).issubset({0, 1})

0 commit comments

Comments
 (0)