#!/usr/bin/env python3 """ Chart Data Provider Core Module This module handles all chart data preparation and market data simulation, separated from the web UI layer. """ import logging import numpy as np import pandas as pd from datetime import datetime, timedelta from typing import Dict, List, Any, Optional, Tuple import plotly.graph_objects as go from plotly.subplots import make_subplots from .cnn_pivot_predictor import CNNPivotPredictor, PivotPrediction from .pivot_detector import WilliamsPivotDetector, DetectedPivot # Setup logging with ASCII-only output logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class ChartDataProvider: """Core chart data provider with market simulation and chart preparation""" def __init__(self, config: Optional[Dict] = None): self.config = config or self._default_config() # Initialize core components self.cnn_predictor = CNNPivotPredictor() self.pivot_detector = WilliamsPivotDetector() # Market data self.current_price = 3500.0 # Starting ETH price self.price_history: List[Dict] = [] # Initialize with sample data self._generate_initial_data() logger.info("Chart Data Provider initialized") def _default_config(self) -> Dict: """Default configuration""" return { 'initial_history_hours': 2, 'price_volatility': 5.0, 'volume_range': (100, 1000), 'chart_height': 600, 'subplots': True } def _generate_initial_data(self) -> None: """Generate initial price history for demonstration""" base_time = datetime.now() - timedelta(hours=self.config['initial_history_hours']) for i in range(120): # 2 hours of minute data # Simulate realistic price movement change = np.random.normal(0, self.config['price_volatility']) self.current_price += change # Ensure price doesn't go negative self.current_price = max(self.current_price, 100.0) timestamp = base_time + timedelta(minutes=i) # Generate OHLC data open_price = self.current_price - np.random.uniform(-2, 2) high_price = max(open_price, self.current_price) + np.random.uniform(0, 8) low_price = min(open_price, self.current_price) - np.random.uniform(0, 8) close_price = self.current_price volume = np.random.uniform(*self.config['volume_range']) candle = { 'timestamp': timestamp, 'open': open_price, 'high': high_price, 'low': low_price, 'close': close_price, 'volume': volume } self.price_history.append(candle) logger.info(f"Generated {len(self.price_history)} initial price candles") def simulate_price_update(self) -> Dict: """Simulate real-time price update""" try: # Generate new price movement change = np.random.normal(0, self.config['price_volatility']) self.current_price += change self.current_price = max(self.current_price, 100.0) # Create new candle timestamp = datetime.now() open_price = self.price_history[-1]['close'] if self.price_history else self.current_price high_price = max(open_price, self.current_price) + np.random.uniform(0, 5) low_price = min(open_price, self.current_price) - np.random.uniform(0, 5) close_price = self.current_price volume = np.random.uniform(*self.config['volume_range']) new_candle = { 'timestamp': timestamp, 'open': open_price, 'high': high_price, 'low': low_price, 'close': close_price, 'volume': volume } self.price_history.append(new_candle) # Keep only last 200 candles to prevent memory growth if len(self.price_history) > 200: self.price_history = self.price_history[-200:] return new_candle except Exception as e: logger.error(f"Error simulating price update: {e}") return {} def get_market_data_df(self) -> pd.DataFrame: """Convert price history to pandas DataFrame""" try: if not self.price_history: return pd.DataFrame() df = pd.DataFrame(self.price_history) df['timestamp'] = pd.to_datetime(df['timestamp']) return df except Exception as e: logger.error(f"Error creating DataFrame: {e}") return pd.DataFrame() def update_predictions_and_pivots(self) -> Tuple[List[PivotPrediction], List[DetectedPivot]]: """Update CNN predictions and detect new pivots""" try: market_df = self.get_market_data_df() if market_df.empty: return [], [] # Update CNN predictions predictions = self.cnn_predictor.update_predictions(market_df, self.current_price) # Detect pivots detected_pivots = self.pivot_detector.detect_pivots(market_df) # Capture training data if new pivots are found for pivot in detected_pivots: if pivot.confirmed: actual_pivot = type('ActualPivot', (), { 'type': pivot.type, 'price': pivot.price, 'timestamp': pivot.timestamp, 'strength': pivot.strength })() self.cnn_predictor.capture_training_data(actual_pivot) return predictions, detected_pivots except Exception as e: logger.error(f"Error updating predictions and pivots: {e}") return [], [] def create_price_chart(self) -> go.Figure: """Create main price chart with candlesticks and volume""" try: market_df = self.get_market_data_df() if market_df.empty: return go.Figure() # Create subplots if self.config['subplots']: fig = make_subplots( rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, subplot_titles=('Price', 'Volume'), row_width=[0.7, 0.3] ) else: fig = go.Figure() # Add candlestick chart candlestick = go.Candlestick( x=market_df['timestamp'], open=market_df['open'], high=market_df['high'], low=market_df['low'], close=market_df['close'], name='ETH/USDT', increasing_line_color='#00ff88', decreasing_line_color='#ff4444' ) if self.config['subplots']: fig.add_trace(candlestick, row=1, col=1) else: fig.add_trace(candlestick) # Add volume bars if subplots enabled if self.config['subplots']: volume_colors = ['#00ff88' if close >= open else '#ff4444' for close, open in zip(market_df['close'], market_df['open'])] volume_bar = go.Bar( x=market_df['timestamp'], y=market_df['volume'], name='Volume', marker_color=volume_colors, opacity=0.7 ) fig.add_trace(volume_bar, row=2, col=1) # Update layout fig.update_layout( title='ETH/USDT Price Chart with CNN Predictions', xaxis_title='Time', yaxis_title='Price (USDT)', height=self.config['chart_height'], showlegend=True, xaxis_rangeslider_visible=False ) return fig except Exception as e: logger.error(f"Error creating price chart: {e}") return go.Figure() def add_cnn_predictions_to_chart(self, fig: go.Figure, predictions: List[PivotPrediction]) -> go.Figure: """Add CNN predictions as hollow circles to the chart""" try: if not predictions: return fig # Separate HIGH and LOW predictions high_predictions = [p for p in predictions if p.type == 'HIGH'] low_predictions = [p for p in predictions if p.type == 'LOW'] # Add HIGH predictions (red hollow circles) if high_predictions: high_x = [p.timestamp for p in high_predictions] high_y = [p.predicted_price for p in high_predictions] high_sizes = [max(8, min(20, p.confidence * 25)) for p in high_predictions] high_text = [f"HIGH Prediction
Price: ${p.predicted_price:.2f}
Confidence: {p.confidence:.1%}
Level: {p.level}" for p in high_predictions] fig.add_trace(go.Scatter( x=high_x, y=high_y, mode='markers', marker=dict( symbol='circle-open', size=high_sizes, color='red', line=dict(width=2) ), name='CNN HIGH Predictions', text=high_text, hovertemplate='%{text}' )) # Add LOW predictions (green hollow circles) if low_predictions: low_x = [p.timestamp for p in low_predictions] low_y = [p.predicted_price for p in low_predictions] low_sizes = [max(8, min(20, p.confidence * 25)) for p in low_predictions] low_text = [f"LOW Prediction
Price: ${p.predicted_price:.2f}
Confidence: {p.confidence:.1%}
Level: {p.level}" for p in low_predictions] fig.add_trace(go.Scatter( x=low_x, y=low_y, mode='markers', marker=dict( symbol='circle-open', size=low_sizes, color='green', line=dict(width=2) ), name='CNN LOW Predictions', text=low_text, hovertemplate='%{text}' )) return fig except Exception as e: logger.error(f"Error adding CNN predictions to chart: {e}") return fig def add_actual_pivots_to_chart(self, fig: go.Figure, pivots: List[DetectedPivot]) -> go.Figure: """Add actual detected pivots as solid triangles to the chart""" try: if not pivots: return fig # Separate HIGH and LOW pivots high_pivots = [p for p in pivots if p.type == 'HIGH'] low_pivots = [p for p in pivots if p.type == 'LOW'] # Add HIGH pivots (red triangles pointing down) if high_pivots: high_x = [p.timestamp for p in high_pivots] high_y = [p.price for p in high_pivots] high_sizes = [max(10, min(25, p.strength * 5)) for p in high_pivots] high_text = [f"HIGH Pivot
Price: ${p.price:.2f}
Strength: {p.strength}
Confirmed: {p.confirmed}" for p in high_pivots] fig.add_trace(go.Scatter( x=high_x, y=high_y, mode='markers', marker=dict( symbol='triangle-down', size=high_sizes, color='darkred', line=dict(width=1, color='white') ), name='Actual HIGH Pivots', text=high_text, hovertemplate='%{text}' )) # Add LOW pivots (green triangles pointing up) if low_pivots: low_x = [p.timestamp for p in low_pivots] low_y = [p.price for p in low_pivots] low_sizes = [max(10, min(25, p.strength * 5)) for p in low_pivots] low_text = [f"LOW Pivot
Price: ${p.price:.2f}
Strength: {p.strength}
Confirmed: {p.confirmed}" for p in low_pivots] fig.add_trace(go.Scatter( x=low_x, y=low_y, mode='markers', marker=dict( symbol='triangle-up', size=low_sizes, color='darkgreen', line=dict(width=1, color='white') ), name='Actual LOW Pivots', text=low_text, hovertemplate='%{text}' )) return fig except Exception as e: logger.error(f"Error adding actual pivots to chart: {e}") return fig def get_current_status(self) -> Dict: """Get current system status for dashboard display""" try: prediction_stats = self.cnn_predictor.get_prediction_stats() pivot_stats = self.pivot_detector.get_statistics() training_stats = self.cnn_predictor.get_training_stats() return { 'current_price': self.current_price, 'total_candles': len(self.price_history), 'last_update': datetime.now().strftime('%H:%M:%S'), 'predictions': prediction_stats, 'pivots': pivot_stats, 'training': training_stats } except Exception as e: logger.error(f"Error getting current status: {e}") return {}