Files
gogo2/core/chart_data_provider.py
2025-06-13 11:48:03 +03:00

378 lines
15 KiB
Python

#!/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<br>Price: ${p.predicted_price:.2f}<br>Confidence: {p.confidence:.1%}<br>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}<extra></extra>'
))
# 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<br>Price: ${p.predicted_price:.2f}<br>Confidence: {p.confidence:.1%}<br>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}<extra></extra>'
))
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<br>Price: ${p.price:.2f}<br>Strength: {p.strength}<br>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}<extra></extra>'
))
# 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<br>Price: ${p.price:.2f}<br>Strength: {p.strength}<br>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}<extra></extra>'
))
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 {}