615 lines
20 KiB
Markdown
Raw Normal View History

2025-05-28 22:37:53 +08:00
# Oscillator Indicators
## Overview
Oscillator indicators help identify overbought and oversold conditions in the market. IncrementalTrader provides RSI (Relative Strength Index) implementations that measure the speed and magnitude of price changes.
## RSIState
Full RSI implementation using Wilder's smoothing method for accurate calculation.
### Features
- **Wilder's Smoothing**: Uses the traditional RSI calculation method
- **Overbought/Oversold**: Clear signals for market extremes
- **Momentum Measurement**: Indicates price momentum strength
- **Divergence Detection**: Helps identify potential trend reversals
### Mathematical Formula
```
RS = Average Gain / Average Loss
RSI = 100 - (100 / (1 + RS))
Where:
- Average Gain = Wilder's smoothing of positive price changes
- Average Loss = Wilder's smoothing of negative price changes
- Wilder's smoothing: ((previous_average × (period - 1)) + current_value) / period
```
### Class Definition
```python
from IncrementalTrader.strategies.indicators import RSIState
class RSIState(IndicatorState):
def __init__(self, period: int):
super().__init__(period)
self.gains = []
self.losses = []
self.avg_gain = 0.0
self.avg_loss = 0.0
self.previous_close = None
self.is_first_calculation = True
def update(self, value: float):
if self.previous_close is not None:
change = value - self.previous_close
gain = max(change, 0.0)
loss = max(-change, 0.0)
if self.is_first_calculation and len(self.gains) >= self.period:
# Initial calculation using simple average
self.avg_gain = sum(self.gains[-self.period:]) / self.period
self.avg_loss = sum(self.losses[-self.period:]) / self.period
self.is_first_calculation = False
elif not self.is_first_calculation:
# Wilder's smoothing
self.avg_gain = ((self.avg_gain * (self.period - 1)) + gain) / self.period
self.avg_loss = ((self.avg_loss * (self.period - 1)) + loss) / self.period
self.gains.append(gain)
self.losses.append(loss)
# Keep only necessary history
if len(self.gains) > self.period:
self.gains.pop(0)
self.losses.pop(0)
self.previous_close = value
self.data_count += 1
def get_value(self) -> float:
if not self.is_ready() or self.avg_loss == 0:
return 50.0 # Neutral RSI
rs = self.avg_gain / self.avg_loss
rsi = 100.0 - (100.0 / (1.0 + rs))
return rsi
def is_ready(self) -> bool:
return self.data_count > self.period and not self.is_first_calculation
```
### Usage Examples
#### Basic RSI Usage
```python
# Create 14-period RSI
rsi_14 = RSIState(period=14)
# Price data
prices = [44, 44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.85, 47.25, 47.92, 46.23, 44.18, 46.57, 46.61, 46.5]
for price in prices:
rsi_14.update(price)
if rsi_14.is_ready():
rsi_value = rsi_14.get_value()
print(f"Price: {price:.2f}, RSI(14): {rsi_value:.2f}")
```
#### RSI Trading Signals
```python
class RSISignals:
def __init__(self, period: int = 14, overbought: float = 70.0, oversold: float = 30.0):
self.rsi = RSIState(period)
self.overbought = overbought
self.oversold = oversold
self.previous_rsi = None
def update(self, price: float):
self.rsi.update(price)
def get_signal(self) -> str:
if not self.rsi.is_ready():
return "HOLD"
current_rsi = self.rsi.get_value()
# Oversold bounce signal
if (self.previous_rsi is not None and
self.previous_rsi <= self.oversold and
current_rsi > self.oversold):
signal = "BUY"
# Overbought pullback signal
elif (self.previous_rsi is not None and
self.previous_rsi >= self.overbought and
current_rsi < self.overbought):
signal = "SELL"
else:
signal = "HOLD"
self.previous_rsi = current_rsi
return signal
def get_condition(self) -> str:
"""Get current market condition based on RSI."""
if not self.rsi.is_ready():
return "UNKNOWN"
rsi_value = self.rsi.get_value()
if rsi_value >= self.overbought:
return "OVERBOUGHT"
elif rsi_value <= self.oversold:
return "OVERSOLD"
else:
return "NEUTRAL"
# Usage
rsi_signals = RSISignals(period=14, overbought=70, oversold=30)
for price in prices:
rsi_signals.update(price)
signal = rsi_signals.get_signal()
condition = rsi_signals.get_condition()
if signal != "HOLD":
print(f"RSI Signal: {signal}, Condition: {condition}, Price: {price:.2f}")
```
### Performance Characteristics
- **Time Complexity**: O(1) per update (after initial period)
- **Space Complexity**: O(period)
- **Memory Usage**: ~16 bytes per period + constant overhead
## SimpleRSIState
Simplified RSI implementation using exponential smoothing for memory efficiency.
### Features
- **O(1) Memory**: Constant memory usage regardless of period
- **Exponential Smoothing**: Uses EMA-based calculation
- **Fast Computation**: No need to maintain gain/loss history
- **Approximate RSI**: Close approximation to traditional RSI
### Mathematical Formula
```
Gain = max(price_change, 0)
Loss = max(-price_change, 0)
EMA_Gain = EMA(Gain, period)
EMA_Loss = EMA(Loss, period)
RSI = 100 - (100 / (1 + EMA_Gain / EMA_Loss))
```
### Class Definition
```python
class SimpleRSIState(IndicatorState):
def __init__(self, period: int):
super().__init__(period)
self.alpha = 2.0 / (period + 1)
self.ema_gain = 0.0
self.ema_loss = 0.0
self.previous_close = None
self.is_first_value = True
def update(self, value: float):
if self.previous_close is not None:
change = value - self.previous_close
gain = max(change, 0.0)
loss = max(-change, 0.0)
if self.is_first_value:
self.ema_gain = gain
self.ema_loss = loss
self.is_first_value = False
else:
self.ema_gain = (gain * self.alpha) + (self.ema_gain * (1 - self.alpha))
self.ema_loss = (loss * self.alpha) + (self.ema_loss * (1 - self.alpha))
self.previous_close = value
self.data_count += 1
def get_value(self) -> float:
if not self.is_ready() or self.ema_loss == 0:
return 50.0 # Neutral RSI
rs = self.ema_gain / self.ema_loss
rsi = 100.0 - (100.0 / (1.0 + rs))
return rsi
def is_ready(self) -> bool:
return self.data_count > 1 and not self.is_first_value
```
### Usage Examples
#### Memory-Efficient RSI
```python
# Create memory-efficient RSI
simple_rsi = SimpleRSIState(period=14)
# Process large amounts of data with constant memory
for i, price in enumerate(large_price_dataset):
simple_rsi.update(price)
if i % 1000 == 0 and simple_rsi.is_ready(): # Print every 1000 updates
print(f"RSI after {i} updates: {simple_rsi.get_value():.2f}")
```
#### RSI Divergence Detection
```python
class RSIDivergence:
def __init__(self, period: int = 14, lookback: int = 20):
self.rsi = SimpleRSIState(period)
self.lookback = lookback
self.price_history = []
self.rsi_history = []
def update(self, price: float):
self.rsi.update(price)
if self.rsi.is_ready():
self.price_history.append(price)
self.rsi_history.append(self.rsi.get_value())
# Keep only recent history
if len(self.price_history) > self.lookback:
self.price_history.pop(0)
self.rsi_history.pop(0)
def detect_bullish_divergence(self) -> bool:
"""Detect bullish divergence: price makes lower low, RSI makes higher low."""
if len(self.price_history) < self.lookback:
return False
# Find recent lows
price_low_idx = self.price_history.index(min(self.price_history[-10:]))
rsi_low_idx = self.rsi_history.index(min(self.rsi_history[-10:]))
# Check for divergence pattern
if (price_low_idx < len(self.price_history) - 3 and
rsi_low_idx < len(self.rsi_history) - 3):
recent_price_low = min(self.price_history[-3:])
recent_rsi_low = min(self.rsi_history[-3:])
# Bullish divergence: price lower low, RSI higher low
if (recent_price_low < self.price_history[price_low_idx] and
recent_rsi_low > self.rsi_history[rsi_low_idx]):
return True
return False
def detect_bearish_divergence(self) -> bool:
"""Detect bearish divergence: price makes higher high, RSI makes lower high."""
if len(self.price_history) < self.lookback:
return False
# Find recent highs
price_high_idx = self.price_history.index(max(self.price_history[-10:]))
rsi_high_idx = self.rsi_history.index(max(self.rsi_history[-10:]))
# Check for divergence pattern
if (price_high_idx < len(self.price_history) - 3 and
rsi_high_idx < len(self.rsi_history) - 3):
recent_price_high = max(self.price_history[-3:])
recent_rsi_high = max(self.rsi_history[-3:])
# Bearish divergence: price higher high, RSI lower high
if (recent_price_high > self.price_history[price_high_idx] and
recent_rsi_high < self.rsi_history[rsi_high_idx]):
return True
return False
# Usage
divergence_detector = RSIDivergence(period=14, lookback=20)
for price in price_data:
divergence_detector.update(price)
if divergence_detector.detect_bullish_divergence():
print(f"Bullish RSI divergence detected at price {price:.2f}")
if divergence_detector.detect_bearish_divergence():
print(f"Bearish RSI divergence detected at price {price:.2f}")
```
### Performance Characteristics
- **Time Complexity**: O(1) per update
- **Space Complexity**: O(1)
- **Memory Usage**: ~32 bytes (constant)
## Comparison: RSIState vs SimpleRSIState
| Aspect | RSIState | SimpleRSIState |
|--------|----------|----------------|
| **Memory Usage** | O(period) | O(1) |
| **Calculation Method** | Wilder's Smoothing | Exponential Smoothing |
| **Accuracy** | Higher (traditional) | Good (approximation) |
| **Responsiveness** | Standard | Slightly more responsive |
| **Historical Compatibility** | Traditional RSI | Modern approximation |
### When to Use RSIState
- **Precise Calculations**: When you need exact traditional RSI values
- **Backtesting**: For historical analysis and strategy validation
- **Research**: When studying exact RSI behavior and patterns
- **Small Periods**: When period is small (< 20) and memory isn't an issue
### When to Use SimpleRSIState
- **Memory Efficiency**: When processing large amounts of data
- **Real-time Systems**: For high-frequency trading applications
- **Approximate Analysis**: When close approximation is sufficient
- **Large Periods**: When using large RSI periods (> 50)
## Advanced Usage Patterns
### Multi-Timeframe RSI Analysis
```python
class MultiTimeframeRSI:
def __init__(self):
self.rsi_short = SimpleRSIState(period=7) # Short-term momentum
self.rsi_medium = SimpleRSIState(period=14) # Standard RSI
self.rsi_long = SimpleRSIState(period=21) # Long-term momentum
def update(self, price: float):
self.rsi_short.update(price)
self.rsi_medium.update(price)
self.rsi_long.update(price)
def get_momentum_regime(self) -> str:
"""Determine current momentum regime."""
if not all([self.rsi_short.is_ready(), self.rsi_medium.is_ready(), self.rsi_long.is_ready()]):
return "UNKNOWN"
short_rsi = self.rsi_short.get_value()
medium_rsi = self.rsi_medium.get_value()
long_rsi = self.rsi_long.get_value()
# All timeframes bullish
if all(rsi > 50 for rsi in [short_rsi, medium_rsi, long_rsi]):
return "STRONG_BULLISH"
# All timeframes bearish
elif all(rsi < 50 for rsi in [short_rsi, medium_rsi, long_rsi]):
return "STRONG_BEARISH"
# Mixed signals
elif short_rsi > 50 and medium_rsi > 50:
return "BULLISH"
elif short_rsi < 50 and medium_rsi < 50:
return "BEARISH"
else:
return "MIXED"
def get_overbought_oversold_consensus(self) -> str:
"""Get consensus on overbought/oversold conditions."""
if not all([self.rsi_short.is_ready(), self.rsi_medium.is_ready(), self.rsi_long.is_ready()]):
return "UNKNOWN"
rsi_values = [self.rsi_short.get_value(), self.rsi_medium.get_value(), self.rsi_long.get_value()]
overbought_count = sum(1 for rsi in rsi_values if rsi >= 70)
oversold_count = sum(1 for rsi in rsi_values if rsi <= 30)
if overbought_count >= 2:
return "OVERBOUGHT"
elif oversold_count >= 2:
return "OVERSOLD"
else:
return "NEUTRAL"
# Usage
multi_rsi = MultiTimeframeRSI()
for price in price_data:
multi_rsi.update(price)
regime = multi_rsi.get_momentum_regime()
consensus = multi_rsi.get_overbought_oversold_consensus()
print(f"Price: {price:.2f}, Momentum: {regime}, Condition: {consensus}")
```
### RSI with Dynamic Thresholds
```python
class AdaptiveRSI:
def __init__(self, period: int = 14, lookback: int = 50):
self.rsi = SimpleRSIState(period)
self.lookback = lookback
self.rsi_history = []
def update(self, price: float):
self.rsi.update(price)
if self.rsi.is_ready():
self.rsi_history.append(self.rsi.get_value())
# Keep only recent history
if len(self.rsi_history) > self.lookback:
self.rsi_history.pop(0)
def get_adaptive_thresholds(self) -> tuple:
"""Calculate adaptive overbought/oversold thresholds."""
if len(self.rsi_history) < 20:
return 70.0, 30.0 # Default thresholds
# Calculate percentiles for adaptive thresholds
sorted_rsi = sorted(self.rsi_history)
# Use 80th and 20th percentiles as adaptive thresholds
overbought_threshold = sorted_rsi[int(len(sorted_rsi) * 0.8)]
oversold_threshold = sorted_rsi[int(len(sorted_rsi) * 0.2)]
# Ensure minimum separation
if overbought_threshold - oversold_threshold < 20:
mid = (overbought_threshold + oversold_threshold) / 2
overbought_threshold = mid + 10
oversold_threshold = mid - 10
return overbought_threshold, oversold_threshold
def get_adaptive_signal(self) -> str:
"""Get signal using adaptive thresholds."""
if not self.rsi.is_ready() or len(self.rsi_history) < 2:
return "HOLD"
current_rsi = self.rsi.get_value()
previous_rsi = self.rsi_history[-2]
overbought, oversold = self.get_adaptive_thresholds()
# Adaptive oversold bounce
if previous_rsi <= oversold and current_rsi > oversold:
return "BUY"
# Adaptive overbought pullback
elif previous_rsi >= overbought and current_rsi < overbought:
return "SELL"
return "HOLD"
# Usage
adaptive_rsi = AdaptiveRSI(period=14, lookback=50)
for price in price_data:
adaptive_rsi.update(price)
signal = adaptive_rsi.get_adaptive_signal()
overbought, oversold = adaptive_rsi.get_adaptive_thresholds()
if signal != "HOLD":
print(f"Adaptive RSI Signal: {signal}, Thresholds: OB={overbought:.1f}, OS={oversold:.1f}")
```
## Integration with Strategies
### RSI Mean Reversion Strategy
```python
class RSIMeanReversionStrategy(IncStrategyBase):
def __init__(self, name: str, params: dict = None):
super().__init__(name, params)
# Initialize RSI
self.rsi = RSIState(self.params.get('rsi_period', 14))
# RSI parameters
self.overbought = self.params.get('overbought', 70.0)
self.oversold = self.params.get('oversold', 30.0)
self.exit_neutral = self.params.get('exit_neutral', 50.0)
# State tracking
self.previous_rsi = None
self.position_type = None
def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal:
open_price, high, low, close, volume = ohlcv
# Update RSI
self.rsi.update(close)
# Wait for RSI to be ready
if not self.rsi.is_ready():
return IncStrategySignal.HOLD()
current_rsi = self.rsi.get_value()
# Entry signals
if self.previous_rsi is not None:
# Oversold bounce (mean reversion up)
if (self.previous_rsi <= self.oversold and
current_rsi > self.oversold and
self.position_type != "LONG"):
confidence = min(0.9, (self.oversold - self.previous_rsi) / 20.0)
self.position_type = "LONG"
return IncStrategySignal.BUY(
confidence=confidence,
metadata={
'rsi': current_rsi,
'previous_rsi': self.previous_rsi,
'signal_type': 'oversold_bounce'
}
)
# Overbought pullback (mean reversion down)
elif (self.previous_rsi >= self.overbought and
current_rsi < self.overbought and
self.position_type != "SHORT"):
confidence = min(0.9, (self.previous_rsi - self.overbought) / 20.0)
self.position_type = "SHORT"
return IncStrategySignal.SELL(
confidence=confidence,
metadata={
'rsi': current_rsi,
'previous_rsi': self.previous_rsi,
'signal_type': 'overbought_pullback'
}
)
# Exit signals (return to neutral)
elif (self.position_type == "LONG" and current_rsi >= self.exit_neutral):
self.position_type = None
return IncStrategySignal.SELL(confidence=0.5, metadata={'signal_type': 'exit_long'})
elif (self.position_type == "SHORT" and current_rsi <= self.exit_neutral):
self.position_type = None
return IncStrategySignal.BUY(confidence=0.5, metadata={'signal_type': 'exit_short'})
self.previous_rsi = current_rsi
return IncStrategySignal.HOLD()
```
## Performance Optimization Tips
### 1. Choose the Right RSI Implementation
```python
# For memory-constrained environments
rsi = SimpleRSIState(period=14) # O(1) memory
# For precise traditional RSI
rsi = RSIState(period=14) # O(period) memory
```
### 2. Batch Processing for Multiple RSIs
```python
def update_multiple_rsis(rsis: list, price: float):
"""Efficiently update multiple RSI indicators."""
for rsi in rsis:
rsi.update(price)
return [rsi.get_value() for rsi in rsis if rsi.is_ready()]
```
### 3. Cache RSI Values for Complex Calculations
```python
class CachedRSI:
def __init__(self, period: int):
self.rsi = SimpleRSIState(period)
self._cached_value = 50.0
self._cache_valid = False
def update(self, price: float):
self.rsi.update(price)
self._cache_valid = False
def get_value(self) -> float:
if not self._cache_valid:
self._cached_value = self.rsi.get_value()
self._cache_valid = True
return self._cached_value
```
---
*RSI indicators are essential for identifying momentum and overbought/oversold conditions. Use RSIState for traditional analysis or SimpleRSIState for memory efficiency in high-frequency applications.*