use common williams market structure calcs
This commit is contained in:
@@ -19,16 +19,19 @@ import logging
|
||||
from datetime import datetime
|
||||
import json
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
# Import core components from main system
|
||||
try:
|
||||
from core.data_provider import DataProvider
|
||||
from core.orchestrator import TradingOrchestrator
|
||||
from core.config import get_config
|
||||
from core.williams_market_structure import WilliamsMarketStructure
|
||||
except ImportError as e:
|
||||
print(f"Warning: Could not import main system components: {e}")
|
||||
print("Running in standalone mode with limited functionality")
|
||||
DataProvider = None
|
||||
WilliamsMarketStructure = None
|
||||
TradingOrchestrator = None
|
||||
get_config = lambda: {}
|
||||
|
||||
@@ -200,66 +203,90 @@ class AnnotationDashboard:
|
||||
|
||||
def _get_pivot_markers_for_timeframe(self, symbol: str, timeframe: str, df: pd.DataFrame) -> dict:
|
||||
"""
|
||||
Get pivot markers for a specific timeframe
|
||||
Returns dict with indices mapping to pivot info for that candle
|
||||
Get pivot markers for a specific timeframe using WilliamsMarketStructure directly
|
||||
Returns dict with all pivot points and identifies which are the last high/low per level
|
||||
"""
|
||||
try:
|
||||
if not self.data_provider:
|
||||
if WilliamsMarketStructure is None:
|
||||
logger.warning("WilliamsMarketStructure not available")
|
||||
return {}
|
||||
|
||||
# Get Williams pivot levels for this timeframe
|
||||
pivot_levels = self.data_provider.get_williams_pivot_levels(
|
||||
symbol=symbol,
|
||||
base_timeframe=timeframe,
|
||||
limit=len(df)
|
||||
if df is None or len(df) < 10:
|
||||
logger.warning(f"Insufficient data for pivot calculation: {len(df) if df is not None else 0} bars")
|
||||
return {}
|
||||
|
||||
# Convert DataFrame to numpy array format expected by Williams Market Structure
|
||||
ohlcv_array = df[['open', 'high', 'low', 'close', 'volume']].copy()
|
||||
|
||||
# Add timestamp as first column (convert to milliseconds)
|
||||
timestamps = df.index.astype(np.int64) // 10**6 # pandas index is ns -> convert to ms
|
||||
ohlcv_array.insert(0, 'timestamp', timestamps)
|
||||
ohlcv_array = ohlcv_array.to_numpy()
|
||||
|
||||
# Initialize Williams Market Structure with default distance
|
||||
# We'll override it in the calculation call
|
||||
williams = WilliamsMarketStructure(min_pivot_distance=1)
|
||||
|
||||
# Calculate recursive pivot points with min_pivot_distance=2
|
||||
# This ensures 5 candles per pivot (tip + 2 prev + 2 next)
|
||||
pivot_levels = williams.calculate_recursive_pivot_points(
|
||||
ohlcv_array,
|
||||
min_pivot_distance=2
|
||||
)
|
||||
|
||||
if not pivot_levels:
|
||||
logger.debug(f"No pivot levels found for {symbol} {timeframe}")
|
||||
return {}
|
||||
|
||||
# Build a map of timestamp -> pivot info
|
||||
# Also track last high/low per level for drawing horizontal lines
|
||||
pivot_map = {}
|
||||
last_pivots = {} # {level: {'high': (ts_str, idx), 'low': (ts_str, idx)}}
|
||||
|
||||
# For each level (1-5), find the last high and last low pivot
|
||||
# For each level (1-5), collect ALL pivot points
|
||||
for level_num, trend_level in pivot_levels.items():
|
||||
if not hasattr(trend_level, 'pivot_points') or not trend_level.pivot_points:
|
||||
continue
|
||||
|
||||
# Find last high and last low for this level
|
||||
last_high = None
|
||||
last_low = None
|
||||
last_pivots[level_num] = {'high': None, 'low': None}
|
||||
|
||||
# Add ALL pivot points to the map
|
||||
for pivot in trend_level.pivot_points:
|
||||
ts_str = pivot.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
if ts_str not in pivot_map:
|
||||
pivot_map[ts_str] = {'highs': [], 'lows': []}
|
||||
|
||||
pivot_info = {
|
||||
'level': level_num,
|
||||
'price': pivot.price,
|
||||
'strength': pivot.strength,
|
||||
'is_last': False # Will be updated below
|
||||
}
|
||||
|
||||
if pivot.pivot_type == 'high':
|
||||
last_high = pivot
|
||||
pivot_map[ts_str]['highs'].append(pivot_info)
|
||||
last_pivots[level_num]['high'] = (ts_str, len(pivot_map[ts_str]['highs']) - 1)
|
||||
elif pivot.pivot_type == 'low':
|
||||
last_low = pivot
|
||||
|
||||
# Add to pivot map
|
||||
if last_high:
|
||||
ts_str = last_high.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||||
if ts_str not in pivot_map:
|
||||
pivot_map[ts_str] = {'highs': [], 'lows': []}
|
||||
pivot_map[ts_str]['highs'].append({
|
||||
'level': level_num,
|
||||
'price': last_high.price,
|
||||
'strength': last_high.strength
|
||||
})
|
||||
|
||||
if last_low:
|
||||
ts_str = last_low.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||||
if ts_str not in pivot_map:
|
||||
pivot_map[ts_str] = {'highs': [], 'lows': []}
|
||||
pivot_map[ts_str]['lows'].append({
|
||||
'level': level_num,
|
||||
'price': last_low.price,
|
||||
'strength': last_low.strength
|
||||
})
|
||||
pivot_map[ts_str]['lows'].append(pivot_info)
|
||||
last_pivots[level_num]['low'] = (ts_str, len(pivot_map[ts_str]['lows']) - 1)
|
||||
|
||||
# Mark the last high and last low for each level
|
||||
for level_num, last_info in last_pivots.items():
|
||||
if last_info['high']:
|
||||
ts_str, idx = last_info['high']
|
||||
pivot_map[ts_str]['highs'][idx]['is_last'] = True
|
||||
if last_info['low']:
|
||||
ts_str, idx = last_info['low']
|
||||
pivot_map[ts_str]['lows'][idx]['is_last'] = True
|
||||
|
||||
logger.info(f"Found {len(pivot_map)} pivot candles for {symbol} {timeframe} (from {len(df)} candles)")
|
||||
return pivot_map
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting pivot markers for {timeframe}: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return {}
|
||||
|
||||
def _setup_routes(self):
|
||||
|
||||
@@ -149,87 +149,117 @@ class ChartManager {
|
||||
// Prepare chart data with pivot bounds
|
||||
const chartData = [candlestickTrace, volumeTrace];
|
||||
|
||||
// Add pivot markers from chart data (last high/low for each level L1-L5)
|
||||
// Add pivot markers from chart data
|
||||
const shapes = [];
|
||||
const annotations = [];
|
||||
|
||||
const pivotDots = { x: [], y: [], text: [], marker: { color: [], size: [], symbol: [] }, mode: 'markers', hoverinfo: 'text', showlegend: false };
|
||||
|
||||
if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) {
|
||||
const xMin = data.timestamps[0];
|
||||
const xMax = data.timestamps[data.timestamps.length - 1];
|
||||
|
||||
|
||||
// Process each timestamp that has pivot markers
|
||||
Object.entries(data.pivot_markers).forEach(([timestamp, pivots]) => {
|
||||
// Draw horizontal lines for last high pivots (resistance)
|
||||
// Process high pivots
|
||||
if (pivots.highs && pivots.highs.length > 0) {
|
||||
pivots.highs.forEach(pivot => {
|
||||
const color = this._getPivotColor(pivot.level, 'high');
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
// Add label for the level
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}H`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
|
||||
// Draw dot on the pivot candle (above the high)
|
||||
pivotDots.x.push(timestamp);
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} High Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.symbol.push('triangle-down');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot of this level
|
||||
if (pivot.is_last) {
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
// Add label for the level
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}H`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Draw horizontal lines for last low pivots (support)
|
||||
|
||||
// Process low pivots
|
||||
if (pivots.lows && pivots.lows.length > 0) {
|
||||
pivots.lows.forEach(pivot => {
|
||||
const color = this._getPivotColor(pivot.level, 'low');
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
// Add label for the level
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}L`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
|
||||
// Draw dot on the pivot candle (below the low)
|
||||
pivotDots.x.push(timestamp);
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} Low Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.symbol.push('triangle-up');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot of this level
|
||||
if (pivot.is_last) {
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
// Add label for the level
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}L`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Add pivot dots trace if we have any
|
||||
if (pivotDots.x.length > 0) {
|
||||
chartData.push(pivotDots);
|
||||
}
|
||||
|
||||
console.log(`Added ${shapes.length} pivot levels to ${timeframe} chart`);
|
||||
}
|
||||
|
||||
@@ -370,85 +400,115 @@ class ChartManager {
|
||||
// Add pivot markers from chart data
|
||||
const shapes = [];
|
||||
const annotations = [];
|
||||
|
||||
const pivotDots = { x: [], y: [], text: [], marker: { color: [], size: [], symbol: [] }, mode: 'markers', hoverinfo: 'text', showlegend: false };
|
||||
|
||||
if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) {
|
||||
const xMin = data.timestamps[0];
|
||||
const xMax = data.timestamps[data.timestamps.length - 1];
|
||||
|
||||
|
||||
// Process each timestamp that has pivot markers
|
||||
Object.entries(data.pivot_markers).forEach(([timestamp, pivots]) => {
|
||||
// Draw horizontal lines for last high pivots
|
||||
// Process high pivots
|
||||
if (pivots.highs && pivots.highs.length > 0) {
|
||||
pivots.highs.forEach(pivot => {
|
||||
const color = this._getPivotColor(pivot.level, 'high');
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}H`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
|
||||
// Draw dot on the pivot candle
|
||||
pivotDots.x.push(timestamp);
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} High Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.symbol.push('triangle-down');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot
|
||||
if (pivot.is_last) {
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}H`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Draw horizontal lines for last low pivots
|
||||
|
||||
// Process low pivots
|
||||
if (pivots.lows && pivots.lows.length > 0) {
|
||||
pivots.lows.forEach(pivot => {
|
||||
const color = this._getPivotColor(pivot.level, 'low');
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}L`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
|
||||
// Draw dot on the pivot candle
|
||||
pivotDots.x.push(timestamp);
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} Low Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.symbol.push('triangle-up');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot
|
||||
if (pivot.is_last) {
|
||||
shapes.push({
|
||||
type: 'line',
|
||||
x0: xMin,
|
||||
y0: pivot.price,
|
||||
x1: xMax,
|
||||
y1: pivot.price,
|
||||
line: {
|
||||
color: color,
|
||||
width: 1,
|
||||
dash: 'dash'
|
||||
},
|
||||
layer: 'below'
|
||||
});
|
||||
|
||||
annotations.push({
|
||||
x: xMax,
|
||||
y: pivot.price,
|
||||
text: `L${pivot.level}L`,
|
||||
showarrow: false,
|
||||
xanchor: 'left',
|
||||
font: {
|
||||
size: 9,
|
||||
color: color
|
||||
},
|
||||
bgcolor: '#1f2937',
|
||||
borderpad: 2
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add pivot dots trace if we have any
|
||||
if (pivotDots.x.length > 0) {
|
||||
chartData.push(pivotDots);
|
||||
}
|
||||
}
|
||||
|
||||
// Use Plotly.react for efficient updates
|
||||
const update = {
|
||||
const update = {
|
||||
shapes: shapes,
|
||||
annotations: annotations
|
||||
};
|
||||
@@ -739,11 +799,11 @@ class ChartManager {
|
||||
// Different colors for different levels
|
||||
const highColors = ['#dc3545', '#ff6b6b', '#ff8787', '#ffa8a8', '#ffc9c9'];
|
||||
const lowColors = ['#28a745', '#51cf66', '#69db7c', '#8ce99a', '#b2f2bb'];
|
||||
|
||||
|
||||
const colors = type === 'high' ? highColors : lowColors;
|
||||
return colors[Math.min(level - 1, colors.length - 1)];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable crosshair cursor
|
||||
*/
|
||||
|
||||
@@ -65,18 +65,22 @@ class WilliamsMarketStructure:
|
||||
|
||||
logger.info(f"Williams Market Structure initialized with {self.max_levels} levels")
|
||||
|
||||
def calculate_recursive_pivot_points(self, ohlcv_data: np.ndarray) -> Dict[int, TrendLevel]:
|
||||
def calculate_recursive_pivot_points(self, ohlcv_data: np.ndarray, min_pivot_distance: int = None) -> Dict[int, TrendLevel]:
|
||||
"""
|
||||
Calculate recursive pivot points following Williams Market Structure methodology
|
||||
|
||||
Args:
|
||||
ohlcv_data: OHLCV data array with shape (N, 6) [timestamp, O, H, L, C, V]
|
||||
min_pivot_distance: Override the instance's min_pivot_distance for this calculation (default: None uses instance value)
|
||||
|
||||
Returns:
|
||||
Dictionary of trend levels with pivot points
|
||||
"""
|
||||
try:
|
||||
if len(ohlcv_data) < self.min_pivot_distance * 2 + 1:
|
||||
# Use provided min_pivot_distance or fall back to instance default
|
||||
pivot_distance = min_pivot_distance if min_pivot_distance is not None else self.min_pivot_distance
|
||||
|
||||
if len(ohlcv_data) < pivot_distance * 2 + 1:
|
||||
logger.warning(f"Insufficient data for pivot calculation: {len(ohlcv_data)} bars")
|
||||
return {}
|
||||
|
||||
@@ -87,6 +91,10 @@ class WilliamsMarketStructure:
|
||||
# Initialize pivot levels
|
||||
self.pivot_levels = {}
|
||||
|
||||
# Temporarily set the pivot distance for this calculation
|
||||
original_distance = self.min_pivot_distance
|
||||
self.min_pivot_distance = pivot_distance
|
||||
|
||||
# Level 1: Calculate pivot points from raw OHLCV data
|
||||
level_1_pivots = self._calculate_level_1_pivots(df)
|
||||
if level_1_pivots:
|
||||
@@ -110,6 +118,9 @@ class WilliamsMarketStructure:
|
||||
else:
|
||||
break # No more higher level pivots possible
|
||||
|
||||
# Restore original pivot distance
|
||||
self.min_pivot_distance = original_distance
|
||||
|
||||
logger.debug(f"Calculated {len(self.pivot_levels)} pivot levels")
|
||||
return self.pivot_levels
|
||||
|
||||
|
||||
Reference in New Issue
Block a user