fix pivots - WORKING PIVOTS!

This commit is contained in:
Dobromir Popov
2025-10-24 11:44:25 +03:00
parent de2ad92602
commit bd95ff610c
4 changed files with 402 additions and 179 deletions

View File

@@ -18,6 +18,7 @@ from dash import Dash, html
import logging
from datetime import datetime
import json
import pandas as pd
# Import core components from main system
try:
@@ -197,6 +198,70 @@ class AnnotationDashboard:
storage_thread = threading.Thread(target=enable_storage, daemon=True)
storage_thread.start()
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
"""
try:
if not self.data_provider:
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 not pivot_levels:
return {}
# Build a map of timestamp -> pivot info
pivot_map = {}
# For each level (1-5), find the last high and last low pivot
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
for pivot in trend_level.pivot_points:
if pivot.pivot_type == 'high':
last_high = pivot
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
})
return pivot_map
except Exception as e:
logger.error(f"Error getting pivot markers for {timeframe}: {e}")
return {}
def _setup_routes(self):
"""Setup Flask routes"""
@@ -333,6 +398,9 @@ class AnnotationDashboard:
)
if df is not None and not df.empty:
# Get pivot points for this timeframe
pivot_markers = self._get_pivot_markers_for_timeframe(symbol, timeframe, df)
# Convert to format suitable for Plotly
chart_data[timeframe] = {
'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(),
@@ -340,7 +408,8 @@ class AnnotationDashboard:
'high': df['high'].tolist(),
'low': df['low'].tolist(),
'close': df['close'].tolist(),
'volume': df['volume'].tolist()
'volume': df['volume'].tolist(),
'pivot_markers': pivot_markers # Optional: only present if pivots exist
}
# Get pivot bounds for the symbol
@@ -569,13 +638,17 @@ class AnnotationDashboard:
)
if df is not None and not df.empty:
# Get pivot markers for this timeframe
pivot_markers = self._get_pivot_markers_for_timeframe(symbol, timeframe, df)
chart_data[timeframe] = {
'timestamps': df.index.strftime('%Y-%m-%d %H:%M:%S').tolist(),
'open': df['open'].tolist(),
'high': df['high'].tolist(),
'low': df['low'].tolist(),
'close': df['close'].tolist(),
'volume': df['volume'].tolist()
'volume': df['volume'].tolist(),
'pivot_markers': pivot_markers # Optional: only present if pivots exist
}
logger.info(f"Refreshed {timeframe}: {len(df)} candles")
else:

View File

@@ -9,39 +9,48 @@ class ChartManager {
this.charts = {};
this.annotations = {};
this.syncedTime = null;
console.log('ChartManager initialized with timeframes:', timeframes);
}
/**
* Initialize charts for all timeframes with pivot bounds
*/
initializeCharts(chartData, pivotBounds = null) {
console.log('Initializing charts with data:', chartData);
console.log('Pivot bounds:', pivotBounds);
this.timeframes.forEach(timeframe => {
if (chartData[timeframe]) {
this.createChart(timeframe, chartData[timeframe], pivotBounds);
// Use requestAnimationFrame to batch chart creation
let index = 0;
const createNextChart = () => {
if (index < this.timeframes.length) {
const timeframe = this.timeframes[index];
if (chartData[timeframe]) {
this.createChart(timeframe, chartData[timeframe], pivotBounds);
}
index++;
requestAnimationFrame(createNextChart);
} else {
// Enable crosshair after all charts are created
this.enableCrosshair();
}
});
// Enable crosshair
this.enableCrosshair();
};
requestAnimationFrame(createNextChart);
}
/**
* Create a single chart for a timeframe
*/
createChart(timeframe, data, pivotBounds = null) {
const plotId = `plot-${timeframe}`;
const plotElement = document.getElementById(plotId);
if (!plotElement) {
console.error(`Plot element not found: ${plotId}`);
return;
}
// Create candlestick trace
const candlestickTrace = {
x: data.timestamps,
@@ -52,23 +61,23 @@ class ChartManager {
type: 'candlestick',
name: 'Price',
increasing: {
line: {color: '#10b981', width: 1},
line: { color: '#10b981', width: 1 },
fillcolor: '#10b981'
},
decreasing: {
line: {color: '#ef4444', width: 1},
line: { color: '#ef4444', width: 1 },
fillcolor: '#ef4444'
},
xaxis: 'x',
yaxis: 'y'
};
// Create volume trace with color based on price direction
const volumeColors = data.close.map((close, i) => {
if (i === 0) return '#3b82f6';
return close >= data.open[i] ? '#10b981' : '#ef4444';
});
const volumeTrace = {
x: data.timestamps,
y: data.volume,
@@ -81,12 +90,12 @@ class ChartManager {
},
hoverinfo: 'y'
};
const layout = {
title: '',
showlegend: false,
xaxis: {
rangeslider: {visible: false},
rangeslider: { visible: false },
gridcolor: '#374151',
color: '#9ca3af',
showgrid: true,
@@ -95,7 +104,7 @@ class ChartManager {
yaxis: {
title: {
text: 'Price (USD)',
font: {size: 10}
font: { size: 10 }
},
gridcolor: '#374151',
color: '#9ca3af',
@@ -106,7 +115,7 @@ class ChartManager {
yaxis2: {
title: {
text: 'Volume',
font: {size: 10}
font: { size: 10 }
},
gridcolor: '#374151',
color: '#9ca3af',
@@ -116,64 +125,128 @@ class ChartManager {
},
plot_bgcolor: '#1f2937',
paper_bgcolor: '#1f2937',
font: {color: '#f8f9fa', size: 11},
margin: {l: 60, r: 20, t: 10, b: 40},
font: { color: '#f8f9fa', size: 11 },
margin: { l: 60, r: 20, t: 10, b: 40 },
hovermode: 'x unified',
dragmode: 'pan'
dragmode: 'pan',
// Performance optimizations
autosize: true,
staticPlot: false
};
const config = {
responsive: true,
displayModeBar: true,
modeBarButtonsToRemove: ['lasso2d', 'select2d', 'autoScale2d'],
displaylogo: false,
scrollZoom: true
scrollZoom: true,
// Performance optimizations
doubleClick: false,
showAxisDragHandles: false,
showAxisRangeEntryBoxes: false
};
// Prepare chart data with pivot bounds
const chartData = [candlestickTrace, volumeTrace];
// Add pivot markers from chart data (last high/low for each level L1-L5)
const shapes = [];
const annotations = [];
// Add pivot levels if available
if (pivotBounds && pivotBounds.support_levels && pivotBounds.resistance_levels) {
// Add support levels
pivotBounds.support_levels.forEach((level, index) => {
chartData.push({
x: data.timestamps,
y: Array(data.timestamps.length).fill(level),
type: 'scatter',
mode: 'lines',
line: {
color: '#28a745',
width: 1,
dash: 'dash'
},
name: `Support ${index + 1}`,
showlegend: index === 0, // Only show legend for first support level
hovertemplate: `Support: $%{y:.2f}<extra></extra>`
});
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)
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 horizontal lines for last low pivots (support)
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
});
});
}
});
// Add resistance levels
pivotBounds.resistance_levels.forEach((level, index) => {
chartData.push({
x: data.timestamps,
y: Array(data.timestamps.length).fill(level),
type: 'scatter',
mode: 'lines',
line: {
color: '#dc3545',
width: 1,
dash: 'dash'
},
name: `Resistance ${index + 1}`,
showlegend: index === 0, // Only show legend for first resistance level
hovertemplate: `Resistance: $%{y:.2f}<extra></extra>`
});
});
console.log(`Added ${shapes.length} pivot levels to ${timeframe} chart`);
}
Plotly.newPlot(plotId, chartData, layout, config);
// Add shapes and annotations to layout
if (shapes.length > 0) {
layout.shapes = shapes;
}
if (annotations.length > 0) {
layout.annotations = annotations;
}
// Use Plotly.react for better performance on updates
Plotly.newPlot(plotId, chartData, layout, config).then(() => {
// Optimize rendering after initial plot
plotElement._fullLayout._replotting = false;
});
// Store chart reference
this.charts[timeframe] = {
plotId: plotId,
@@ -181,12 +254,12 @@ class ChartManager {
element: plotElement,
annotations: []
};
// Add click handler for chart
plotElement.on('plotly_click', (eventData) => {
this.handleChartClick(timeframe, eventData);
});
// Add click handler for annotations
plotElement.on('plotly_clickannotation', (eventData) => {
const annotationName = eventData.annotation.name;
@@ -194,7 +267,7 @@ class ChartManager {
const parts = annotationName.split('_');
const action = parts[0]; // 'entry', 'exit', or 'delete'
const annotationId = parts[1];
if (action === 'delete') {
this.handleAnnotationClick(annotationId, 'delete');
} else {
@@ -202,23 +275,23 @@ class ChartManager {
}
}
});
// Add hover handler to update info
plotElement.on('plotly_hover', (eventData) => {
this.updateChartInfo(timeframe, eventData);
});
console.log(`Chart created for ${timeframe} with ${data.timestamps.length} candles`);
}
/**
* Handle chart click for annotation
*/
handleChartClick(timeframe, eventData) {
if (!eventData.points || eventData.points.length === 0) return;
const point = eventData.points[0];
// Get the actual price from candlestick data
let price;
if (point.data.type === 'candlestick') {
@@ -230,22 +303,22 @@ class ChartManager {
} else {
price = point.y;
}
const clickData = {
timeframe: timeframe,
timestamp: point.x,
price: price,
index: point.pointIndex
};
console.log('Chart clicked:', clickData);
// Trigger annotation manager
if (window.appState && window.appState.annotationManager) {
window.appState.annotationManager.handleChartClick(clickData);
}
}
/**
* Update charts with new data including pivot levels
*/
@@ -253,81 +326,146 @@ class ChartManager {
Object.keys(newData).forEach(timeframe => {
if (this.charts[timeframe]) {
const plotId = this.charts[timeframe].plotId;
const data = newData[timeframe];
// Create volume colors
const volumeColors = data.close.map((close, i) => {
if (i === 0) return '#3b82f6';
return close >= data.open[i] ? '#10b981' : '#ef4444';
});
// Prepare chart data
const chartData = [
{
x: newData[timeframe].timestamps,
open: newData[timeframe].open,
high: newData[timeframe].high,
low: newData[timeframe].low,
close: newData[timeframe].close,
x: data.timestamps,
open: data.open,
high: data.high,
low: data.low,
close: data.close,
type: 'candlestick',
name: 'Price'
name: 'Price',
increasing: {
line: { color: '#10b981', width: 1 },
fillcolor: '#10b981'
},
decreasing: {
line: { color: '#ef4444', width: 1 },
fillcolor: '#ef4444'
}
},
{
x: newData[timeframe].timestamps,
y: newData[timeframe].volume,
x: data.timestamps,
y: data.volume,
type: 'bar',
yaxis: 'y2',
name: 'Volume',
marker: { color: 'rgba(0, 123, 255, 0.3)' }
marker: {
color: volumeColors,
opacity: 0.3
},
hoverinfo: 'y'
}
];
// Add pivot markers from chart data
const shapes = [];
const annotations = [];
// Add pivot levels if available
if (pivotBounds && pivotBounds.support_levels && pivotBounds.resistance_levels) {
// Add support levels
pivotBounds.support_levels.forEach((level, index) => {
chartData.push({
x: newData[timeframe].timestamps,
y: Array(newData[timeframe].timestamps.length).fill(level),
type: 'scatter',
mode: 'lines',
line: {
color: '#28a745',
width: 1,
dash: 'dash'
},
name: `Support ${index + 1}`,
showlegend: index === 0, // Only show legend for first support level
hovertemplate: `Support: $%{y:.2f}<extra></extra>`
});
});
if (data.pivot_markers && Object.keys(data.pivot_markers).length > 0) {
const xMin = data.timestamps[0];
const xMax = data.timestamps[data.timestamps.length - 1];
// Add resistance levels
pivotBounds.resistance_levels.forEach((level, index) => {
chartData.push({
x: newData[timeframe].timestamps,
y: Array(newData[timeframe].timestamps.length).fill(level),
type: 'scatter',
mode: 'lines',
line: {
color: '#dc3545',
width: 1,
dash: 'dash'
},
name: `Resistance ${index + 1}`,
showlegend: index === 0, // Only show legend for first resistance level
hovertemplate: `Resistance: $%{y:.2f}<extra></extra>`
});
// Process each timestamp that has pivot markers
Object.entries(data.pivot_markers).forEach(([timestamp, pivots]) => {
// Draw horizontal lines for last 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 horizontal lines for last 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
});
});
}
});
}
Plotly.react(plotId, chartData);
// Use Plotly.react for efficient updates
const update = {
shapes: shapes,
annotations: annotations
};
Plotly.react(plotId, chartData, update);
}
});
}
/**
* Add annotation to charts
*/
addAnnotation(annotation) {
console.log('Adding annotation to charts:', annotation);
// Store annotation
this.annotations[annotation.annotation_id] = annotation;
// Add markers to relevant timeframe chart
const timeframe = annotation.timeframe;
if (this.charts[timeframe]) {
@@ -335,7 +473,7 @@ class ChartManager {
this.updateChartAnnotations(timeframe);
}
}
/**
* Remove annotation from charts
*/
@@ -343,35 +481,35 @@ class ChartManager {
if (this.annotations[annotationId]) {
const annotation = this.annotations[annotationId];
delete this.annotations[annotationId];
// Update chart
if (this.charts[annotation.timeframe]) {
this.updateChartAnnotations(annotation.timeframe);
}
}
}
/**
* Update chart annotations
*/
updateChartAnnotations(timeframe) {
const chart = this.charts[timeframe];
if (!chart) return;
// Get annotations for this timeframe
const timeframeAnnotations = Object.values(this.annotations)
.filter(ann => ann.timeframe === timeframe);
// Build Plotly annotations and shapes
const plotlyAnnotations = [];
const plotlyShapes = [];
timeframeAnnotations.forEach(ann => {
const entryTime = ann.entry.timestamp;
const exitTime = ann.exit.timestamp;
const entryPrice = ann.entry.price;
const exitPrice = ann.exit.price;
// Entry marker (clickable)
plotlyAnnotations.push({
x: entryTime,
@@ -387,7 +525,7 @@ class ChartManager {
captureevents: true,
name: `entry_${ann.annotation_id}`
});
// Exit marker (clickable)
plotlyAnnotations.push({
x: exitTime,
@@ -403,12 +541,12 @@ class ChartManager {
captureevents: true,
name: `exit_${ann.annotation_id}`
});
// P&L label with delete button
const midTime = new Date((new Date(entryTime).getTime() + new Date(exitTime).getTime()) / 2);
const midPrice = (entryPrice + exitPrice) / 2;
const pnlColor = ann.profit_loss_pct >= 0 ? '#10b981' : '#ef4444';
plotlyAnnotations.push({
x: midTime,
y: midPrice,
@@ -429,7 +567,7 @@ class ChartManager {
captureevents: true,
name: `delete_${ann.annotation_id}`
});
// Connecting line (clickable for selection)
plotlyShapes.push({
type: 'line',
@@ -445,22 +583,22 @@ class ChartManager {
name: `line_${ann.annotation_id}`
});
});
// Update chart layout with annotations
Plotly.relayout(chart.plotId, {
annotations: plotlyAnnotations,
shapes: plotlyShapes
});
console.log(`Updated ${timeframeAnnotations.length} annotations for ${timeframe}`);
}
/**
* Handle annotation click for editing/deleting
*/
handleAnnotationClick(annotationId, action) {
console.log(`Annotation ${action}:`, annotationId);
if (action === 'delete') {
if (confirm('Delete this annotation?')) {
if (window.deleteAnnotation) {
@@ -473,18 +611,18 @@ class ChartManager {
}
}
}
/**
* Highlight annotation
*/
highlightAnnotation(annotationId) {
const annotation = this.annotations[annotationId];
if (!annotation) return;
const timeframe = annotation.timeframe;
const chart = this.charts[timeframe];
if (!chart) return;
// Flash the annotation by temporarily changing its color
const originalAnnotations = chart.element.layout.annotations || [];
const highlightedAnnotations = originalAnnotations.map(ann => {
@@ -497,24 +635,24 @@ class ChartManager {
}
};
});
Plotly.relayout(chart.plotId, {annotations: highlightedAnnotations});
Plotly.relayout(chart.plotId, { annotations: highlightedAnnotations });
// Restore original colors after 1 second
setTimeout(() => {
this.updateChartAnnotations(timeframe);
}, 1000);
console.log('Highlighted annotation:', annotationId);
}
/**
* Edit annotation - allows moving entry/exit points
*/
editAnnotation(annotationId) {
const annotation = this.annotations[annotationId];
if (!annotation) return;
// Show edit dialog
const action = prompt(
'Edit annotation:\n' +
@@ -524,11 +662,11 @@ class ChartManager {
'Enter choice (1-3):',
'1'
);
if (action === '1') {
// Move entry point
window.showSuccess('Click on chart to set new entry point');
// Store annotation for editing
if (window.appState && window.appState.annotationManager) {
window.appState.annotationManager.editingAnnotation = {
@@ -536,10 +674,10 @@ class ChartManager {
original: annotation,
editMode: 'entry'
};
// Remove current annotation from display
this.removeAnnotation(annotationId);
// Show exit marker as reference
const chart = this.charts[annotation.timeframe];
if (chart) {
@@ -552,7 +690,7 @@ class ChartManager {
arrowhead: 2,
ax: 0,
ay: 40,
font: {size: 14, color: '#9ca3af'}
font: { size: 14, color: '#9ca3af' }
}]
});
}
@@ -560,17 +698,17 @@ class ChartManager {
} else if (action === '2') {
// Move exit point
window.showSuccess('Click on chart to set new exit point');
if (window.appState && window.appState.annotationManager) {
window.appState.annotationManager.editingAnnotation = {
annotation_id: annotationId,
original: annotation,
editMode: 'exit'
};
// Remove current annotation from display
this.removeAnnotation(annotationId);
// Show entry marker as reference
const chart = this.charts[annotation.timeframe];
if (chart) {
@@ -583,7 +721,7 @@ class ChartManager {
arrowhead: 2,
ax: 0,
ay: -40,
font: {size: 14, color: '#9ca3af'}
font: { size: 14, color: '#9ca3af' }
}]
});
}
@@ -593,6 +731,18 @@ class ChartManager {
this.handleAnnotationClick(annotationId, 'delete');
}
}
/**
* Get color for pivot level
*/
_getPivotColor(level, type) {
// 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
@@ -601,7 +751,7 @@ class ChartManager {
// Crosshair is enabled via hovermode in layout
console.log('Crosshair enabled');
}
/**
* Handle zoom
*/
@@ -613,7 +763,7 @@ class ChartManager {
});
});
}
/**
* Reset zoom
*/
@@ -625,23 +775,23 @@ class ChartManager {
});
});
}
/**
* Synchronize time navigation across charts
*/
syncTimeNavigation(timestamp) {
this.syncedTime = timestamp;
// Update all charts to center on this timestamp
Object.values(this.charts).forEach(chart => {
const data = chart.data;
const timestamps = data.timestamps;
// Find index closest to target timestamp
const targetTime = new Date(timestamp);
let closestIndex = 0;
let minDiff = Infinity;
timestamps.forEach((ts, i) => {
const diff = Math.abs(new Date(ts) - targetTime);
if (diff < minDiff) {
@@ -649,35 +799,35 @@ class ChartManager {
closestIndex = i;
}
});
// Center the view on this index
const rangeSize = 100; // Show 100 candles
const startIndex = Math.max(0, closestIndex - rangeSize / 2);
const endIndex = Math.min(timestamps.length - 1, closestIndex + rangeSize / 2);
Plotly.relayout(chart.plotId, {
'xaxis.range': [timestamps[startIndex], timestamps[endIndex]]
});
});
console.log('Synced charts to timestamp:', timestamp);
}
/**
* Update chart info display on hover
*/
updateChartInfo(timeframe, eventData) {
if (!eventData.points || eventData.points.length === 0) return;
const point = eventData.points[0];
const infoElement = document.getElementById(`info-${timeframe}`);
if (infoElement && point.data.type === 'candlestick') {
const open = point.data.open[point.pointIndex];
const high = point.data.high[point.pointIndex];
const low = point.data.low[point.pointIndex];
const close = point.data.close[point.pointIndex];
infoElement.textContent = `O: ${open.toFixed(2)} H: ${high.toFixed(2)} L: ${low.toFixed(2)} C: ${close.toFixed(2)}`;
}
}

View File

@@ -61,8 +61,8 @@
// Initialize application state
window.appState = {
currentSymbol: '{{ current_symbol }}',
currentTimeframes: '{{ timeframes | tojson }}',
annotations: '{{ annotations | tojson }}',
currentTimeframes: {{ timeframes | tojson }},
annotations: {{ annotations | tojson }},
pendingAnnotation: null,
chartManager: null,
annotationManager: null,

View File

@@ -2244,8 +2244,8 @@ class DataProvider:
# Initialize Williams Market Structure analyzer
try:
williams = WilliamsMarketStructure(1)
# Use pivot length of 2 (tip + 2 prev + 2 next = 5 candles)
williams = WilliamsMarketStructure(min_pivot_distance=2)
# Calculate 5 levels of recursive pivot points
logger.info("Running Williams Market Structure analysis...")