fifo n1 que

This commit is contained in:
Dobromir Popov
2025-07-26 21:34:16 +03:00
parent a3828c708c
commit c349ff6f30
8 changed files with 2173 additions and 240 deletions

View File

@ -16,6 +16,7 @@ import logging
import time
import threading
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Tuple, Union
from dataclasses import dataclass, field
@ -178,6 +179,23 @@ class TradingOrchestrator:
self.fusion_decisions_count: int = 0
self.fusion_training_data: List[Any] = [] # Store training examples for decision model
# FIFO Data Queues - Ensure consistent data availability across different refresh rates
self.data_queues = {
'ohlcv_1s': {symbol: deque(maxlen=500) for symbol in [self.symbol] + self.ref_symbols},
'ohlcv_1m': {symbol: deque(maxlen=300) for symbol in [self.symbol] + self.ref_symbols},
'ohlcv_1h': {symbol: deque(maxlen=300) for symbol in [self.symbol] + self.ref_symbols},
'ohlcv_1d': {symbol: deque(maxlen=300) for symbol in [self.symbol] + self.ref_symbols},
'technical_indicators': {symbol: deque(maxlen=100) for symbol in [self.symbol] + self.ref_symbols},
'cob_data': {symbol: deque(maxlen=50) for symbol in [self.symbol]}, # COB only for primary symbol
'model_predictions': {symbol: deque(maxlen=20) for symbol in [self.symbol]}
}
# Data queue locks for thread safety
self.data_queue_locks = {
data_type: {symbol: threading.Lock() for symbol in queue_dict.keys()}
for data_type, queue_dict in self.data_queues.items()
}
# COB Integration - Real-time market microstructure data
self.cob_integration = None # Will be set to COBIntegration instance if available
self.latest_cob_data: Dict[str, Any] = {} # {symbol: COBSnapshot}
@ -241,6 +259,13 @@ class TradingOrchestrator:
self.data_provider.start_centralized_data_collection()
logger.info("Centralized data collection started - all models and dashboard will receive data")
# Initialize FIFO data queue integration
self._initialize_data_queue_integration()
# Log initial queue status
logger.info("FIFO data queues initialized")
self.log_queue_status(detailed=False)
# Initialize database cleanup task
self._schedule_database_cleanup()
@ -1976,85 +2001,118 @@ class TradingOrchestrator:
return 50.0
async def _get_cnn_predictions(self, model: CNNModelInterface, symbol: str) -> List[Prediction]:
"""Get predictions from CNN model for all timeframes with enhanced COB features"""
"""Get predictions from CNN model using FIFO queue data"""
predictions = []
try:
timeframes = getattr(self.config, 'timeframes', ['1m','5m','15m','1h'])
for timeframe in timeframes:
# 1) build or fetch your feature matrix (and optionally augment with COB)…
feature_matrix = self.data_provider.get_feature_matrix(
symbol=symbol,
timeframes=[timeframe],
window_size=getattr(model, 'window_size', 20)
)
if feature_matrix is None:
continue
# …apply COBaugmentation here (omitted for brevity)—
enhanced_features = self._augment_with_cob(feature_matrix, symbol)
# 2) Initialize these before we call the model
action_probs, confidence = None, None
# 3) Try the actual model inference
# Use FIFO queue data instead of direct data provider calls
base_data = self.build_base_data_input(symbol)
if not base_data:
logger.warning(f"Cannot build BaseDataInput for CNN prediction: {symbol}")
return predictions
# Use CNN adapter if available
if hasattr(self, 'cnn_adapter') and self.cnn_adapter:
try:
# if your model has an .act() that returns (probs, conf)
if hasattr(model.model, 'act'):
# Flatten / reshape enhanced_features as needed…
x = self._prepare_cnn_input(enhanced_features)
result = self.cnn_adapter.predict(base_data)
if result:
prediction = Prediction(
action=result.action,
confidence=result.confidence,
probabilities=result.predictions,
timeframe="multi", # Multi-timeframe prediction
timestamp=datetime.now(),
model_name="enhanced_cnn",
metadata={
'feature_size': len(base_data.get_feature_vector()),
'data_sources': ['ohlcv_1s', 'ohlcv_1m', 'ohlcv_1h', 'ohlcv_1d', 'btc', 'cob', 'indicators']
}
)
predictions.append(prediction)
# Debugging: Print the type and content of x before passing to act()
logger.debug(f"CNN input (x) type: {type(x)}, shape: {x.shape}, content sample: {x.flatten()[:5]}...")
action_idx, confidence, action_probs = model.model.act(x, explore=False)
# Store prediction in queue for future use
self.update_data_queue('model_predictions', symbol, result)
# Debugging: Print the type and content of the unpacked values
logger.debug(f"CNN act() returned: action_idx={action_idx} (type={type(action_idx)}), confidence={confidence} (type={type(confidence)}), action_probs={action_probs[:5]}... (type={type(action_probs)})")
else:
# fallback to generic predict
result = model.predict(enhanced_features)
if isinstance(result, tuple) and len(result)==2:
action_probs, confidence = result
else:
action_probs = result
confidence = 0.7
except Exception as e:
logger.warning(f"CNN inference failed for {symbol}@{timeframe}: {e}")
continue # skip this timeframe entirely
# 4) If we still don't have valid probs, skip
if action_probs is None:
continue
# 5) Build your Prediction
action_names = ['SELL','HOLD','BUY']
best_idx = int(np.argmax(action_probs))
best_action = action_names[best_idx]
pred = Prediction(
action=best_action,
confidence=float(confidence),
probabilities={n: float(p) for n,p in zip(action_names, action_probs)},
timeframe=timeframe,
timestamp=datetime.now(),
model_name=model.name,
metadata={
'feature_shape': str(enhanced_features.shape),
'cob_enhanced': enhanced_features is not feature_matrix
}
)
predictions.append(pred)
# …and capture for the dashboard if you like…
current_price = self._get_current_price(symbol)
if current_price is not None:
predicted_price = current_price * (1 + (0.01 * (confidence if best_action=='BUY' else -confidence if best_action=='SELL' else 0)))
self.capture_cnn_prediction(
symbol,
direction=best_idx,
confidence=confidence,
current_price=current_price,
predicted_price=predicted_price
logger.error(f"Error using CNN adapter: {e}")
# Fallback to legacy CNN prediction if adapter fails
if not predictions:
timeframes = getattr(self.config, 'timeframes', ['1m','5m','15m','1h'])
for timeframe in timeframes:
# 1) build or fetch your feature matrix (and optionally augment with COB)…
feature_matrix = self.data_provider.get_feature_matrix(
symbol=symbol,
timeframes=[timeframe],
window_size=getattr(model, 'window_size', 20)
)
if feature_matrix is None:
continue
# …apply COBaugmentation here (omitted for brevity)—
enhanced_features = self._augment_with_cob(feature_matrix, symbol)
# 2) Initialize these before we call the model
action_probs, confidence = None, None
# 3) Try the actual model inference
try:
# if your model has an .act() that returns (probs, conf)
if hasattr(model.model, 'act'):
# Flatten / reshape enhanced_features as needed…
x = self._prepare_cnn_input(enhanced_features)
# Debugging: Print the type and content of x before passing to act()
logger.debug(f"CNN input (x) type: {type(x)}, shape: {x.shape}, content sample: {x.flatten()[:5]}...")
action_idx, confidence, action_probs = model.model.act(x, explore=False)
# Debugging: Print the type and content of the unpacked values
logger.debug(f"CNN act() returned: action_idx={action_idx} (type={type(action_idx)}), confidence={confidence} (type={type(confidence)}), action_probs={action_probs[:5]}... (type={type(action_probs)})")
else:
# fallback to generic predict
result = model.predict(enhanced_features)
if isinstance(result, tuple) and len(result)==2:
action_probs, confidence = result
else:
action_probs = result
confidence = 0.7
except Exception as e:
logger.warning(f"CNN inference failed for {symbol}@{timeframe}: {e}")
continue # skip this timeframe entirely
# 4) If we still don't have valid probs, skip
if action_probs is None:
continue
# 5) Build your Prediction
action_names = ['SELL','HOLD','BUY']
best_idx = int(np.argmax(action_probs))
best_action = action_names[best_idx]
pred = Prediction(
action=best_action,
confidence=float(confidence),
probabilities={n: float(p) for n,p in zip(action_names, action_probs)},
timeframe=timeframe,
timestamp=datetime.now(),
model_name=model.name,
metadata={
'feature_shape': str(enhanced_features.shape),
'cob_enhanced': enhanced_features is not feature_matrix
}
)
predictions.append(pred)
# …and capture for the dashboard if you like…
current_price = self._get_current_price(symbol)
if current_price is not None:
predicted_price = current_price * (1 + (0.01 * (confidence if best_action=='BUY' else -confidence if best_action=='SELL' else 0)))
self.capture_cnn_prediction(
symbol,
direction=best_idx,
confidence=confidence,
current_price=current_price,
predicted_price=predicted_price
)
except Exception as e:
logger.error(f"Orch: Error getting CNN predictions: {e}")
return predictions
@ -2073,8 +2131,17 @@ class TradingOrchestrator:
arr = arr[:300]
return arr.reshape(1,-1)
async def _get_rl_prediction(self, model: RLAgentInterface, symbol: str) -> Optional[Prediction]:
"""Get prediction from RL agent"""
"""Get prediction from RL agent using FIFO queue data"""
try:
# Use FIFO queue data to build consistent state
base_data = self.build_base_data_input(symbol)
if not base_data:
logger.warning(f"Cannot build BaseDataInput for RL prediction: {symbol}")
return None
# Convert BaseDataInput to RL state format
state_features = base_data.get_feature_vector()
# Get current state for RL agent
state = self._get_rl_state(symbol)
if state is None:
@ -3630,4 +3697,661 @@ class TradingOrchestrator:
This is much faster than loading the entire checkpoint just to get metadata
"""
return self.db_manager.get_best_checkpoint_metadata(model_name)
return self.db_manager.get_best_checkpoint_metadata(model_name)
# === FIFO DATA QUEUE MANAGEMENT ===
def update_data_queue(self, data_type: str, symbol: str, data: Any) -> bool:
"""
Update FIFO data queue with new data
Args:
data_type: Type of data ('ohlcv_1s', 'ohlcv_1m', etc.)
symbol: Trading symbol
data: New data to add
Returns:
bool: True if successful
"""
try:
if data_type not in self.data_queues:
logger.warning(f"Unknown data type: {data_type}")
return False
if symbol not in self.data_queues[data_type]:
logger.warning(f"Unknown symbol for {data_type}: {symbol}")
return False
# Thread-safe queue update
with self.data_queue_locks[data_type][symbol]:
self.data_queues[data_type][symbol].append(data)
return True
except Exception as e:
logger.error(f"Error updating data queue {data_type}/{symbol}: {e}")
return False
def get_latest_data(self, data_type: str, symbol: str, count: int = 1) -> List[Any]:
"""
Get latest data from FIFO queue
Args:
data_type: Type of data
symbol: Trading symbol
count: Number of latest items to retrieve
Returns:
List of latest data items
"""
try:
if data_type not in self.data_queues or symbol not in self.data_queues[data_type]:
return []
with self.data_queue_locks[data_type][symbol]:
queue = self.data_queues[data_type][symbol]
if len(queue) == 0:
return []
# Get last 'count' items
return list(queue)[-count:] if count > 1 else [queue[-1]]
except Exception as e:
logger.error(f"Error getting latest data {data_type}/{symbol}: {e}")
return []
def get_queue_data(self, data_type: str, symbol: str, max_items: int = None) -> List[Any]:
"""
Get all data from FIFO queue
Args:
data_type: Type of data
symbol: Trading symbol
max_items: Maximum number of items to return (None for all)
Returns:
List of data items
"""
try:
if data_type not in self.data_queues or symbol not in self.data_queues[data_type]:
return []
with self.data_queue_locks[data_type][symbol]:
queue = self.data_queues[data_type][symbol]
data_list = list(queue)
if max_items and len(data_list) > max_items:
return data_list[-max_items:]
return data_list
except Exception as e:
logger.error(f"Error getting queue data {data_type}/{symbol}: {e}")
return []
def get_queue_status(self) -> Dict[str, Dict[str, int]]:
"""Get status of all data queues"""
status = {}
for data_type, symbol_queues in self.data_queues.items():
status[data_type] = {}
for symbol, queue in symbol_queues.items():
with self.data_queue_locks[data_type][symbol]:
status[data_type][symbol] = len(queue)
return status
def get_detailed_queue_status(self) -> Dict[str, Any]:
"""Get detailed status of all data queues with timestamps and data info"""
detailed_status = {}
for data_type, symbol_queues in self.data_queues.items():
detailed_status[data_type] = {}
for symbol, queue in symbol_queues.items():
with self.data_queue_locks[data_type][symbol]:
queue_list = list(queue)
queue_info = {
'count': len(queue_list),
'max_size': queue.maxlen,
'usage_percent': (len(queue_list) / queue.maxlen * 100) if queue.maxlen else 0,
'oldest_timestamp': None,
'newest_timestamp': None,
'data_type_info': None
}
if queue_list:
# Try to get timestamps from data
try:
if hasattr(queue_list[0], 'timestamp'):
queue_info['oldest_timestamp'] = queue_list[0].timestamp.isoformat()
queue_info['newest_timestamp'] = queue_list[-1].timestamp.isoformat()
# Add data type specific info
if data_type.startswith('ohlcv_'):
if hasattr(queue_list[-1], 'close'):
queue_info['data_type_info'] = f"latest_price={queue_list[-1].close:.2f}"
elif data_type == 'technical_indicators':
if isinstance(queue_list[-1], dict):
indicators = list(queue_list[-1].keys())[:3] # First 3 indicators
queue_info['data_type_info'] = f"indicators={indicators}"
elif data_type == 'cob_data':
queue_info['data_type_info'] = "cob_snapshot"
elif data_type == 'model_predictions':
if hasattr(queue_list[-1], 'action'):
queue_info['data_type_info'] = f"latest_action={queue_list[-1].action}"
except Exception as e:
queue_info['data_type_info'] = f"error_getting_info: {e}"
detailed_status[data_type][symbol] = queue_info
return detailed_status
def log_queue_status(self, detailed: bool = False):
"""Log current queue status for debugging"""
if detailed:
status = self.get_detailed_queue_status()
logger.info("=== Detailed Queue Status ===")
for data_type, symbols in status.items():
logger.info(f"{data_type}:")
for symbol, info in symbols.items():
logger.info(f" {symbol}: {info['count']}/{info['max_size']} ({info['usage_percent']:.1f}%) - {info.get('data_type_info', 'no_info')}")
else:
status = self.get_queue_status()
logger.info("=== Queue Status ===")
for data_type, symbols in status.items():
symbol_counts = [f"{symbol}:{count}" for symbol, count in symbols.items()]
logger.info(f"{data_type}: {', '.join(symbol_counts)}")
def ensure_minimum_data(self, data_type: str, symbol: str, min_count: int) -> bool:
"""
Check if queue has minimum required data
Args:
data_type: Type of data
symbol: Trading symbol
min_count: Minimum required items
Returns:
bool: True if minimum data available
"""
try:
if data_type not in self.data_queues or symbol not in self.data_queues[data_type]:
return False
with self.data_queue_locks[data_type][symbol]:
return len(self.data_queues[data_type][symbol]) >= min_count
except Exception as e:
logger.error(f"Error checking minimum data {data_type}/{symbol}: {e}")
return False
def build_base_data_input(self, symbol: str) -> Optional[Any]:
"""
Build BaseDataInput from FIFO queues with consistent data
Args:
symbol: Trading symbol
Returns:
BaseDataInput with consistent data structure
"""
try:
from core.data_models import BaseDataInput
# Check minimum data requirements
min_requirements = {
'ohlcv_1s': 100,
'ohlcv_1m': 50,
'ohlcv_1h': 20,
'ohlcv_1d': 10
}
# Verify we have minimum data for all timeframes with fallback strategy
missing_data = []
for data_type, min_count in min_requirements.items():
if not self.ensure_minimum_data(data_type, symbol, min_count):
# Get actual count for better logging
actual_count = 0
if data_type in self.data_queues and symbol in self.data_queues[data_type]:
with self.data_queue_locks[data_type][symbol]:
actual_count = len(self.data_queues[data_type][symbol])
missing_data.append((data_type, actual_count, min_count))
# If we're missing critical 1s data, try to use 1m data as fallback
if missing_data:
critical_missing = [d for d in missing_data if d[0] in ['ohlcv_1s', 'ohlcv_1h']]
if critical_missing:
logger.warning(f"Missing critical data for {symbol}: {critical_missing}")
# Try fallback strategy: use available data with padding
if self._try_fallback_data_strategy(symbol, missing_data):
logger.info(f"Successfully applied fallback data strategy for {symbol}")
else:
for data_type, actual_count, min_count in missing_data:
logger.warning(f"Insufficient {data_type} data for {symbol}: have {actual_count}, need {min_count}")
return None
# Get BTC data (reference symbol)
btc_symbol = 'BTC/USDT'
if not self.ensure_minimum_data('ohlcv_1s', btc_symbol, 100):
# Get actual BTC data count for logging
btc_count = 0
if 'ohlcv_1s' in self.data_queues and btc_symbol in self.data_queues['ohlcv_1s']:
with self.data_queue_locks['ohlcv_1s'][btc_symbol]:
btc_count = len(self.data_queues['ohlcv_1s'][btc_symbol])
logger.warning(f"Insufficient BTC data for reference: have {btc_count}, need 100, using ETH data as fallback")
# Use ETH data as fallback
btc_data = self.get_queue_data('ohlcv_1s', symbol, 300)
else:
btc_data = self.get_queue_data('ohlcv_1s', btc_symbol, 300)
# Build BaseDataInput with queue data
base_data = BaseDataInput(
symbol=symbol,
timestamp=datetime.now(),
ohlcv_1s=self.get_queue_data('ohlcv_1s', symbol, 300),
ohlcv_1m=self.get_queue_data('ohlcv_1m', symbol, 300),
ohlcv_1h=self.get_queue_data('ohlcv_1h', symbol, 300),
ohlcv_1d=self.get_queue_data('ohlcv_1d', symbol, 300),
btc_ohlcv_1s=btc_data,
technical_indicators=self._get_latest_indicators(symbol),
cob_data=self._get_latest_cob_data(symbol),
last_predictions=self._get_recent_model_predictions(symbol)
)
# Validate the data
if not base_data.validate():
logger.warning(f"BaseDataInput validation failed for {symbol}")
return None
return base_data
except Exception as e:
logger.error(f"Error building BaseDataInput for {symbol}: {e}")
return None
def _get_latest_indicators(self, symbol: str) -> Dict[str, float]:
"""Get latest technical indicators from queue"""
try:
indicators_data = self.get_latest_data('technical_indicators', symbol, 1)
if indicators_data:
return indicators_data[0]
return {}
except Exception as e:
logger.error(f"Error getting indicators for {symbol}: {e}")
return {}
def _get_latest_cob_data(self, symbol: str) -> Optional[Any]:
"""Get latest COB data from queue"""
try:
cob_data = self.get_latest_data('cob_data', symbol, 1)
if cob_data:
return cob_data[0]
return None
except Exception as e:
logger.error(f"Error getting COB data for {symbol}: {e}")
return None
def _get_recent_model_predictions(self, symbol: str) -> Dict[str, Any]:
"""Get recent model predictions from queue"""
try:
predictions_data = self.get_latest_data('model_predictions', symbol, 5)
# Convert to dict format expected by BaseDataInput
predictions_dict = {}
for i, pred in enumerate(predictions_data):
predictions_dict[f"model_{i}"] = pred
return predictions_dict
except Exception as e:
logger.error(f"Error getting model predictions for {symbol}: {e}")
return {}
def _initialize_data_queue_integration(self):
"""Initialize integration between data provider and FIFO queues"""
try:
# Register callbacks with data provider to populate FIFO queues
if hasattr(self.data_provider, 'register_data_callback'):
# Register for different data types
self.data_provider.register_data_callback('ohlcv', self._on_ohlcv_data)
self.data_provider.register_data_callback('technical_indicators', self._on_indicators_data)
self.data_provider.register_data_callback('cob', self._on_cob_data)
logger.info("Data provider callbacks registered for FIFO queues")
else:
# Fallback: Start a background thread to poll data
self._start_data_polling_thread()
logger.info("Started data polling thread for FIFO queues")
except Exception as e:
logger.error(f"Error initializing data queue integration: {e}")
def _on_ohlcv_data(self, symbol: str, timeframe: str, data: Any):
"""Callback for new OHLCV data"""
try:
data_type = f'ohlcv_{timeframe}'
if data_type in self.data_queues and symbol in self.data_queues[data_type]:
self.update_data_queue(data_type, symbol, data)
except Exception as e:
logger.error(f"Error processing OHLCV data callback: {e}")
def _on_indicators_data(self, symbol: str, indicators: Dict[str, float]):
"""Callback for new technical indicators"""
try:
self.update_data_queue('technical_indicators', symbol, indicators)
except Exception as e:
logger.error(f"Error processing indicators data callback: {e}")
def _on_cob_data(self, symbol: str, cob_data: Any):
"""Callback for new COB data"""
try:
self.update_data_queue('cob_data', symbol, cob_data)
except Exception as e:
logger.error(f"Error processing COB data callback: {e}")
def _start_data_polling_thread(self):
"""Start background thread to poll data and populate queues"""
def data_polling_worker():
"""Background worker to poll data and update queues"""
poll_count = 0
while self.running:
try:
poll_count += 1
# Log polling activity every 30 seconds
if poll_count % 30 == 1:
logger.info(f"Data polling cycle #{poll_count} - checking data sources")
# Poll OHLCV data for all symbols and timeframes
for symbol in [self.symbol] + self.ref_symbols:
for timeframe in ['1s', '1m', '1h', '1d']:
try:
# Get latest data from data provider using correct method
if hasattr(self.data_provider, 'get_latest_candles'):
df = self.data_provider.get_latest_candles(symbol, timeframe, limit=1)
if df is not None and not df.empty:
# Convert DataFrame row to OHLCVBar
latest_row = df.iloc[-1]
from core.data_models import OHLCVBar
ohlcv_bar = OHLCVBar(
symbol=symbol,
timestamp=latest_row.name if hasattr(latest_row.name, 'to_pydatetime') else datetime.now(),
open=float(latest_row['open']),
high=float(latest_row['high']),
low=float(latest_row['low']),
close=float(latest_row['close']),
volume=float(latest_row['volume']),
timeframe=timeframe
)
self.update_data_queue(f'ohlcv_{timeframe}', symbol, ohlcv_bar)
elif hasattr(self.data_provider, 'get_historical_data'):
df = self.data_provider.get_historical_data(symbol, timeframe, limit=1)
if df is not None and not df.empty:
# Convert DataFrame row to OHLCVBar
latest_row = df.iloc[-1]
from core.data_models import OHLCVBar
ohlcv_bar = OHLCVBar(
symbol=symbol,
timestamp=latest_row.name if hasattr(latest_row.name, 'to_pydatetime') else datetime.now(),
open=float(latest_row['open']),
high=float(latest_row['high']),
low=float(latest_row['low']),
close=float(latest_row['close']),
volume=float(latest_row['volume']),
timeframe=timeframe
)
self.update_data_queue(f'ohlcv_{timeframe}', symbol, ohlcv_bar)
except Exception as e:
logger.debug(f"Error polling {symbol} {timeframe}: {e}")
# Poll technical indicators
for symbol in [self.symbol] + self.ref_symbols:
try:
# Get recent data and calculate basic indicators
df = None
if hasattr(self.data_provider, 'get_latest_candles'):
df = self.data_provider.get_latest_candles(symbol, '1m', limit=50)
elif hasattr(self.data_provider, 'get_historical_data'):
df = self.data_provider.get_historical_data(symbol, '1m', limit=50)
if df is not None and not df.empty and len(df) >= 20:
# Calculate basic technical indicators
indicators = {}
try:
import ta
indicators['rsi'] = ta.momentum.RSIIndicator(df['close']).rsi().iloc[-1]
indicators['sma_20'] = df['close'].rolling(20).mean().iloc[-1]
indicators['ema_12'] = df['close'].ewm(span=12).mean().iloc[-1]
indicators['ema_26'] = df['close'].ewm(span=26).mean().iloc[-1]
indicators['macd'] = indicators['ema_12'] - indicators['ema_26']
# Remove NaN values
indicators = {k: float(v) for k, v in indicators.items() if not pd.isna(v)}
if indicators:
self.update_data_queue('technical_indicators', symbol, indicators)
except Exception as ta_e:
logger.debug(f"Error calculating indicators for {symbol}: {ta_e}")
except Exception as e:
logger.debug(f"Error polling indicators for {symbol}: {e}")
# Poll COB data (primary symbol only)
try:
if hasattr(self.data_provider, 'get_latest_cob_data'):
cob_data = self.data_provider.get_latest_cob_data(self.symbol)
if cob_data and isinstance(cob_data, dict) and cob_data:
self.update_data_queue('cob_data', self.symbol, cob_data)
except Exception as e:
logger.debug(f"Error polling COB data: {e}")
# Sleep between polls
time.sleep(1) # Poll every second
except Exception as e:
logger.error(f"Error in data polling worker: {e}")
time.sleep(5) # Wait longer on error
# Start the polling thread
self.data_polling_thread = threading.Thread(target=data_polling_worker, daemon=True)
self.data_polling_thread.start()
logger.info("Data polling thread started")
# Populate initial data
self._populate_initial_queue_data()
def _populate_initial_queue_data(self):
"""Populate FIFO queues with initial historical data"""
try:
logger.info("Populating FIFO queues with initial data...")
# Get initial OHLCV data for all symbols and timeframes
for symbol in [self.symbol] + self.ref_symbols:
for timeframe in ['1s', '1m', '1h', '1d']:
try:
# Determine how much data to fetch based on timeframe
limits = {'1s': 500, '1m': 300, '1h': 300, '1d': 300}
limit = limits.get(timeframe, 300)
# Get historical data
df = None
if hasattr(self.data_provider, 'get_historical_data'):
df = self.data_provider.get_historical_data(symbol, timeframe, limit=limit)
if df is not None and not df.empty:
logger.info(f"Loading {len(df)} {timeframe} bars for {symbol}")
# Convert DataFrame to OHLCVBar objects and add to queue
from core.data_models import OHLCVBar
for idx, row in df.iterrows():
try:
ohlcv_bar = OHLCVBar(
symbol=symbol,
timestamp=idx if hasattr(idx, 'to_pydatetime') else datetime.now(),
open=float(row['open']),
high=float(row['high']),
low=float(row['low']),
close=float(row['close']),
volume=float(row['volume']),
timeframe=timeframe
)
self.update_data_queue(f'ohlcv_{timeframe}', symbol, ohlcv_bar)
except Exception as bar_e:
logger.debug(f"Error creating OHLCV bar: {bar_e}")
else:
logger.warning(f"No historical data available for {symbol} {timeframe}")
except Exception as e:
logger.warning(f"Error loading initial data for {symbol} {timeframe}: {e}")
# Calculate and populate technical indicators
logger.info("Calculating technical indicators...")
for symbol in [self.symbol] + self.ref_symbols:
try:
# Use 1m data to calculate indicators
if self.ensure_minimum_data('ohlcv_1m', symbol, 50):
minute_data = self.get_queue_data('ohlcv_1m', symbol, 100)
if minute_data and len(minute_data) >= 20:
# Convert to DataFrame for indicator calculation
df_data = []
for bar in minute_data:
df_data.append({
'timestamp': bar.timestamp,
'open': bar.open,
'high': bar.high,
'low': bar.low,
'close': bar.close,
'volume': bar.volume
})
df = pd.DataFrame(df_data)
df.set_index('timestamp', inplace=True)
# Calculate indicators
indicators = {}
try:
import ta
if len(df) >= 14:
indicators['rsi'] = ta.momentum.RSIIndicator(df['close']).rsi().iloc[-1]
if len(df) >= 20:
indicators['sma_20'] = df['close'].rolling(20).mean().iloc[-1]
if len(df) >= 12:
indicators['ema_12'] = df['close'].ewm(span=12).mean().iloc[-1]
if len(df) >= 26:
indicators['ema_26'] = df['close'].ewm(span=26).mean().iloc[-1]
if 'ema_12' in indicators:
indicators['macd'] = indicators['ema_12'] - indicators['ema_26']
# Bollinger Bands
if len(df) >= 20:
bb_period = 20
bb_std = 2
sma = df['close'].rolling(bb_period).mean()
std = df['close'].rolling(bb_period).std()
indicators['bb_upper'] = (sma + (std * bb_std)).iloc[-1]
indicators['bb_lower'] = (sma - (std * bb_std)).iloc[-1]
indicators['bb_middle'] = sma.iloc[-1]
# Remove NaN values
indicators = {k: float(v) for k, v in indicators.items() if not pd.isna(v)}
if indicators:
self.update_data_queue('technical_indicators', symbol, indicators)
logger.info(f"Calculated {len(indicators)} indicators for {symbol}")
except Exception as ta_e:
logger.warning(f"Error calculating indicators for {symbol}: {ta_e}")
except Exception as e:
logger.warning(f"Error processing indicators for {symbol}: {e}")
# Log final queue status
logger.info("Initial data population completed")
self.log_queue_status(detailed=True)
except Exception as e:
logger.error(f"Error populating initial queue data: {e}")
def _try_fallback_data_strategy(self, symbol: str, missing_data: List[Tuple[str, int, int]]) -> bool:
"""
Try to fill missing data using fallback strategies
Args:
symbol: Trading symbol
missing_data: List of (data_type, actual_count, min_count) tuples
Returns:
bool: True if fallback successful
"""
try:
from core.data_models import OHLCVBar
for data_type, actual_count, min_count in missing_data:
needed_count = min_count - actual_count
if data_type == 'ohlcv_1s' and needed_count > 0:
# Try to use 1m data to generate 1s data (simple interpolation)
if self.ensure_minimum_data('ohlcv_1m', symbol, 10):
logger.info(f"Using 1m data to generate {needed_count} 1s bars for {symbol}")
# Get some 1m data
minute_data = self.get_queue_data('ohlcv_1m', symbol, 10)
if minute_data:
# Generate synthetic 1s bars from 1m data
for i, minute_bar in enumerate(minute_data[-5:]): # Use last 5 minutes
# Create 60 synthetic 1s bars from each 1m bar
for second in range(60):
if len(self.data_queues['ohlcv_1s'][symbol]) >= min_count:
break
# Simple interpolation (not perfect but functional)
synthetic_bar = OHLCVBar(
symbol=symbol,
timestamp=minute_bar.timestamp,
open=minute_bar.open,
high=minute_bar.high,
low=minute_bar.low,
close=minute_bar.close,
volume=minute_bar.volume / 60, # Distribute volume
timeframe='1s'
)
self.update_data_queue('ohlcv_1s', symbol, synthetic_bar)
elif data_type == 'ohlcv_1h' and needed_count > 0:
# Try to use 1m data to generate 1h data
if self.ensure_minimum_data('ohlcv_1m', symbol, 60):
logger.info(f"Using 1m data to generate {needed_count} 1h bars for {symbol}")
minute_data = self.get_queue_data('ohlcv_1m', symbol, 300)
if minute_data and len(minute_data) >= 60:
# Group 1m bars into 1h bars
for hour_start in range(0, len(minute_data) - 60, 60):
if len(self.data_queues['ohlcv_1h'][symbol]) >= min_count:
break
hour_bars = minute_data[hour_start:hour_start + 60]
if len(hour_bars) == 60:
# Aggregate 1m bars into 1h bar
hour_bar = OHLCVBar(
symbol=symbol,
timestamp=hour_bars[0].timestamp,
open=hour_bars[0].open,
high=max(bar.high for bar in hour_bars),
low=min(bar.low for bar in hour_bars),
close=hour_bars[-1].close,
volume=sum(bar.volume for bar in hour_bars),
timeframe='1h'
)
self.update_data_queue('ohlcv_1h', symbol, hour_bar)
# Check if we now have minimum data
all_satisfied = True
for data_type, _, min_count in missing_data:
if not self.ensure_minimum_data(data_type, symbol, min_count):
all_satisfied = False
break
return all_satisfied
except Exception as e:
logger.error(f"Error in fallback data strategy: {e}")
return False