Files
zhutoutoutousan 98a87a69ca Update
2026-02-13 08:03:25 +01:00

397 lines
17 KiB
Python

"""
RSI Divergence Detection Module
Detects regular and hidden RSI divergences in price action.
Regular Divergence:
- Bullish: Price makes lower low, RSI makes higher low (reversal signal)
- Bearish: Price makes higher high, RSI makes lower high (reversal signal)
Hidden Divergence:
- Bullish: Price makes higher low, RSI makes lower low (continuation signal)
- Bearish: Price makes lower high, RSI makes higher high (continuation signal)
"""
import numpy as np
import pandas as pd
from typing import Tuple, Optional, List, Dict
from dataclasses import dataclass
from enum import Enum
class DivergenceType(Enum):
"""Types of RSI divergences"""
NONE = 0
REGULAR_BULLISH = 1 # Price lower low, RSI higher low
REGULAR_BEARISH = 2 # Price higher high, RSI lower high
HIDDEN_BULLISH = 3 # Price higher low, RSI lower low
HIDDEN_BEARISH = 4 # Price lower high, RSI higher high
@dataclass
class DivergenceSignal:
"""Represents a detected divergence signal"""
type: DivergenceType
price_swing_start: int # Index of price swing start
price_swing_end: int # Index of price swing end
rsi_swing_start: int # Index of RSI swing start
rsi_swing_end: int # Index of RSI swing end
price_start: float # Price at swing start
price_end: float # Price at swing end
rsi_start: float # RSI at swing start
rsi_end: float # RSI at swing end
strength: float # Divergence strength (0-1)
confidence: float # Confidence score (0-1)
timestamp: pd.Timestamp
class RSIDivergenceDetector:
"""
Detects RSI divergences in price data.
"""
def __init__(self, rsi_period: int = 14, min_swing_bars: int = 5,
max_swing_bars: int = 50, min_divergence_strength: float = 0.1):
"""
Initialize the RSI divergence detector.
Args:
rsi_period: Period for RSI calculation
min_swing_bars: Minimum bars for a valid swing
max_swing_bars: Maximum bars to look back for swings
min_divergence_strength: Minimum strength for valid divergence
"""
self.rsi_period = rsi_period
self.min_swing_bars = min_swing_bars
self.max_swing_bars = max_swing_bars
self.min_divergence_strength = min_divergence_strength
def calculate_rsi(self, prices: pd.Series, period: int = None) -> pd.Series:
"""
Calculate RSI indicator.
Args:
prices: Price series (typically close prices)
period: RSI period (defaults to self.rsi_period)
Returns:
RSI values
"""
if period is None:
period = self.rsi_period
delta = prices.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
# Avoid division by zero
rs = gain / (loss + 1e-10)
rsi = 100 - (100 / (1 + rs))
return rsi
def find_swings(self, data: pd.Series, lookback: int = None) -> Tuple[List[int], List[int]]:
"""
Find swing highs and lows in the data.
Args:
data: Series to find swings in (price or RSI)
lookback: Number of bars to look back (defaults to max_swing_bars)
Returns:
Tuple of (swing_highs, swing_lows) - lists of indices
"""
if lookback is None:
lookback = self.max_swing_bars
swing_highs = []
swing_lows = []
for i in range(lookback, len(data) - lookback):
# Check for swing high
is_swing_high = True
for j in range(i - lookback, i + lookback + 1):
if j != i and data.iloc[j] >= data.iloc[i]:
is_swing_high = False
break
if is_swing_high:
swing_highs.append(i)
# Check for swing low
is_swing_low = True
for j in range(i - lookback, i + lookback + 1):
if j != i and data.iloc[j] <= data.iloc[i]:
is_swing_low = False
break
if is_swing_low:
swing_lows.append(i)
return swing_highs, swing_lows
def detect_divergence(self, df: pd.DataFrame, current_index: int) -> Optional[DivergenceSignal]:
"""
Detect divergence at the current index.
Args:
df: DataFrame with 'close' and 'rsi' columns
current_index: Current bar index to check for divergence
Returns:
DivergenceSignal if found, None otherwise
"""
if current_index < self.max_swing_bars * 2:
return None
# Get price and RSI data up to current index
price_data = df['close'].iloc[:current_index + 1]
rsi_data = df['rsi'].iloc[:current_index + 1]
# Find recent swings
price_highs, price_lows = self.find_swings(price_data, self.max_swing_bars)
rsi_highs, rsi_lows = self.find_swings(rsi_data, self.max_swing_bars)
if len(price_highs) < 2 or len(price_lows) < 2:
return None
if len(rsi_highs) < 2 or len(rsi_lows) < 2:
return None
# Get the two most recent swings
current_price = price_data.iloc[current_index]
current_rsi = rsi_data.iloc[current_index]
# Check for regular bearish divergence (price higher high, RSI lower high)
if len(price_highs) >= 2 and len(rsi_highs) >= 2:
price_high_1_idx = price_highs[-1]
price_high_2_idx = price_highs[-2] if len(price_highs) >= 2 else price_highs[-1]
rsi_high_1_idx = rsi_highs[-1]
rsi_high_2_idx = rsi_highs[-2] if len(rsi_highs) >= 2 else rsi_highs[-1]
# Regular bearish: price higher high, RSI lower high
if (price_high_1_idx == current_index or abs(price_high_1_idx - current_index) <= 3):
if price_data.iloc[price_high_1_idx] > price_data.iloc[price_high_2_idx]:
if rsi_data.iloc[rsi_high_1_idx] < rsi_data.iloc[rsi_high_2_idx]:
strength = self._calculate_strength(
price_data.iloc[price_high_2_idx], price_data.iloc[price_high_1_idx],
rsi_data.iloc[rsi_high_2_idx], rsi_data.iloc[rsi_high_1_idx]
)
if strength >= self.min_divergence_strength:
return DivergenceSignal(
type=DivergenceType.REGULAR_BEARISH,
price_swing_start=price_high_2_idx,
price_swing_end=price_high_1_idx,
rsi_swing_start=rsi_high_2_idx,
rsi_swing_end=rsi_high_1_idx,
price_start=price_data.iloc[price_high_2_idx],
price_end=price_data.iloc[price_high_1_idx],
rsi_start=rsi_data.iloc[rsi_high_2_idx],
rsi_end=rsi_data.iloc[rsi_high_1_idx],
strength=strength,
confidence=self._calculate_confidence(df, price_high_1_idx, DivergenceType.REGULAR_BEARISH),
timestamp=df.index[current_index]
)
# Check for regular bullish divergence (price lower low, RSI higher low)
if len(price_lows) >= 2 and len(rsi_lows) >= 2:
price_low_1_idx = price_lows[-1]
price_low_2_idx = price_lows[-2] if len(price_lows) >= 2 else price_lows[-1]
rsi_low_1_idx = rsi_lows[-1]
rsi_low_2_idx = rsi_lows[-2] if len(rsi_lows) >= 2 else rsi_lows[-1]
# Regular bullish: price lower low, RSI higher low
if (price_low_1_idx == current_index or abs(price_low_1_idx - current_index) <= 3):
if price_data.iloc[price_low_1_idx] < price_data.iloc[price_low_2_idx]:
if rsi_data.iloc[rsi_low_1_idx] > rsi_data.iloc[rsi_low_2_idx]:
strength = self._calculate_strength(
price_data.iloc[price_low_2_idx], price_data.iloc[price_low_1_idx],
rsi_data.iloc[rsi_low_2_idx], rsi_data.iloc[rsi_low_1_idx],
reverse=True
)
if strength >= self.min_divergence_strength:
return DivergenceSignal(
type=DivergenceType.REGULAR_BULLISH,
price_swing_start=price_low_2_idx,
price_swing_end=price_low_1_idx,
rsi_swing_start=rsi_low_2_idx,
rsi_swing_end=rsi_low_1_idx,
price_start=price_data.iloc[price_low_2_idx],
price_end=price_data.iloc[price_low_1_idx],
rsi_start=rsi_data.iloc[rsi_low_2_idx],
rsi_end=rsi_data.iloc[rsi_low_1_idx],
strength=strength,
confidence=self._calculate_confidence(df, price_low_1_idx, DivergenceType.REGULAR_BULLISH),
timestamp=df.index[current_index]
)
# Check for hidden bearish divergence (price lower high, RSI higher high)
if len(price_highs) >= 2 and len(rsi_highs) >= 2:
price_high_1_idx = price_highs[-1]
price_high_2_idx = price_highs[-2] if len(price_highs) >= 2 else price_highs[-1]
rsi_high_1_idx = rsi_highs[-1]
rsi_high_2_idx = rsi_highs[-2] if len(rsi_highs) >= 2 else rsi_highs[-1]
# Hidden bearish: price lower high, RSI higher high
if (price_high_1_idx == current_index or abs(price_high_1_idx - current_index) <= 3):
if price_data.iloc[price_high_1_idx] < price_data.iloc[price_high_2_idx]:
if rsi_data.iloc[rsi_high_1_idx] > rsi_data.iloc[rsi_high_2_idx]:
strength = self._calculate_strength(
price_data.iloc[price_high_2_idx], price_data.iloc[price_high_1_idx],
rsi_data.iloc[rsi_high_2_idx], rsi_data.iloc[rsi_high_1_idx]
)
if strength >= self.min_divergence_strength:
return DivergenceSignal(
type=DivergenceType.HIDDEN_BEARISH,
price_swing_start=price_high_2_idx,
price_swing_end=price_high_1_idx,
rsi_swing_start=rsi_high_2_idx,
rsi_swing_end=rsi_high_1_idx,
price_start=price_data.iloc[price_high_2_idx],
price_end=price_data.iloc[price_high_1_idx],
rsi_start=rsi_data.iloc[rsi_high_2_idx],
rsi_end=rsi_data.iloc[rsi_high_1_idx],
strength=strength,
confidence=self._calculate_confidence(df, price_high_1_idx, DivergenceType.HIDDEN_BEARISH),
timestamp=df.index[current_index]
)
# Check for hidden bullish divergence (price higher low, RSI lower low)
if len(price_lows) >= 2 and len(rsi_lows) >= 2:
price_low_1_idx = price_lows[-1]
price_low_2_idx = price_lows[-2] if len(price_lows) >= 2 else price_lows[-1]
rsi_low_1_idx = rsi_lows[-1]
rsi_low_2_idx = rsi_lows[-2] if len(rsi_lows) >= 2 else rsi_lows[-1]
# Hidden bullish: price higher low, RSI lower low
if (price_low_1_idx == current_index or abs(price_low_1_idx - current_index) <= 3):
if price_data.iloc[price_low_1_idx] > price_data.iloc[price_low_2_idx]:
if rsi_data.iloc[rsi_low_1_idx] < rsi_data.iloc[rsi_low_2_idx]:
strength = self._calculate_strength(
price_data.iloc[price_low_2_idx], price_data.iloc[price_low_1_idx],
rsi_data.iloc[rsi_low_2_idx], rsi_data.iloc[rsi_low_1_idx],
reverse=True
)
if strength >= self.min_divergence_strength:
return DivergenceSignal(
type=DivergenceType.HIDDEN_BULLISH,
price_swing_start=price_low_2_idx,
price_swing_end=price_low_1_idx,
rsi_swing_start=rsi_low_2_idx,
rsi_swing_end=rsi_low_1_idx,
price_start=price_data.iloc[price_low_2_idx],
price_end=price_data.iloc[price_low_1_idx],
rsi_start=rsi_data.iloc[rsi_low_2_idx],
rsi_end=rsi_data.iloc[rsi_low_1_idx],
strength=strength,
confidence=self._calculate_confidence(df, price_low_1_idx, DivergenceType.HIDDEN_BULLISH),
timestamp=df.index[current_index]
)
return None
def _calculate_strength(self, price1: float, price2: float,
rsi1: float, rsi2: float, reverse: bool = False) -> float:
"""
Calculate divergence strength (0-1).
Args:
price1: First price value
price2: Second price value
rsi1: First RSI value
rsi2: Second RSI value
reverse: If True, reverse the calculation for bullish divergences
Returns:
Strength score (0-1)
"""
if price1 == 0 or price2 == 0:
return 0.0
price_change_pct = abs((price2 - price1) / price1)
rsi_change = abs(rsi2 - rsi1)
# Normalize to 0-1 range
price_strength = min(price_change_pct * 10, 1.0) # Scale price change
rsi_strength = min(rsi_change / 20.0, 1.0) # Scale RSI change (max ~20 points)
# Combined strength
strength = (price_strength + rsi_strength) / 2.0
return min(max(strength, 0.0), 1.0)
def _calculate_confidence(self, df: pd.DataFrame, signal_index: int,
divergence_type: DivergenceType) -> float:
"""
Calculate confidence score for a divergence signal.
Args:
df: DataFrame with market data
signal_index: Index where divergence was detected
divergence_type: Type of divergence
Returns:
Confidence score (0-1)
"""
confidence = 0.5 # Base confidence
# Check RSI extremes
if signal_index < len(df):
rsi = df['rsi'].iloc[signal_index]
# Higher confidence if RSI is in extreme zones
if divergence_type in [DivergenceType.REGULAR_BULLISH, DivergenceType.HIDDEN_BULLISH]:
if rsi < 30:
confidence += 0.2
elif rsi < 40:
confidence += 0.1
elif divergence_type in [DivergenceType.REGULAR_BEARISH, DivergenceType.HIDDEN_BEARISH]:
if rsi > 70:
confidence += 0.2
elif rsi > 60:
confidence += 0.1
# Check volume (if available)
if 'tick_volume' in df.columns and signal_index < len(df):
volume = df['tick_volume'].iloc[signal_index]
avg_volume = df['tick_volume'].rolling(20).mean().iloc[signal_index] if signal_index >= 20 else volume
if avg_volume > 0:
volume_ratio = volume / avg_volume
if volume_ratio > 1.2: # Higher volume increases confidence
confidence += 0.1
return min(max(confidence, 0.0), 1.0)
def label_data(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Label entire dataset with divergence signals.
Args:
df: DataFrame with 'close' column and datetime index
Returns:
DataFrame with 'divergence_type' and 'divergence_confidence' columns
"""
# Calculate RSI
if 'rsi' not in df.columns:
df['rsi'] = self.calculate_rsi(df['close'], self.rsi_period)
# Initialize labels
df['divergence_type'] = DivergenceType.NONE.value
df['divergence_confidence'] = 0.0
df['divergence_strength'] = 0.0
# Detect divergences at each point
for i in range(self.max_swing_bars * 2, len(df)):
signal = self.detect_divergence(df, i)
if signal:
df.loc[df.index[i], 'divergence_type'] = signal.type.value
df.loc[df.index[i], 'divergence_confidence'] = signal.confidence
df.loc[df.index[i], 'divergence_strength'] = signal.strength
return df