This commit is contained in:
Dobromir Popov
2025-05-30 01:38:04 +03:00
parent 5a30c5721d
commit 1130e02f35
9 changed files with 153 additions and 114 deletions

View File

@ -103,7 +103,12 @@ class WilliamsMarketStructure:
def calculate_recursive_pivot_points(self, ohlcv_data: np.ndarray) -> Dict[str, MarketStructureLevel]:
"""
Calculate 5 levels of recursive pivot points
Calculate 5 levels of recursive pivot points using TRUE recursion
Level 1: Calculated from 1s OHLCV data
Level 2: Calculated from Level 1 pivot points treated as individual price bars
Level 3: Calculated from Level 2 pivot points treated as individual price bars
etc.
Args:
ohlcv_data: OHLCV data array with columns [timestamp, open, high, low, close, volume]
@ -116,13 +121,18 @@ class WilliamsMarketStructure:
return self._create_empty_structure()
levels = {}
current_data = ohlcv_data.copy()
current_price_points = ohlcv_data.copy() # Start with raw price data
for level in range(self.max_levels):
logger.debug(f"Analyzing level {level} with {len(current_data)} data points")
logger.debug(f"Analyzing level {level} with {len(current_price_points)} data points")
# Find swing points for this level
swing_points = self._find_swing_points_multi_strength(current_data)
if level == 0:
# Level 0 (Level 1): Calculate from raw OHLCV data
swing_points = self._find_swing_points_multi_strength(current_price_points)
else:
# Level 1+ (Level 2+): Calculate from previous level's pivot points
# Treat pivot points as individual price bars
swing_points = self._find_pivot_points_from_pivot_points(current_price_points, level)
if len(swing_points) < self.min_swings_for_trend:
logger.debug(f"Not enough swings at level {level}: {len(swing_points)}")
@ -136,14 +146,14 @@ class WilliamsMarketStructure:
# Find support/resistance levels
support_levels, resistance_levels = self._find_support_resistance(
swing_points, current_data
swing_points, current_price_points if level == 0 else None
)
# Determine current market bias
current_bias = self._determine_market_bias(swing_points, trend_analysis)
# Detect structure breaks
structure_breaks = self._detect_structure_breaks(swing_points, current_data)
structure_breaks = self._detect_structure_breaks(swing_points, current_price_points if level == 0 else None)
# Create level data
levels[f'level_{level}'] = MarketStructureLevel(
@ -156,11 +166,11 @@ class WilliamsMarketStructure:
structure_breaks=structure_breaks
)
# Prepare data for next level (use swing points as input)
# Prepare data for next level: convert swing points to "price points"
if len(swing_points) >= 5:
current_data = self._convert_swings_to_ohlcv(swing_points)
if len(current_data) < 10:
logger.debug(f"Insufficient converted data for level {level + 1}")
current_price_points = self._convert_pivots_to_price_points(swing_points)
if len(current_price_points) < 10:
logger.debug(f"Insufficient pivot data for level {level + 1}")
break
else:
logger.debug(f"Not enough swings to continue to level {level + 1}")
@ -490,41 +500,89 @@ class WilliamsMarketStructure:
return structure_breaks
def _convert_swings_to_ohlcv(self, swing_points: List[SwingPoint]) -> np.ndarray:
"""Convert swing points to OHLCV format for next level analysis"""
def _find_pivot_points_from_pivot_points(self, pivot_array: np.ndarray, level: int) -> List[SwingPoint]:
"""
Find pivot points from previous level's pivot points
For Level 2+: A Level N low pivot is when a Level N-1 pivot low is surrounded
by higher Level N-1 pivot lows (and vice versa for highs)
Args:
pivot_array: Array of pivot points as [timestamp, price, price, price, price, 0] format
level: Current level being calculated
"""
swings = []
if len(pivot_array) < 5:
return swings
# Use configurable strength for higher levels (more conservative)
strength = min(2 + level, 5) # Level 1: 3 bars, Level 2: 4 bars, Level 3+: 5 bars
for i in range(strength, len(pivot_array) - strength):
current_price = pivot_array[i, 1] # Use the price from pivot point
current_timestamp = pivot_array[i, 0]
# Check for swing high (pivot high surrounded by lower pivot highs)
is_swing_high = True
for j in range(i - strength, i + strength + 1):
if j != i and pivot_array[j, 1] >= current_price:
is_swing_high = False
break
if is_swing_high:
swings.append(SwingPoint(
timestamp=datetime.fromtimestamp(current_timestamp) if current_timestamp > 1e9 else datetime.now(),
price=current_price,
index=i,
swing_type=SwingType.SWING_HIGH,
strength=strength,
volume=0.0 # Pivot points don't have volume
))
# Check for swing low (pivot low surrounded by higher pivot lows)
is_swing_low = True
for j in range(i - strength, i + strength + 1):
if j != i and pivot_array[j, 1] <= current_price:
is_swing_low = False
break
if is_swing_low:
swings.append(SwingPoint(
timestamp=datetime.fromtimestamp(current_timestamp) if current_timestamp > 1e9 else datetime.now(),
price=current_price,
index=i,
swing_type=SwingType.SWING_LOW,
strength=strength,
volume=0.0 # Pivot points don't have volume
))
return swings
def _convert_pivots_to_price_points(self, swing_points: List[SwingPoint]) -> np.ndarray:
"""
Convert swing points to price point array for next level calculation
Each swing point becomes a "price bar" where OHLC = pivot price
This allows the next level to treat pivot points as individual price data
"""
if len(swing_points) < 2:
return np.array([])
ohlcv_data = []
price_points = []
for i in range(len(swing_points) - 1):
current_swing = swing_points[i]
next_swing = swing_points[i + 1]
# Create synthetic OHLCV bar from swing to swing
if current_swing.swing_type == SwingType.SWING_HIGH:
# From high to next point
open_price = current_swing.price
high_price = current_swing.price
low_price = min(current_swing.price, next_swing.price)
close_price = next_swing.price
else:
# From low to next point
open_price = current_swing.price
high_price = max(current_swing.price, next_swing.price)
low_price = current_swing.price
close_price = next_swing.price
ohlcv_data.append([
current_swing.timestamp.timestamp(),
open_price,
high_price,
low_price,
close_price,
current_swing.volume
for swing in swing_points:
# Each pivot point becomes a price point where OHLC = pivot price
price_points.append([
swing.timestamp.timestamp(),
swing.price, # Open = pivot price
swing.price, # High = pivot price
swing.price, # Low = pivot price
swing.price, # Close = pivot price
0.0 # Volume = 0 (not applicable for pivot points)
])
return np.array(ohlcv_data)
return np.array(price_points)
def _create_empty_structure(self) -> Dict[str, MarketStructureLevel]:
"""Create empty structure when insufficient data"""