refactoring, predictions WIP
This commit is contained in:
378
core/chart_data_provider.py
Normal file
378
core/chart_data_provider.py
Normal file
@ -0,0 +1,378 @@
|
||||
#!/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 {}
|
Reference in New Issue
Block a user