working with errors
This commit is contained in:
@ -196,11 +196,39 @@ class DataProvider:
|
|||||||
# Load existing pivot bounds from cache
|
# Load existing pivot bounds from cache
|
||||||
self._load_all_pivot_bounds()
|
self._load_all_pivot_bounds()
|
||||||
|
|
||||||
|
# Centralized data collection for models and dashboard
|
||||||
|
self.cob_data_cache: Dict[str, deque] = {} # COB data for models
|
||||||
|
self.training_data_cache: Dict[str, deque] = {} # Training data for models
|
||||||
|
self.model_data_subscribers: Dict[str, List[Callable]] = {} # Model-specific data callbacks
|
||||||
|
|
||||||
|
# Callbacks for data distribution
|
||||||
|
self.cob_data_callbacks: List[Callable] = [] # COB data callbacks
|
||||||
|
self.training_data_callbacks: List[Callable] = [] # Training data callbacks
|
||||||
|
self.model_prediction_callbacks: List[Callable] = [] # Model prediction callbacks
|
||||||
|
|
||||||
|
# Initialize data caches
|
||||||
|
for symbol in self.symbols:
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
self.cob_data_cache[binance_symbol] = deque(maxlen=300) # 5 minutes of COB data
|
||||||
|
self.training_data_cache[binance_symbol] = deque(maxlen=1000) # Training data buffer
|
||||||
|
|
||||||
|
# Data collection threads
|
||||||
|
self.data_collection_active = False
|
||||||
|
|
||||||
|
# COB data collection
|
||||||
|
self.cob_collection_active = False
|
||||||
|
self.cob_collection_thread = None
|
||||||
|
|
||||||
|
# Training data collection
|
||||||
|
self.training_data_collection_active = False
|
||||||
|
self.training_data_thread = None
|
||||||
|
|
||||||
logger.info(f"DataProvider initialized for symbols: {self.symbols}")
|
logger.info(f"DataProvider initialized for symbols: {self.symbols}")
|
||||||
logger.info(f"Timeframes: {self.timeframes}")
|
logger.info(f"Timeframes: {self.timeframes}")
|
||||||
logger.info("Centralized data distribution enabled")
|
logger.info("Centralized data distribution enabled")
|
||||||
logger.info("Pivot-based normalization system enabled")
|
logger.info("Pivot-based normalization system enabled")
|
||||||
logger.info("Williams Market Structure integration enabled")
|
logger.info("Williams Market Structure integration enabled")
|
||||||
|
logger.info("COB and training data collection enabled")
|
||||||
|
|
||||||
# Rate limiting
|
# Rate limiting
|
||||||
self.last_request_time = {}
|
self.last_request_time = {}
|
||||||
@ -2559,4 +2587,591 @@ class DataProvider:
|
|||||||
if attempt < self.max_retries - 1:
|
if attempt < self.max_retries - 1:
|
||||||
time.sleep(5 * (attempt + 1))
|
time.sleep(5 * (attempt + 1))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
# ===== CENTRALIZED DATA COLLECTION METHODS =====
|
||||||
|
|
||||||
|
def start_centralized_data_collection(self):
|
||||||
|
"""Start all centralized data collection processes"""
|
||||||
|
logger.info("Starting centralized data collection for all models and dashboard")
|
||||||
|
|
||||||
|
# Start COB data collection
|
||||||
|
self.start_cob_data_collection()
|
||||||
|
|
||||||
|
# Start training data collection
|
||||||
|
self.start_training_data_collection()
|
||||||
|
|
||||||
|
logger.info("All centralized data collection processes started")
|
||||||
|
|
||||||
|
def stop_centralized_data_collection(self):
|
||||||
|
"""Stop all centralized data collection processes"""
|
||||||
|
logger.info("Stopping centralized data collection")
|
||||||
|
|
||||||
|
# Stop COB collection
|
||||||
|
self.cob_collection_active = False
|
||||||
|
if self.cob_collection_thread and self.cob_collection_thread.is_alive():
|
||||||
|
self.cob_collection_thread.join(timeout=5)
|
||||||
|
|
||||||
|
# Stop training data collection
|
||||||
|
self.training_data_collection_active = False
|
||||||
|
if self.training_data_thread and self.training_data_thread.is_alive():
|
||||||
|
self.training_data_thread.join(timeout=5)
|
||||||
|
|
||||||
|
logger.info("Centralized data collection stopped")
|
||||||
|
|
||||||
|
def start_cob_data_collection(self):
|
||||||
|
"""Start COB (Consolidated Order Book) data collection"""
|
||||||
|
if self.cob_collection_active:
|
||||||
|
logger.warning("COB data collection already active")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.cob_collection_active = True
|
||||||
|
self.cob_collection_thread = Thread(target=self._cob_collection_worker, daemon=True)
|
||||||
|
self.cob_collection_thread.start()
|
||||||
|
logger.info("COB data collection started")
|
||||||
|
|
||||||
|
def _cob_collection_worker(self):
|
||||||
|
"""Worker thread for COB data collection"""
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
|
logger.info("COB data collection worker started")
|
||||||
|
|
||||||
|
# Use separate threads for each symbol to achieve higher update frequency
|
||||||
|
def collect_symbol_data(symbol):
|
||||||
|
while self.cob_collection_active:
|
||||||
|
try:
|
||||||
|
self._collect_cob_data_for_symbol(symbol)
|
||||||
|
# Sleep for a very short time to achieve ~120 updates/sec across all symbols
|
||||||
|
# With 2 symbols, each can update at ~60/sec
|
||||||
|
time.sleep(0.016) # ~60 updates per second per symbol
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error collecting COB data for {symbol}: {e}")
|
||||||
|
time.sleep(1) # Short recovery time
|
||||||
|
|
||||||
|
# Start a thread for each symbol
|
||||||
|
threads = []
|
||||||
|
for symbol in self.symbols:
|
||||||
|
thread = threading.Thread(target=collect_symbol_data, args=(symbol,), daemon=True)
|
||||||
|
thread.start()
|
||||||
|
threads.append(thread)
|
||||||
|
|
||||||
|
# Keep the main thread alive
|
||||||
|
while self.cob_collection_active:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Join threads when collection is stopped
|
||||||
|
for thread in threads:
|
||||||
|
thread.join(timeout=1)
|
||||||
|
|
||||||
|
def _collect_cob_data_for_symbol(self, symbol: str):
|
||||||
|
"""Collect COB data for a specific symbol using Binance REST API"""
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Convert symbol format
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
|
||||||
|
# Get order book data
|
||||||
|
url = f"https://api.binance.com/api/v3/depth"
|
||||||
|
params = {
|
||||||
|
'symbol': binance_symbol,
|
||||||
|
'limit': 100 # Get top 100 levels
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params, timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
order_book = response.json()
|
||||||
|
|
||||||
|
# Process and cache the data
|
||||||
|
cob_snapshot = self._process_order_book_data(symbol, order_book)
|
||||||
|
|
||||||
|
# Store in cache (ensure cache exists)
|
||||||
|
if binance_symbol not in self.cob_data_cache:
|
||||||
|
self.cob_data_cache[binance_symbol] = deque(maxlen=300)
|
||||||
|
|
||||||
|
self.cob_data_cache[binance_symbol].append(cob_snapshot)
|
||||||
|
|
||||||
|
# Distribute to COB data subscribers
|
||||||
|
self._distribute_cob_data(symbol, cob_snapshot)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.debug(f"Failed to fetch COB data for {symbol}: {response.status_code}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error collecting COB data for {symbol}: {e}")
|
||||||
|
|
||||||
|
def _process_order_book_data(self, symbol: str, order_book: dict) -> dict:
|
||||||
|
"""Process raw order book data into structured COB snapshot with multi-timeframe imbalance metrics"""
|
||||||
|
try:
|
||||||
|
bids = [[float(price), float(qty)] for price, qty in order_book.get('bids', [])]
|
||||||
|
asks = [[float(price), float(qty)] for price, qty in order_book.get('asks', [])]
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
total_bid_volume = sum(qty for _, qty in bids)
|
||||||
|
total_ask_volume = sum(qty for _, qty in asks)
|
||||||
|
|
||||||
|
best_bid = bids[0][0] if bids else 0
|
||||||
|
best_ask = asks[0][0] if asks else 0
|
||||||
|
mid_price = (best_bid + best_ask) / 2 if best_bid and best_ask else 0
|
||||||
|
spread = best_ask - best_bid if best_bid and best_ask else 0
|
||||||
|
spread_bps = (spread / mid_price * 10000) if mid_price > 0 else 0
|
||||||
|
|
||||||
|
# Calculate current imbalance
|
||||||
|
imbalance = (total_bid_volume - total_ask_volume) / (total_bid_volume + total_ask_volume) if (total_bid_volume + total_ask_volume) > 0 else 0
|
||||||
|
|
||||||
|
# Calculate multi-timeframe imbalances
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
|
||||||
|
# Initialize imbalance metrics
|
||||||
|
imbalance_1s = imbalance # Current imbalance is 1s
|
||||||
|
imbalance_5s = imbalance # Default to current if not enough history
|
||||||
|
imbalance_15s = imbalance
|
||||||
|
imbalance_60s = imbalance
|
||||||
|
|
||||||
|
# Calculate historical imbalances if we have enough data
|
||||||
|
if binance_symbol in self.cob_data_cache:
|
||||||
|
cache = self.cob_data_cache[binance_symbol]
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
# Get snapshots for different timeframes
|
||||||
|
snapshots_5s = [s for s in cache if (now - s['timestamp']).total_seconds() <= 5]
|
||||||
|
snapshots_15s = [s for s in cache if (now - s['timestamp']).total_seconds() <= 15]
|
||||||
|
snapshots_60s = [s for s in cache if (now - s['timestamp']).total_seconds() <= 60]
|
||||||
|
|
||||||
|
# Calculate imbalances for each timeframe
|
||||||
|
if snapshots_5s:
|
||||||
|
bid_vol_5s = sum(s['stats']['bid_liquidity'] for s in snapshots_5s)
|
||||||
|
ask_vol_5s = sum(s['stats']['ask_liquidity'] for s in snapshots_5s)
|
||||||
|
total_vol_5s = bid_vol_5s + ask_vol_5s
|
||||||
|
imbalance_5s = (bid_vol_5s - ask_vol_5s) / total_vol_5s if total_vol_5s > 0 else 0
|
||||||
|
|
||||||
|
if snapshots_15s:
|
||||||
|
bid_vol_15s = sum(s['stats']['bid_liquidity'] for s in snapshots_15s)
|
||||||
|
ask_vol_15s = sum(s['stats']['ask_liquidity'] for s in snapshots_15s)
|
||||||
|
total_vol_15s = bid_vol_15s + ask_vol_15s
|
||||||
|
imbalance_15s = (bid_vol_15s - ask_vol_15s) / total_vol_15s if total_vol_15s > 0 else 0
|
||||||
|
|
||||||
|
if snapshots_60s:
|
||||||
|
bid_vol_60s = sum(s['stats']['bid_liquidity'] for s in snapshots_60s)
|
||||||
|
ask_vol_60s = sum(s['stats']['ask_liquidity'] for s in snapshots_60s)
|
||||||
|
total_vol_60s = bid_vol_60s + ask_vol_60s
|
||||||
|
imbalance_60s = (bid_vol_60s - ask_vol_60s) / total_vol_60s if total_vol_60s > 0 else 0
|
||||||
|
|
||||||
|
cob_snapshot = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'timestamp': datetime.now(),
|
||||||
|
'bids': bids[:20], # Top 20 levels
|
||||||
|
'asks': asks[:20], # Top 20 levels
|
||||||
|
'stats': {
|
||||||
|
'best_bid': best_bid,
|
||||||
|
'best_ask': best_ask,
|
||||||
|
'mid_price': mid_price,
|
||||||
|
'spread': spread,
|
||||||
|
'spread_bps': spread_bps,
|
||||||
|
'bid_liquidity': total_bid_volume,
|
||||||
|
'ask_liquidity': total_ask_volume,
|
||||||
|
'total_liquidity': total_bid_volume + total_ask_volume,
|
||||||
|
'imbalance': imbalance,
|
||||||
|
'imbalance_1s': imbalance_1s,
|
||||||
|
'imbalance_5s': imbalance_5s,
|
||||||
|
'imbalance_15s': imbalance_15s,
|
||||||
|
'imbalance_60s': imbalance_60s,
|
||||||
|
'levels': len(bids) + len(asks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cob_snapshot
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing order book data for {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def start_training_data_collection(self):
|
||||||
|
"""Start training data collection for models"""
|
||||||
|
if self.training_data_collection_active:
|
||||||
|
logger.warning("Training data collection already active")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.training_data_collection_active = True
|
||||||
|
self.training_data_thread = Thread(target=self._training_data_collection_worker, daemon=True)
|
||||||
|
self.training_data_thread.start()
|
||||||
|
logger.info("Training data collection started")
|
||||||
|
|
||||||
|
def _training_data_collection_worker(self):
|
||||||
|
"""Worker thread for training data collection"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
logger.info("Training data collection worker started")
|
||||||
|
|
||||||
|
while self.training_data_collection_active:
|
||||||
|
try:
|
||||||
|
# Collect training data for all symbols
|
||||||
|
for symbol in self.symbols:
|
||||||
|
training_sample = self._collect_training_sample(symbol)
|
||||||
|
if training_sample:
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
self.training_data_cache[binance_symbol].append(training_sample)
|
||||||
|
|
||||||
|
# Distribute to training data subscribers
|
||||||
|
self._distribute_training_data(symbol, training_sample)
|
||||||
|
|
||||||
|
# Sleep for 5 seconds between collections
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in training data collection worker: {e}")
|
||||||
|
time.sleep(10) # Wait longer on error
|
||||||
|
|
||||||
|
def _collect_training_sample(self, symbol: str) -> Optional[dict]:
|
||||||
|
"""Collect a training sample for a specific symbol"""
|
||||||
|
try:
|
||||||
|
# Get recent market data
|
||||||
|
recent_data = self.get_historical_data(symbol, '1m', limit=100)
|
||||||
|
if recent_data is None or len(recent_data) < 50:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get recent ticks
|
||||||
|
recent_ticks = self.get_recent_ticks(symbol, count=100)
|
||||||
|
if len(recent_ticks) < 10:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get COB data
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
recent_cob = list(self.cob_data_cache.get(binance_symbol, []))[-10:] if binance_symbol in self.cob_data_cache else []
|
||||||
|
|
||||||
|
# Create training sample
|
||||||
|
training_sample = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'timestamp': datetime.now(),
|
||||||
|
'ohlcv_data': recent_data.tail(50).to_dict('records'),
|
||||||
|
'tick_data': [
|
||||||
|
{
|
||||||
|
'price': tick.price,
|
||||||
|
'volume': tick.volume,
|
||||||
|
'timestamp': tick.timestamp
|
||||||
|
} for tick in recent_ticks[-50:]
|
||||||
|
],
|
||||||
|
'cob_data': recent_cob,
|
||||||
|
'features': self._extract_training_features(symbol, recent_data, recent_ticks, recent_cob)
|
||||||
|
}
|
||||||
|
|
||||||
|
return training_sample
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error collecting training sample for {symbol}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_training_features(self, symbol: str, ohlcv_data: pd.DataFrame,
|
||||||
|
recent_ticks: List[MarketTick], recent_cob: List[dict]) -> dict:
|
||||||
|
"""Extract features for training from various data sources"""
|
||||||
|
try:
|
||||||
|
features = {}
|
||||||
|
|
||||||
|
# OHLCV features
|
||||||
|
if len(ohlcv_data) > 0:
|
||||||
|
latest = ohlcv_data.iloc[-1]
|
||||||
|
features.update({
|
||||||
|
'price': latest['close'],
|
||||||
|
'volume': latest['volume'],
|
||||||
|
'price_change_1m': (latest['close'] - ohlcv_data.iloc[-2]['close']) / ohlcv_data.iloc[-2]['close'] if len(ohlcv_data) > 1 else 0,
|
||||||
|
'volume_ratio': latest['volume'] / ohlcv_data['volume'].mean() if len(ohlcv_data) > 1 else 1,
|
||||||
|
'volatility': ohlcv_data['close'].pct_change().std() if len(ohlcv_data) > 1 else 0
|
||||||
|
})
|
||||||
|
|
||||||
|
# Tick features
|
||||||
|
if recent_ticks:
|
||||||
|
tick_prices = [tick.price for tick in recent_ticks]
|
||||||
|
tick_volumes = [tick.volume for tick in recent_ticks]
|
||||||
|
features.update({
|
||||||
|
'tick_price_std': np.std(tick_prices) if len(tick_prices) > 1 else 0,
|
||||||
|
'tick_volume_mean': np.mean(tick_volumes),
|
||||||
|
'tick_count': len(recent_ticks)
|
||||||
|
})
|
||||||
|
|
||||||
|
# COB features
|
||||||
|
if recent_cob:
|
||||||
|
latest_cob = recent_cob[-1]
|
||||||
|
if 'stats' in latest_cob:
|
||||||
|
stats = latest_cob['stats']
|
||||||
|
features.update({
|
||||||
|
'spread_bps': stats.get('spread_bps', 0),
|
||||||
|
'imbalance': stats.get('imbalance', 0),
|
||||||
|
'liquidity': stats.get('total_liquidity', 0),
|
||||||
|
'cob_levels': stats.get('levels', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting training features for {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# ===== SUBSCRIPTION METHODS FOR MODELS AND DASHBOARD =====
|
||||||
|
|
||||||
|
def subscribe_to_cob_data(self, callback: Callable[[str, dict], None]) -> str:
|
||||||
|
"""Subscribe to COB data updates"""
|
||||||
|
subscriber_id = str(uuid.uuid4())
|
||||||
|
self.cob_data_callbacks.append(callback)
|
||||||
|
logger.info(f"COB data subscriber added: {subscriber_id}")
|
||||||
|
return subscriber_id
|
||||||
|
|
||||||
|
def subscribe_to_training_data(self, callback: Callable[[str, dict], None]) -> str:
|
||||||
|
"""Subscribe to training data updates"""
|
||||||
|
subscriber_id = str(uuid.uuid4())
|
||||||
|
self.training_data_callbacks.append(callback)
|
||||||
|
logger.info(f"Training data subscriber added: {subscriber_id}")
|
||||||
|
return subscriber_id
|
||||||
|
|
||||||
|
def subscribe_to_model_predictions(self, callback: Callable[[str, dict], None]) -> str:
|
||||||
|
"""Subscribe to model prediction updates"""
|
||||||
|
subscriber_id = str(uuid.uuid4())
|
||||||
|
self.model_prediction_callbacks.append(callback)
|
||||||
|
logger.info(f"Model prediction subscriber added: {subscriber_id}")
|
||||||
|
return subscriber_id
|
||||||
|
|
||||||
|
def _distribute_cob_data(self, symbol: str, cob_snapshot: dict):
|
||||||
|
"""Distribute COB data to all subscribers"""
|
||||||
|
for callback in self.cob_data_callbacks:
|
||||||
|
try:
|
||||||
|
Thread(target=lambda: callback(symbol, cob_snapshot), daemon=True).start()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error distributing COB data: {e}")
|
||||||
|
|
||||||
|
def _distribute_training_data(self, symbol: str, training_sample: dict):
|
||||||
|
"""Distribute training data to all subscribers"""
|
||||||
|
for callback in self.training_data_callbacks:
|
||||||
|
try:
|
||||||
|
Thread(target=lambda: callback(symbol, training_sample), daemon=True).start()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error distributing training data: {e}")
|
||||||
|
|
||||||
|
def _distribute_model_predictions(self, symbol: str, prediction: dict):
|
||||||
|
"""Distribute model predictions to all subscribers"""
|
||||||
|
for callback in self.model_prediction_callbacks:
|
||||||
|
try:
|
||||||
|
Thread(target=lambda: callback(symbol, prediction), daemon=True).start()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error distributing model prediction: {e}")
|
||||||
|
|
||||||
|
# ===== DATA ACCESS METHODS FOR MODELS AND DASHBOARD =====
|
||||||
|
|
||||||
|
def get_cob_data(self, symbol: str, count: int = 50) -> List[dict]:
|
||||||
|
"""Get recent COB data for a symbol"""
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
if binance_symbol in self.cob_data_cache:
|
||||||
|
return list(self.cob_data_cache[binance_symbol])[-count:]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_training_data(self, symbol: str, count: int = 100) -> List[dict]:
|
||||||
|
"""Get recent training data for a symbol"""
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
if binance_symbol in self.training_data_cache:
|
||||||
|
return list(self.training_data_cache[binance_symbol])[-count:]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def collect_cob_data(self, symbol: str) -> dict:
|
||||||
|
"""
|
||||||
|
Collect Consolidated Order Book (COB) data for a symbol using REST API
|
||||||
|
|
||||||
|
This centralized method collects COB data for all consumers (models, dashboard, etc.)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Use Binance REST API for order book data
|
||||||
|
binance_symbol = symbol.replace('/', '')
|
||||||
|
url = f"https://api.binance.com/api/v3/depth?symbol={binance_symbol}&limit=500"
|
||||||
|
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Process order book data
|
||||||
|
bids = [[float(price), float(qty)] for price, qty in data.get('bids', [])]
|
||||||
|
asks = [[float(price), float(qty)] for price, qty in data.get('asks', [])]
|
||||||
|
|
||||||
|
# Calculate mid price
|
||||||
|
best_bid = bids[0][0] if bids else 0
|
||||||
|
best_ask = asks[0][0] if asks else 0
|
||||||
|
mid_price = (best_bid + best_ask) / 2 if best_bid and best_ask else 0
|
||||||
|
|
||||||
|
# Calculate order book stats
|
||||||
|
bid_liquidity = sum(qty for _, qty in bids[:20])
|
||||||
|
ask_liquidity = sum(qty for _, qty in asks[:20])
|
||||||
|
total_liquidity = bid_liquidity + ask_liquidity
|
||||||
|
|
||||||
|
# Calculate imbalance
|
||||||
|
imbalance = (bid_liquidity - ask_liquidity) / total_liquidity if total_liquidity > 0 else 0
|
||||||
|
|
||||||
|
# Calculate spread in basis points
|
||||||
|
spread = (best_ask - best_bid) / mid_price * 10000 if mid_price > 0 else 0
|
||||||
|
|
||||||
|
# Create COB snapshot
|
||||||
|
cob_snapshot = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'timestamp': int(time.time() * 1000),
|
||||||
|
'bids': bids[:50], # Limit to top 50 levels
|
||||||
|
'asks': asks[:50], # Limit to top 50 levels
|
||||||
|
'stats': {
|
||||||
|
'mid_price': mid_price,
|
||||||
|
'best_bid': best_bid,
|
||||||
|
'best_ask': best_ask,
|
||||||
|
'bid_liquidity': bid_liquidity,
|
||||||
|
'ask_liquidity': ask_liquidity,
|
||||||
|
'total_liquidity': total_liquidity,
|
||||||
|
'imbalance': imbalance,
|
||||||
|
'spread_bps': spread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store in cache
|
||||||
|
with self.subscriber_lock:
|
||||||
|
if not hasattr(self, 'cob_data_cache'):
|
||||||
|
self.cob_data_cache = {}
|
||||||
|
|
||||||
|
if symbol not in self.cob_data_cache:
|
||||||
|
self.cob_data_cache[symbol] = []
|
||||||
|
|
||||||
|
# Add to cache with max size limit
|
||||||
|
self.cob_data_cache[symbol].append(cob_snapshot)
|
||||||
|
if len(self.cob_data_cache[symbol]) > 300: # Keep 5 minutes of 1s data
|
||||||
|
self.cob_data_cache[symbol].pop(0)
|
||||||
|
|
||||||
|
# Notify subscribers
|
||||||
|
self._notify_cob_subscribers(symbol, cob_snapshot)
|
||||||
|
|
||||||
|
return cob_snapshot
|
||||||
|
else:
|
||||||
|
logger.warning(f"Failed to fetch COB data for {symbol}: {response.status_code}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error collecting COB data for {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def start_cob_collection(self):
|
||||||
|
"""
|
||||||
|
Start COB data collection in background thread
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
def cob_collector():
|
||||||
|
"""Collect COB data using REST API calls"""
|
||||||
|
logger.info("Starting centralized COB data collection")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Collect data for both symbols
|
||||||
|
for symbol in ['ETH/USDT', 'BTC/USDT']:
|
||||||
|
self.collect_cob_data(symbol)
|
||||||
|
|
||||||
|
# Sleep for 1 second between collections
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error in COB collection: {e}")
|
||||||
|
time.sleep(5) # Wait longer on error
|
||||||
|
|
||||||
|
# Start collector in background thread
|
||||||
|
if not hasattr(self, '_cob_thread_started') or not self._cob_thread_started:
|
||||||
|
cob_thread = threading.Thread(target=cob_collector, daemon=True)
|
||||||
|
cob_thread.start()
|
||||||
|
self._cob_thread_started = True
|
||||||
|
logger.info("Centralized COB data collection started")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error starting COB collection: {e}")
|
||||||
|
|
||||||
|
def _notify_cob_subscribers(self, symbol: str, cob_snapshot: dict):
|
||||||
|
"""Notify subscribers of new COB data"""
|
||||||
|
with self.subscriber_lock:
|
||||||
|
if not hasattr(self, 'cob_subscribers'):
|
||||||
|
self.cob_subscribers = {}
|
||||||
|
|
||||||
|
# Notify all subscribers for this symbol
|
||||||
|
for subscriber_id, callback in self.cob_subscribers.items():
|
||||||
|
try:
|
||||||
|
callback(symbol, cob_snapshot)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error notifying COB subscriber {subscriber_id}: {e}")
|
||||||
|
|
||||||
|
def subscribe_to_cob(self, callback) -> str:
|
||||||
|
"""Subscribe to COB data updates"""
|
||||||
|
with self.subscriber_lock:
|
||||||
|
if not hasattr(self, 'cob_subscribers'):
|
||||||
|
self.cob_subscribers = {}
|
||||||
|
|
||||||
|
subscriber_id = str(uuid.uuid4())
|
||||||
|
self.cob_subscribers[subscriber_id] = callback
|
||||||
|
|
||||||
|
# Start collection if not already started
|
||||||
|
self.start_cob_collection()
|
||||||
|
|
||||||
|
return subscriber_id
|
||||||
|
|
||||||
|
def get_latest_cob_data(self, symbol: str) -> dict:
|
||||||
|
"""Get latest COB data for a symbol"""
|
||||||
|
with self.subscriber_lock:
|
||||||
|
# Convert symbol to Binance format for cache lookup
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
|
||||||
|
logger.debug(f"Getting COB data for {symbol} (binance: {binance_symbol})")
|
||||||
|
|
||||||
|
if not hasattr(self, 'cob_data_cache'):
|
||||||
|
logger.debug("COB data cache not initialized")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if binance_symbol not in self.cob_data_cache:
|
||||||
|
logger.debug(f"Symbol {binance_symbol} not in COB cache. Available: {list(self.cob_data_cache.keys())}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if not self.cob_data_cache[binance_symbol]:
|
||||||
|
logger.debug(f"COB cache for {binance_symbol} is empty")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
latest_data = self.cob_data_cache[binance_symbol][-1]
|
||||||
|
logger.debug(f"Latest COB data type for {binance_symbol}: {type(latest_data)}")
|
||||||
|
return latest_data
|
||||||
|
|
||||||
|
def get_cob_data(self, symbol: str, count: int = 50) -> List[dict]:
|
||||||
|
"""Get recent COB data for a symbol"""
|
||||||
|
with self.subscriber_lock:
|
||||||
|
# Convert symbol to Binance format for cache lookup
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
|
||||||
|
if not hasattr(self, 'cob_data_cache') or binance_symbol not in self.cob_data_cache:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Return the most recent 'count' snapshots
|
||||||
|
return list(self.cob_data_cache[binance_symbol])[-count:]
|
||||||
|
|
||||||
|
def get_data_summary(self) -> dict:
|
||||||
|
"""Get summary of all collected data"""
|
||||||
|
summary = {
|
||||||
|
'symbols': self.symbols,
|
||||||
|
'subscribers': {
|
||||||
|
'tick_subscribers': len(self.subscribers),
|
||||||
|
'cob_subscribers': len(self.cob_data_callbacks),
|
||||||
|
'training_subscribers': len(self.training_data_callbacks),
|
||||||
|
'prediction_subscribers': len(self.model_prediction_callbacks)
|
||||||
|
},
|
||||||
|
'data_counts': {},
|
||||||
|
'collection_status': {
|
||||||
|
'cob_collection': self.cob_collection_active,
|
||||||
|
'training_collection': self.training_data_collection_active,
|
||||||
|
'streaming': self.is_streaming
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add data counts for each symbol
|
||||||
|
for symbol in self.symbols:
|
||||||
|
binance_symbol = symbol.replace('/', '').upper()
|
||||||
|
summary['data_counts'][symbol] = {
|
||||||
|
'ticks': len(self.tick_buffers.get(binance_symbol, [])),
|
||||||
|
'cob_snapshots': len(self.cob_data_cache.get(binance_symbol, [])),
|
||||||
|
'training_samples': len(self.training_data_cache.get(binance_symbol, []))
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary
|
@ -215,6 +215,11 @@ class TradingOrchestrator:
|
|||||||
logger.info(f"Symbols: {self.symbols}")
|
logger.info(f"Symbols: {self.symbols}")
|
||||||
logger.info("Universal Data Adapter integrated for centralized data flow")
|
logger.info("Universal Data Adapter integrated for centralized data flow")
|
||||||
|
|
||||||
|
# Start centralized data collection for all models and dashboard
|
||||||
|
logger.info("Starting centralized data collection...")
|
||||||
|
self.data_provider.start_centralized_data_collection()
|
||||||
|
logger.info("Centralized data collection started - all models and dashboard will receive data")
|
||||||
|
|
||||||
# Initialize models, COB integration, and training system
|
# Initialize models, COB integration, and training system
|
||||||
self._initialize_ml_models()
|
self._initialize_ml_models()
|
||||||
self._initialize_cob_integration()
|
self._initialize_cob_integration()
|
||||||
|
@ -1,40 +1,331 @@
|
|||||||
import psutil
|
"""
|
||||||
|
Kill Stale Processes
|
||||||
|
|
||||||
|
This script identifies and kills stale Python processes that might be causing
|
||||||
|
the dashboard startup freeze. It looks for:
|
||||||
|
1. Hanging dashboard processes
|
||||||
|
2. Stale COB data collection threads
|
||||||
|
3. Matplotlib GUI processes
|
||||||
|
4. Blocked network connections
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python kill_stale_processes.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import psutil
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
try:
|
def find_python_processes():
|
||||||
current_pid = psutil.Process().pid
|
"""Find all Python processes"""
|
||||||
processes = [
|
python_processes = []
|
||||||
p for p in psutil.process_iter()
|
|
||||||
if any(x in p.name().lower() for x in ["python", "tensorboard"])
|
try:
|
||||||
and any(x in ' '.join(p.cmdline()) for x in ["scalping", "training", "tensorboard"])
|
for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'create_time', 'status']):
|
||||||
and p.pid != current_pid
|
|
||||||
]
|
|
||||||
for p in processes:
|
|
||||||
try:
|
|
||||||
p.kill()
|
|
||||||
print(f"Killed process: PID={p.pid}, Name={p.name()}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error killing PID={p.pid}: {e}")
|
|
||||||
|
|
||||||
killed_pids = set()
|
|
||||||
for port in range(8050, 8052):
|
|
||||||
for proc in psutil.process_iter():
|
|
||||||
if proc.pid == current_pid:
|
|
||||||
continue
|
|
||||||
try:
|
try:
|
||||||
for conn in proc.connections(kind="inet"):
|
if proc.info['name'] and 'python' in proc.info['name'].lower():
|
||||||
if conn.laddr.port == port:
|
# Get command line to identify dashboard processes
|
||||||
if proc.pid not in killed_pids:
|
cmdline = ' '.join(proc.info['cmdline']) if proc.info['cmdline'] else ''
|
||||||
proc.kill()
|
|
||||||
print(f"Killed process on port {port}: PID={proc.pid}, Name={proc.name()}")
|
python_processes.append({
|
||||||
killed_pids.add(proc.pid)
|
'pid': proc.info['pid'],
|
||||||
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
'name': proc.info['name'],
|
||||||
|
'cmdline': cmdline,
|
||||||
|
'create_time': proc.info['create_time'],
|
||||||
|
'status': proc.info['status'],
|
||||||
|
'process': proc
|
||||||
|
})
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
|
||||||
print(f"Error checking/killing PID={proc.pid} for port {port}: {e}")
|
except Exception as e:
|
||||||
if not any(pid for pid in killed_pids):
|
print(f"Error finding Python processes: {e}")
|
||||||
print(f"No process found using port {port}")
|
|
||||||
print("Stale processes killed")
|
return python_processes
|
||||||
except Exception as e:
|
|
||||||
print(f"Error in kill_stale_processes.py: {e}")
|
def identify_dashboard_processes(python_processes):
|
||||||
sys.exit(1)
|
"""Identify processes related to the dashboard"""
|
||||||
|
dashboard_processes = []
|
||||||
|
|
||||||
|
dashboard_keywords = [
|
||||||
|
'clean_dashboard',
|
||||||
|
'run_clean_dashboard',
|
||||||
|
'dashboard',
|
||||||
|
'trading',
|
||||||
|
'cob_data',
|
||||||
|
'orchestrator',
|
||||||
|
'data_provider'
|
||||||
|
]
|
||||||
|
|
||||||
|
for proc_info in python_processes:
|
||||||
|
cmdline = proc_info['cmdline'].lower()
|
||||||
|
|
||||||
|
# Check if this is a dashboard-related process
|
||||||
|
is_dashboard = any(keyword in cmdline for keyword in dashboard_keywords)
|
||||||
|
|
||||||
|
if is_dashboard:
|
||||||
|
dashboard_processes.append(proc_info)
|
||||||
|
|
||||||
|
return dashboard_processes
|
||||||
|
|
||||||
|
def identify_stale_processes(python_processes):
|
||||||
|
"""Identify potentially stale processes"""
|
||||||
|
stale_processes = []
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
for proc_info in python_processes:
|
||||||
|
try:
|
||||||
|
proc = proc_info['process']
|
||||||
|
|
||||||
|
# Check if process is in a problematic state
|
||||||
|
if proc_info['status'] in ['zombie', 'stopped']:
|
||||||
|
stale_processes.append({
|
||||||
|
**proc_info,
|
||||||
|
'reason': f"Process status: {proc_info['status']}"
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if process has been running for a very long time without activity
|
||||||
|
age_hours = (current_time - proc_info['create_time']) / 3600
|
||||||
|
if age_hours > 24: # Running for more than 24 hours
|
||||||
|
try:
|
||||||
|
# Check CPU usage
|
||||||
|
cpu_percent = proc.cpu_percent(interval=1)
|
||||||
|
if cpu_percent < 0.1: # Very low CPU usage
|
||||||
|
stale_processes.append({
|
||||||
|
**proc_info,
|
||||||
|
'reason': f"Old process ({age_hours:.1f}h) with low CPU usage ({cpu_percent:.1f}%)"
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check for processes with high memory usage but no activity
|
||||||
|
try:
|
||||||
|
memory_info = proc.memory_info()
|
||||||
|
memory_mb = memory_info.rss / 1024 / 1024
|
||||||
|
|
||||||
|
if memory_mb > 500: # More than 500MB
|
||||||
|
cpu_percent = proc.cpu_percent(interval=1)
|
||||||
|
if cpu_percent < 0.1:
|
||||||
|
stale_processes.append({
|
||||||
|
**proc_info,
|
||||||
|
'reason': f"High memory usage ({memory_mb:.1f}MB) with low CPU usage ({cpu_percent:.1f}%)"
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return stale_processes
|
||||||
|
|
||||||
|
def kill_process_safely(proc_info, force=False):
|
||||||
|
"""Kill a process safely"""
|
||||||
|
try:
|
||||||
|
proc = proc_info['process']
|
||||||
|
pid = proc_info['pid']
|
||||||
|
|
||||||
|
print(f"Attempting to {'force kill' if force else 'terminate'} PID {pid}: {proc_info['name']}")
|
||||||
|
|
||||||
|
if force:
|
||||||
|
# Force kill
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
os.system(f"taskkill /F /PID {pid}")
|
||||||
|
else: # Unix/Linux
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
else:
|
||||||
|
# Graceful termination
|
||||||
|
proc.terminate()
|
||||||
|
|
||||||
|
# Wait for termination
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=5)
|
||||||
|
print(f"✅ Process {pid} terminated gracefully")
|
||||||
|
return True
|
||||||
|
except psutil.TimeoutExpired:
|
||||||
|
print(f"⚠️ Process {pid} didn't terminate gracefully, will force kill")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"✅ Process {pid} killed")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
|
||||||
|
print(f"⚠️ Could not kill process {proc_info['pid']}: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error killing process {proc_info['pid']}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_port_usage():
|
||||||
|
"""Check if dashboard port is in use"""
|
||||||
|
try:
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# Check if port 8050 is in use
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
result = sock.connect_ex(('localhost', 8050))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if result == 0:
|
||||||
|
print("⚠️ Port 8050 is in use")
|
||||||
|
|
||||||
|
# Find process using the port
|
||||||
|
for conn in psutil.net_connections():
|
||||||
|
if conn.laddr.port == 8050:
|
||||||
|
try:
|
||||||
|
proc = psutil.Process(conn.pid)
|
||||||
|
print(f" Port 8050 used by PID {conn.pid}: {proc.name()}")
|
||||||
|
return conn.pid
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print("✅ Port 8050 is available")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error checking port usage: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function"""
|
||||||
|
print("🔍 Stale Process Killer")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Find all Python processes
|
||||||
|
print("🔍 Finding Python processes...")
|
||||||
|
python_processes = find_python_processes()
|
||||||
|
print(f"Found {len(python_processes)} Python processes")
|
||||||
|
|
||||||
|
# Step 2: Identify dashboard processes
|
||||||
|
print("\n🎯 Identifying dashboard processes...")
|
||||||
|
dashboard_processes = identify_dashboard_processes(python_processes)
|
||||||
|
|
||||||
|
if dashboard_processes:
|
||||||
|
print(f"Found {len(dashboard_processes)} dashboard-related processes:")
|
||||||
|
for proc in dashboard_processes:
|
||||||
|
age_hours = (time.time() - proc['create_time']) / 3600
|
||||||
|
print(f" PID {proc['pid']}: {proc['name']} (age: {age_hours:.1f}h, status: {proc['status']})")
|
||||||
|
print(f" Command: {proc['cmdline'][:100]}...")
|
||||||
|
else:
|
||||||
|
print("No dashboard processes found")
|
||||||
|
|
||||||
|
# Step 3: Check port usage
|
||||||
|
print("\n🌐 Checking port usage...")
|
||||||
|
port_pid = check_port_usage()
|
||||||
|
|
||||||
|
# Step 4: Identify stale processes
|
||||||
|
print("\n🕵️ Identifying stale processes...")
|
||||||
|
stale_processes = identify_stale_processes(python_processes)
|
||||||
|
|
||||||
|
if stale_processes:
|
||||||
|
print(f"Found {len(stale_processes)} potentially stale processes:")
|
||||||
|
for proc in stale_processes:
|
||||||
|
print(f" PID {proc['pid']}: {proc['name']} - {proc['reason']}")
|
||||||
|
else:
|
||||||
|
print("No stale processes identified")
|
||||||
|
|
||||||
|
# Step 5: Ask user what to do
|
||||||
|
if dashboard_processes or stale_processes or port_pid:
|
||||||
|
print("\n🤔 What would you like to do?")
|
||||||
|
print("1. Kill all dashboard processes")
|
||||||
|
print("2. Kill only stale processes")
|
||||||
|
print("3. Kill process using port 8050")
|
||||||
|
print("4. Kill all identified processes")
|
||||||
|
print("5. Show process details and exit")
|
||||||
|
print("6. Exit without killing anything")
|
||||||
|
|
||||||
|
try:
|
||||||
|
choice = input("\nEnter your choice (1-6): ").strip()
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
# Kill dashboard processes
|
||||||
|
print("\n🔫 Killing dashboard processes...")
|
||||||
|
for proc in dashboard_processes:
|
||||||
|
if not kill_process_safely(proc):
|
||||||
|
kill_process_safely(proc, force=True)
|
||||||
|
|
||||||
|
elif choice == '2':
|
||||||
|
# Kill stale processes
|
||||||
|
print("\n🔫 Killing stale processes...")
|
||||||
|
for proc in stale_processes:
|
||||||
|
if not kill_process_safely(proc):
|
||||||
|
kill_process_safely(proc, force=True)
|
||||||
|
|
||||||
|
elif choice == '3':
|
||||||
|
# Kill process using port 8050
|
||||||
|
if port_pid:
|
||||||
|
print(f"\n🔫 Killing process using port 8050 (PID {port_pid})...")
|
||||||
|
try:
|
||||||
|
proc = psutil.Process(port_pid)
|
||||||
|
proc_info = {
|
||||||
|
'pid': port_pid,
|
||||||
|
'name': proc.name(),
|
||||||
|
'process': proc
|
||||||
|
}
|
||||||
|
if not kill_process_safely(proc_info):
|
||||||
|
kill_process_safely(proc_info, force=True)
|
||||||
|
except:
|
||||||
|
print(f"❌ Could not kill process {port_pid}")
|
||||||
|
else:
|
||||||
|
print("No process found using port 8050")
|
||||||
|
|
||||||
|
elif choice == '4':
|
||||||
|
# Kill all identified processes
|
||||||
|
print("\n🔫 Killing all identified processes...")
|
||||||
|
all_processes = dashboard_processes + stale_processes
|
||||||
|
if port_pid:
|
||||||
|
try:
|
||||||
|
proc = psutil.Process(port_pid)
|
||||||
|
all_processes.append({
|
||||||
|
'pid': port_pid,
|
||||||
|
'name': proc.name(),
|
||||||
|
'process': proc
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for proc in all_processes:
|
||||||
|
if not kill_process_safely(proc):
|
||||||
|
kill_process_safely(proc, force=True)
|
||||||
|
|
||||||
|
elif choice == '5':
|
||||||
|
# Show details
|
||||||
|
print("\n📋 Process Details:")
|
||||||
|
all_processes = dashboard_processes + stale_processes
|
||||||
|
for proc in all_processes:
|
||||||
|
print(f"\nPID {proc['pid']}: {proc['name']}")
|
||||||
|
print(f" Status: {proc['status']}")
|
||||||
|
print(f" Command: {proc['cmdline']}")
|
||||||
|
print(f" Created: {datetime.fromtimestamp(proc['create_time'])}")
|
||||||
|
|
||||||
|
elif choice == '6':
|
||||||
|
print("👋 Exiting without killing processes")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("❌ Invalid choice")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n👋 Cancelled by user")
|
||||||
|
else:
|
||||||
|
print("\n✅ No problematic processes found")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("💡 After killing processes, you can try:")
|
||||||
|
print(" python run_lightweight_dashboard.py")
|
||||||
|
print(" or")
|
||||||
|
print(" python fix_startup_freeze.py")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error in main function: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = main()
|
||||||
|
if not success:
|
||||||
|
sys.exit(1)
|
@ -9,6 +9,10 @@ import os
|
|||||||
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
|
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
|
||||||
os.environ['OMP_NUM_THREADS'] = '4'
|
os.environ['OMP_NUM_THREADS'] = '4'
|
||||||
|
|
||||||
|
# Fix matplotlib backend issue - set non-interactive backend before any imports
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg') # Use non-interactive Agg backend
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
@ -18,6 +18,15 @@ This ensures consistent data across all models and components.
|
|||||||
Uses layout and component managers to reduce file size and improve maintainability
|
Uses layout and component managers to reduce file size and improve maintainability
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Force matplotlib to use non-interactive backend before any imports
|
||||||
|
import os
|
||||||
|
os.environ['MPLBACKEND'] = 'Agg'
|
||||||
|
|
||||||
|
# Set matplotlib configuration
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg') # Use non-interactive Agg backend
|
||||||
|
matplotlib.interactive(False) # Disable interactive mode
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
from dash import Dash, dcc, html, Input, Output, State
|
from dash import Dash, dcc, html, Input, Output, State
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
@ -33,6 +42,7 @@ import threading
|
|||||||
from typing import Dict, List, Optional, Any, Union
|
from typing import Dict, List, Optional, Any, Union
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import sys # Import sys for global exception handler
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash.exceptions import PreventUpdate
|
from dash.exceptions import PreventUpdate
|
||||||
from collections import deque
|
from collections import deque
|
||||||
@ -236,7 +246,7 @@ class CleanTradingDashboard:
|
|||||||
|
|
||||||
logger.debug("Clean Trading Dashboard initialized with HIGH-FREQUENCY COB integration and signal generation")
|
logger.debug("Clean Trading Dashboard initialized with HIGH-FREQUENCY COB integration and signal generation")
|
||||||
logger.info("🌙 Overnight Training Coordinator ready - call start_overnight_training() to begin")
|
logger.info("🌙 Overnight Training Coordinator ready - call start_overnight_training() to begin")
|
||||||
|
|
||||||
def start_overnight_training(self):
|
def start_overnight_training(self):
|
||||||
"""Start the overnight training session"""
|
"""Start the overnight training session"""
|
||||||
try:
|
try:
|
||||||
@ -411,6 +421,19 @@ class CleanTradingDashboard:
|
|||||||
logger.error(f"Error getting model status: {e}")
|
logger.error(f"Error getting model status: {e}")
|
||||||
return {'loaded_models': {}, 'total_models': 0, 'system_status': 'ERROR'}
|
return {'loaded_models': {}, 'total_models': 0, 'system_status': 'ERROR'}
|
||||||
|
|
||||||
|
def _safe_strftime(self, timestamp_val, format_str='%H:%M:%S'):
|
||||||
|
"""Safely format timestamp, handling both string and datetime objects"""
|
||||||
|
try:
|
||||||
|
if isinstance(timestamp_val, str):
|
||||||
|
return timestamp_val
|
||||||
|
elif hasattr(timestamp_val, 'strftime'):
|
||||||
|
return timestamp_val.strftime(format_str)
|
||||||
|
else:
|
||||||
|
return datetime.now().strftime(format_str)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error formatting timestamp {timestamp_val}: {e}")
|
||||||
|
return datetime.now().strftime(format_str)
|
||||||
|
|
||||||
def _get_initial_balance(self) -> float:
|
def _get_initial_balance(self) -> float:
|
||||||
"""Get initial balance from trading executor or default"""
|
"""Get initial balance from trading executor or default"""
|
||||||
try:
|
try:
|
||||||
@ -616,7 +639,18 @@ class CleanTradingDashboard:
|
|||||||
|
|
||||||
# Only show signals that are significantly different or from different time periods
|
# Only show signals that are significantly different or from different time periods
|
||||||
signal_key = f"{action}_{int(price)}_{int(confidence*100)}"
|
signal_key = f"{action}_{int(price)}_{int(confidence*100)}"
|
||||||
time_key = int(timestamp.timestamp() // 30) # Group by 30-second intervals
|
# Handle timestamp safely - could be string or datetime
|
||||||
|
if isinstance(timestamp, str):
|
||||||
|
try:
|
||||||
|
# Try to parse string timestamp
|
||||||
|
timestamp_dt = datetime.strptime(timestamp, '%H:%M:%S')
|
||||||
|
time_key = int(timestamp_dt.timestamp() // 30)
|
||||||
|
except:
|
||||||
|
time_key = int(datetime.now().timestamp() // 30)
|
||||||
|
elif hasattr(timestamp, 'timestamp'):
|
||||||
|
time_key = int(timestamp.timestamp() // 30)
|
||||||
|
else:
|
||||||
|
time_key = int(datetime.now().timestamp() // 30)
|
||||||
full_key = f"{signal_key}_{time_key}"
|
full_key = f"{signal_key}_{time_key}"
|
||||||
|
|
||||||
if full_key not in seen_signals:
|
if full_key not in seen_signals:
|
||||||
@ -709,6 +743,9 @@ class CleanTradingDashboard:
|
|||||||
btc_data_time = self.cob_last_update.get('BTC/USDT', 0) if hasattr(self, 'cob_last_update') else 0
|
btc_data_time = self.cob_last_update.get('BTC/USDT', 0) if hasattr(self, 'cob_last_update') else 0
|
||||||
import time
|
import time
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
# Ensure data times are not None
|
||||||
|
eth_data_time = eth_data_time or 0
|
||||||
|
btc_data_time = btc_data_time or 0
|
||||||
logger.info(f"COB Data Age: ETH: {current_time - eth_data_time:.1f}s, BTC: {current_time - btc_data_time:.1f}s")
|
logger.info(f"COB Data Age: ETH: {current_time - eth_data_time:.1f}s, BTC: {current_time - btc_data_time:.1f}s")
|
||||||
|
|
||||||
eth_imbalance_stats = self._calculate_cumulative_imbalance('ETH/USDT')
|
eth_imbalance_stats = self._calculate_cumulative_imbalance('ETH/USDT')
|
||||||
@ -717,6 +754,20 @@ class CleanTradingDashboard:
|
|||||||
# Determine COB data source mode
|
# Determine COB data source mode
|
||||||
cob_mode = self._get_cob_mode()
|
cob_mode = self._get_cob_mode()
|
||||||
|
|
||||||
|
# Debug: Log snapshot types only when needed (every 1000 intervals)
|
||||||
|
if n % 1000 == 0:
|
||||||
|
logger.debug(f"DEBUG: ETH snapshot type: {type(eth_snapshot)}, BTC snapshot type: {type(btc_snapshot)}")
|
||||||
|
if isinstance(eth_snapshot, list):
|
||||||
|
logger.debug(f"ETH snapshot is a list with {len(eth_snapshot)} items: {eth_snapshot[:2] if eth_snapshot else 'empty'}")
|
||||||
|
if isinstance(btc_snapshot, list):
|
||||||
|
logger.error(f"BTC snapshot is a list with {len(btc_snapshot)} items: {btc_snapshot[:2] if btc_snapshot else 'empty'}")
|
||||||
|
|
||||||
|
# If we get a list, don't pass it to the formatter - create a proper object or return None
|
||||||
|
if isinstance(eth_snapshot, list):
|
||||||
|
eth_snapshot = None
|
||||||
|
if isinstance(btc_snapshot, list):
|
||||||
|
btc_snapshot = None
|
||||||
|
|
||||||
eth_components = self.component_manager.format_cob_data(eth_snapshot, 'ETH/USDT', eth_imbalance_stats, cob_mode)
|
eth_components = self.component_manager.format_cob_data(eth_snapshot, 'ETH/USDT', eth_imbalance_stats, cob_mode)
|
||||||
btc_components = self.component_manager.format_cob_data(btc_snapshot, 'BTC/USDT', btc_imbalance_stats, cob_mode)
|
btc_components = self.component_manager.format_cob_data(btc_snapshot, 'BTC/USDT', btc_imbalance_stats, cob_mode)
|
||||||
|
|
||||||
@ -2252,16 +2303,73 @@ class CleanTradingDashboard:
|
|||||||
return {'error': str(e), 'cob_status': 'Error Getting Status', 'orchestrator_type': 'Unknown'}
|
return {'error': str(e), 'cob_status': 'Error Getting Status', 'orchestrator_type': 'Unknown'}
|
||||||
|
|
||||||
def _get_cob_snapshot(self, symbol: str) -> Optional[Any]:
|
def _get_cob_snapshot(self, symbol: str) -> Optional[Any]:
|
||||||
"""Get COB snapshot for symbol - PERFORMANCE OPTIMIZED: Use orchestrator's COB integration"""
|
"""Get COB snapshot for symbol - CENTRALIZED: Use data provider's COB data"""
|
||||||
try:
|
try:
|
||||||
# PERFORMANCE FIX: Use orchestrator's COB integration instead of separate dashboard integration
|
# Priority 1: Use data provider's centralized COB data (primary source)
|
||||||
# This eliminates redundant COB providers and improves performance
|
if self.data_provider:
|
||||||
|
try:
|
||||||
|
cob_data = self.data_provider.get_latest_cob_data(symbol)
|
||||||
|
logger.debug(f"COB data type for {symbol}: {type(cob_data)}, data: {cob_data}")
|
||||||
|
|
||||||
|
if cob_data and isinstance(cob_data, dict) and 'stats' in cob_data:
|
||||||
|
logger.debug(f"COB snapshot available for {symbol} from centralized data provider")
|
||||||
|
|
||||||
|
# Create a snapshot object from the data provider's data
|
||||||
|
class COBSnapshot:
|
||||||
|
def __init__(self, data):
|
||||||
|
# Convert list format [[price, qty], ...] to dictionary format
|
||||||
|
raw_bids = data.get('bids', [])
|
||||||
|
raw_asks = data.get('asks', [])
|
||||||
|
|
||||||
|
# Convert to dictionary format expected by component manager
|
||||||
|
self.consolidated_bids = []
|
||||||
|
for bid in raw_bids:
|
||||||
|
if isinstance(bid, list) and len(bid) >= 2:
|
||||||
|
self.consolidated_bids.append({
|
||||||
|
'price': bid[0],
|
||||||
|
'size': bid[1],
|
||||||
|
'total_size': bid[1],
|
||||||
|
'total_volume_usd': bid[0] * bid[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
self.consolidated_asks = []
|
||||||
|
for ask in raw_asks:
|
||||||
|
if isinstance(ask, list) and len(ask) >= 2:
|
||||||
|
self.consolidated_asks.append({
|
||||||
|
'price': ask[0],
|
||||||
|
'size': ask[1],
|
||||||
|
'total_size': ask[1],
|
||||||
|
'total_volume_usd': ask[0] * ask[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
self.stats = data.get('stats', {})
|
||||||
|
# Add direct attributes for new format compatibility
|
||||||
|
self.volume_weighted_mid = self.stats.get('mid_price', 0)
|
||||||
|
self.spread_bps = self.stats.get('spread_bps', 0)
|
||||||
|
self.liquidity_imbalance = self.stats.get('imbalance', 0)
|
||||||
|
self.total_bid_liquidity = self.stats.get('bid_liquidity', 0)
|
||||||
|
self.total_ask_liquidity = self.stats.get('ask_liquidity', 0)
|
||||||
|
self.exchanges_active = ['Binance'] # Default for now
|
||||||
|
|
||||||
|
return COBSnapshot(cob_data)
|
||||||
|
else:
|
||||||
|
logger.warning(f"Invalid COB data for {symbol}: type={type(cob_data)}, has_stats={'stats' in cob_data if isinstance(cob_data, dict) else False}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting COB data from data provider: {e}")
|
||||||
|
|
||||||
|
# Priority 2: Use orchestrator's COB integration (secondary source)
|
||||||
if hasattr(self.orchestrator, 'cob_integration') and self.orchestrator.cob_integration:
|
if hasattr(self.orchestrator, 'cob_integration') and self.orchestrator.cob_integration:
|
||||||
# First try to get snapshot from orchestrator's COB integration
|
# Try to get snapshot from orchestrator's COB integration
|
||||||
snapshot = self.orchestrator.cob_integration.get_cob_snapshot(symbol)
|
snapshot = self.orchestrator.cob_integration.get_cob_snapshot(symbol)
|
||||||
if snapshot:
|
if snapshot:
|
||||||
logger.debug(f"COB snapshot available for {symbol} from orchestrator COB integration")
|
logger.debug(f"COB snapshot available for {symbol} from orchestrator COB integration, type: {type(snapshot)}")
|
||||||
return snapshot
|
|
||||||
|
# Check if it's a list (which would cause the error)
|
||||||
|
if isinstance(snapshot, list):
|
||||||
|
logger.warning(f"Orchestrator returned list instead of COB snapshot for {symbol}")
|
||||||
|
# Don't return the list, continue to other sources
|
||||||
|
else:
|
||||||
|
return snapshot
|
||||||
|
|
||||||
# If no snapshot, try to get from orchestrator's cached data
|
# If no snapshot, try to get from orchestrator's cached data
|
||||||
if hasattr(self.orchestrator, 'latest_cob_data') and symbol in self.orchestrator.latest_cob_data:
|
if hasattr(self.orchestrator, 'latest_cob_data') and symbol in self.orchestrator.latest_cob_data:
|
||||||
@ -2277,7 +2385,7 @@ class CleanTradingDashboard:
|
|||||||
|
|
||||||
return COBSnapshot(cob_data)
|
return COBSnapshot(cob_data)
|
||||||
|
|
||||||
# Fallback: Use cached COB data if orchestrator integration not available
|
# Priority 3: Use dashboard's cached COB data (last resort fallback)
|
||||||
if symbol in self.latest_cob_data and self.latest_cob_data[symbol]:
|
if symbol in self.latest_cob_data and self.latest_cob_data[symbol]:
|
||||||
cob_data = self.latest_cob_data[symbol]
|
cob_data = self.latest_cob_data[symbol]
|
||||||
logger.debug(f"COB snapshot available for {symbol} from dashboard cached data (fallback)")
|
logger.debug(f"COB snapshot available for {symbol} from dashboard cached data (fallback)")
|
||||||
@ -2298,7 +2406,7 @@ class CleanTradingDashboard:
|
|||||||
|
|
||||||
return COBSnapshot(cob_data)
|
return COBSnapshot(cob_data)
|
||||||
|
|
||||||
logger.debug(f"No COB snapshot available for {symbol} - no orchestrator integration or cached data")
|
logger.debug(f"No COB snapshot available for {symbol} - no data provider, orchestrator integration, or cached data")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -2458,7 +2566,13 @@ class CleanTradingDashboard:
|
|||||||
if dqn_latest:
|
if dqn_latest:
|
||||||
last_action = dqn_latest.get('action', 'NONE')
|
last_action = dqn_latest.get('action', 'NONE')
|
||||||
last_confidence = dqn_latest.get('confidence', 0.72)
|
last_confidence = dqn_latest.get('confidence', 0.72)
|
||||||
last_timestamp = dqn_latest.get('timestamp', datetime.now()).strftime('%H:%M:%S')
|
timestamp_val = dqn_latest.get('timestamp', datetime.now())
|
||||||
|
if isinstance(timestamp_val, str):
|
||||||
|
last_timestamp = timestamp_val
|
||||||
|
elif hasattr(timestamp_val, 'strftime'):
|
||||||
|
last_timestamp = timestamp_val.strftime('%H:%M:%S')
|
||||||
|
else:
|
||||||
|
last_timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
else:
|
else:
|
||||||
if signal_generation_active and len(self.recent_decisions) > 0:
|
if signal_generation_active and len(self.recent_decisions) > 0:
|
||||||
recent_signal = self.recent_decisions[-1]
|
recent_signal = self.recent_decisions[-1]
|
||||||
@ -2531,7 +2645,13 @@ class CleanTradingDashboard:
|
|||||||
if cnn_latest:
|
if cnn_latest:
|
||||||
cnn_action = cnn_latest.get('action', 'PATTERN_ANALYSIS')
|
cnn_action = cnn_latest.get('action', 'PATTERN_ANALYSIS')
|
||||||
cnn_confidence = cnn_latest.get('confidence', 0.68)
|
cnn_confidence = cnn_latest.get('confidence', 0.68)
|
||||||
cnn_timestamp = cnn_latest.get('timestamp', datetime.now()).strftime('%H:%M:%S')
|
timestamp_val = cnn_latest.get('timestamp', datetime.now())
|
||||||
|
if isinstance(timestamp_val, str):
|
||||||
|
cnn_timestamp = timestamp_val
|
||||||
|
elif hasattr(timestamp_val, 'strftime'):
|
||||||
|
cnn_timestamp = timestamp_val.strftime('%H:%M:%S')
|
||||||
|
else:
|
||||||
|
cnn_timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
cnn_predicted_price = cnn_latest.get('predicted_price', 0)
|
cnn_predicted_price = cnn_latest.get('predicted_price', 0)
|
||||||
else:
|
else:
|
||||||
cnn_action = 'PATTERN_ANALYSIS'
|
cnn_action = 'PATTERN_ANALYSIS'
|
||||||
@ -2594,7 +2714,13 @@ class CleanTradingDashboard:
|
|||||||
if transformer_latest:
|
if transformer_latest:
|
||||||
transformer_action = transformer_latest.get('action', 'PRICE_PREDICTION')
|
transformer_action = transformer_latest.get('action', 'PRICE_PREDICTION')
|
||||||
transformer_confidence = transformer_latest.get('confidence', 0.75)
|
transformer_confidence = transformer_latest.get('confidence', 0.75)
|
||||||
transformer_timestamp = transformer_latest.get('timestamp', datetime.now()).strftime('%H:%M:%S')
|
timestamp_val = transformer_latest.get('timestamp', datetime.now())
|
||||||
|
if isinstance(timestamp_val, str):
|
||||||
|
transformer_timestamp = timestamp_val
|
||||||
|
elif hasattr(timestamp_val, 'strftime'):
|
||||||
|
transformer_timestamp = timestamp_val.strftime('%H:%M:%S')
|
||||||
|
else:
|
||||||
|
transformer_timestamp = datetime.now().strftime('%H:%M:%S')
|
||||||
transformer_predicted_price = transformer_latest.get('predicted_price', 0)
|
transformer_predicted_price = transformer_latest.get('predicted_price', 0)
|
||||||
transformer_price_change = transformer_latest.get('price_change', 0)
|
transformer_price_change = transformer_latest.get('price_change', 0)
|
||||||
else:
|
else:
|
||||||
@ -5159,11 +5285,11 @@ class CleanTradingDashboard:
|
|||||||
self.position_sync_enabled = False
|
self.position_sync_enabled = False
|
||||||
|
|
||||||
def _initialize_cob_integration(self):
|
def _initialize_cob_integration(self):
|
||||||
"""Initialize COB integration using orchestrator's COB system"""
|
"""Initialize COB integration using centralized data provider"""
|
||||||
try:
|
try:
|
||||||
logger.info("Initializing COB integration via orchestrator")
|
logger.info("Initializing COB integration via centralized data provider")
|
||||||
|
|
||||||
# Initialize COB data storage (for fallback)
|
# Initialize COB data storage (for dashboard display)
|
||||||
self.cob_data_history = {
|
self.cob_data_history = {
|
||||||
'ETH/USDT': [],
|
'ETH/USDT': [],
|
||||||
'BTC/USDT': []
|
'BTC/USDT': []
|
||||||
@ -5181,9 +5307,15 @@ class CleanTradingDashboard:
|
|||||||
'BTC/USDT': None
|
'BTC/USDT': None
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if orchestrator has COB integration
|
# Primary approach: Use the data provider's centralized COB collection
|
||||||
|
if self.data_provider:
|
||||||
|
logger.info("Using centralized data provider for COB data collection")
|
||||||
|
self._start_simple_cob_collection() # This now uses the data provider
|
||||||
|
|
||||||
|
# Secondary approach: If orchestrator has COB integration, use that as well
|
||||||
|
# This ensures we have multiple data sources for redundancy
|
||||||
if hasattr(self.orchestrator, 'cob_integration') and self.orchestrator.cob_integration:
|
if hasattr(self.orchestrator, 'cob_integration') and self.orchestrator.cob_integration:
|
||||||
logger.info("Using orchestrator's COB integration")
|
logger.info("Also using orchestrator's COB integration as secondary source")
|
||||||
|
|
||||||
# Start orchestrator's COB integration in background
|
# Start orchestrator's COB integration in background
|
||||||
def start_orchestrator_cob():
|
def start_orchestrator_cob():
|
||||||
@ -5199,137 +5331,129 @@ class CleanTradingDashboard:
|
|||||||
cob_thread = threading.Thread(target=start_orchestrator_cob, daemon=True)
|
cob_thread = threading.Thread(target=start_orchestrator_cob, daemon=True)
|
||||||
cob_thread.start()
|
cob_thread.start()
|
||||||
|
|
||||||
logger.info("Orchestrator COB integration started successfully")
|
logger.info("Orchestrator COB integration started as secondary source")
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning("Orchestrator COB integration not available, using fallback simple collection")
|
|
||||||
# Fallback to simple collection
|
|
||||||
self._start_simple_cob_collection()
|
|
||||||
|
|
||||||
# ALWAYS start simple collection as backup even if orchestrator COB exists
|
|
||||||
# This ensures we have data flowing while orchestrator COB integration starts up
|
|
||||||
logger.info("Starting simple COB collection as backup/fallback")
|
|
||||||
self._start_simple_cob_collection()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error initializing COB integration: {e}")
|
logger.error(f"Error initializing COB integration: {e}")
|
||||||
# Fallback to simple collection
|
# Last resort fallback
|
||||||
self._start_simple_cob_collection()
|
if self.data_provider:
|
||||||
|
logger.warning("Falling back to direct data provider COB collection")
|
||||||
|
self._start_simple_cob_collection()
|
||||||
|
|
||||||
def _start_simple_cob_collection(self):
|
def _start_simple_cob_collection(self):
|
||||||
"""Start simple COB data collection using REST APIs (no async required)"""
|
"""Start COB data collection using the centralized data provider"""
|
||||||
try:
|
try:
|
||||||
import threading
|
# Use the data provider's COB collection instead of implementing our own
|
||||||
import time
|
if self.data_provider:
|
||||||
|
# Start the centralized COB data collection in the data provider
|
||||||
def cob_collector():
|
self.data_provider.start_cob_collection()
|
||||||
"""Collect COB data using simple REST API calls"""
|
|
||||||
while True:
|
# Subscribe to COB updates from the data provider
|
||||||
|
def cob_update_callback(symbol, cob_snapshot):
|
||||||
|
"""Callback for COB data updates from data provider"""
|
||||||
try:
|
try:
|
||||||
# Collect data for both symbols
|
# Store the latest COB data
|
||||||
for symbol in ['ETH/USDT', 'BTC/USDT']:
|
if not hasattr(self, 'latest_cob_data'):
|
||||||
self._collect_simple_cob_data(symbol)
|
self.latest_cob_data = {}
|
||||||
|
|
||||||
# Sleep for 1 second between collections
|
self.latest_cob_data[symbol] = cob_snapshot
|
||||||
time.sleep(1)
|
|
||||||
|
# Update current price from COB data
|
||||||
|
if 'stats' in cob_snapshot and 'mid_price' in cob_snapshot['stats']:
|
||||||
|
self.current_prices[symbol] = cob_snapshot['stats']['mid_price']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error in COB collection: {e}")
|
logger.debug(f"Error in COB update callback: {e}")
|
||||||
time.sleep(5) # Wait longer on error
|
|
||||||
|
# Register for COB updates
|
||||||
# Start collector in background thread
|
self.data_provider.subscribe_to_cob(cob_update_callback)
|
||||||
cob_thread = threading.Thread(target=cob_collector, daemon=True)
|
|
||||||
cob_thread.start()
|
logger.info("Centralized COB data collection started via data provider")
|
||||||
|
else:
|
||||||
logger.info("Simple COB data collection started")
|
logger.error("Cannot start COB collection - data provider not available")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error starting COB collection: {e}")
|
logger.error(f"Error starting COB collection: {e}")
|
||||||
|
|
||||||
def _collect_simple_cob_data(self, symbol: str):
|
def _collect_simple_cob_data(self, symbol: str):
|
||||||
"""Collect simple COB data using Binance REST API"""
|
"""Get COB data from the centralized data provider"""
|
||||||
try:
|
try:
|
||||||
import requests
|
# Use the data provider to get COB data
|
||||||
import time
|
if self.data_provider:
|
||||||
|
# Get the COB data from the data provider
|
||||||
# Use Binance REST API for order book data
|
cob_snapshot = self.data_provider.collect_cob_data(symbol)
|
||||||
binance_symbol = symbol.replace('/', '')
|
|
||||||
url = f"https://api.binance.com/api/v3/depth?symbol={binance_symbol}&limit=500"
|
|
||||||
|
|
||||||
response = requests.get(url, timeout=5)
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
# Process order book data
|
if cob_snapshot and 'stats' in cob_snapshot:
|
||||||
bids = []
|
# Process the COB data for dashboard display
|
||||||
asks = []
|
|
||||||
|
|
||||||
# Process bids (buy orders)
|
|
||||||
for bid in data['bids'][:100]: # Top 100 levels
|
|
||||||
price = float(bid[0])
|
|
||||||
size = float(bid[1])
|
|
||||||
bids.append({
|
|
||||||
'price': price,
|
|
||||||
'size': size,
|
|
||||||
'total': price * size
|
|
||||||
})
|
|
||||||
|
|
||||||
# Process asks (sell orders)
|
|
||||||
for ask in data['asks'][:100]: # Top 100 levels
|
|
||||||
price = float(ask[0])
|
|
||||||
size = float(ask[1])
|
|
||||||
asks.append({
|
|
||||||
'price': price,
|
|
||||||
'size': size,
|
|
||||||
'total': price * size
|
|
||||||
})
|
|
||||||
|
|
||||||
# Calculate statistics
|
|
||||||
if bids and asks:
|
|
||||||
best_bid = max(bids, key=lambda x: x['price'])
|
|
||||||
best_ask = min(asks, key=lambda x: x['price'])
|
|
||||||
mid_price = (best_bid['price'] + best_ask['price']) / 2
|
|
||||||
spread_bps = ((best_ask['price'] - best_bid['price']) / mid_price) * 10000 if mid_price > 0 else 0
|
|
||||||
|
|
||||||
total_bid_liquidity = sum(bid['total'] for bid in bids[:20])
|
# Format the data for our dashboard
|
||||||
total_ask_liquidity = sum(ask['total'] for ask in asks[:20])
|
bids = []
|
||||||
total_liquidity = total_bid_liquidity + total_ask_liquidity
|
asks = []
|
||||||
imbalance = (total_bid_liquidity - total_ask_liquidity) / total_liquidity if total_liquidity > 0 else 0
|
|
||||||
|
|
||||||
# Create COB snapshot
|
# Process bids
|
||||||
cob_snapshot = {
|
for bid_price, bid_size in cob_snapshot.get('bids', [])[:100]:
|
||||||
|
bids.append({
|
||||||
|
'price': bid_price,
|
||||||
|
'size': bid_size,
|
||||||
|
'total': bid_price * bid_size
|
||||||
|
})
|
||||||
|
|
||||||
|
# Process asks
|
||||||
|
for ask_price, ask_size in cob_snapshot.get('asks', [])[:100]:
|
||||||
|
asks.append({
|
||||||
|
'price': ask_price,
|
||||||
|
'size': ask_size,
|
||||||
|
'total': ask_price * ask_size
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create dashboard-friendly COB snapshot
|
||||||
|
dashboard_cob_snapshot = {
|
||||||
'symbol': symbol,
|
'symbol': symbol,
|
||||||
'timestamp': time.time(),
|
'timestamp': cob_snapshot.get('timestamp', time.time()),
|
||||||
'bids': bids,
|
'bids': bids,
|
||||||
'asks': asks,
|
'asks': asks,
|
||||||
'stats': {
|
'stats': {
|
||||||
'mid_price': mid_price,
|
'mid_price': cob_snapshot['stats'].get('mid_price', 0),
|
||||||
'spread_bps': spread_bps,
|
'spread_bps': cob_snapshot['stats'].get('spread_bps', 0),
|
||||||
'total_bid_liquidity': total_bid_liquidity,
|
'total_bid_liquidity': cob_snapshot['stats'].get('bid_liquidity', 0),
|
||||||
'total_ask_liquidity': total_ask_liquidity,
|
'total_ask_liquidity': cob_snapshot['stats'].get('ask_liquidity', 0),
|
||||||
'imbalance': imbalance,
|
'imbalance': cob_snapshot['stats'].get('imbalance', 0),
|
||||||
'exchanges_active': ['Binance']
|
'exchanges_active': ['Binance']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Initialize history if needed
|
||||||
|
if not hasattr(self, 'cob_data_history'):
|
||||||
|
self.cob_data_history = {}
|
||||||
|
|
||||||
|
if symbol not in self.cob_data_history:
|
||||||
|
self.cob_data_history[symbol] = []
|
||||||
|
|
||||||
# Store in history (keep last 15 seconds)
|
# Store in history (keep last 15 seconds)
|
||||||
self.cob_data_history[symbol].append(cob_snapshot)
|
self.cob_data_history[symbol].append(dashboard_cob_snapshot)
|
||||||
if len(self.cob_data_history[symbol]) > 15: # Keep 15 seconds
|
if len(self.cob_data_history[symbol]) > 15: # Keep 15 seconds
|
||||||
self.cob_data_history[symbol] = self.cob_data_history[symbol][-15:]
|
self.cob_data_history[symbol] = self.cob_data_history[symbol][-15:]
|
||||||
|
|
||||||
|
# Initialize latest data if needed
|
||||||
|
if not hasattr(self, 'latest_cob_data'):
|
||||||
|
self.latest_cob_data = {}
|
||||||
|
|
||||||
|
if not hasattr(self, 'cob_last_update'):
|
||||||
|
self.cob_last_update = {}
|
||||||
|
|
||||||
# Update latest data
|
# Update latest data
|
||||||
self.latest_cob_data[symbol] = cob_snapshot
|
self.latest_cob_data[symbol] = dashboard_cob_snapshot
|
||||||
self.cob_last_update[symbol] = time.time()
|
self.cob_last_update[symbol] = time.time()
|
||||||
|
|
||||||
# Generate bucketed data for models
|
# Generate bucketed data for models
|
||||||
self._generate_bucketed_cob_data(symbol, cob_snapshot)
|
self._generate_bucketed_cob_data(symbol, dashboard_cob_snapshot)
|
||||||
|
|
||||||
# Generate COB signals based on imbalance
|
# Generate COB signals based on imbalance
|
||||||
self._generate_cob_signal(symbol, cob_snapshot)
|
self._generate_cob_signal(symbol, dashboard_cob_snapshot)
|
||||||
|
|
||||||
logger.debug(f"COB data collected for {symbol}: {len(bids)} bids, {len(asks)} asks")
|
logger.debug(f"COB data retrieved from data provider for {symbol}: {len(bids)} bids, {len(asks)} asks")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error collecting COB data for {symbol}: {e}")
|
logger.debug(f"Error getting COB data for {symbol}: {e}")
|
||||||
|
|
||||||
def _generate_bucketed_cob_data(self, symbol: str, cob_snapshot: dict):
|
def _generate_bucketed_cob_data(self, symbol: str, cob_snapshot: dict):
|
||||||
"""Generate bucketed COB data for model feeding"""
|
"""Generate bucketed COB data for model feeding"""
|
||||||
|
@ -296,6 +296,27 @@ class DashboardComponentManager:
|
|||||||
html.P(f"Mode: {cob_mode}", className="text-muted small")
|
html.P(f"Mode: {cob_mode}", className="text-muted small")
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# Defensive: If cob_snapshot is a list, log and return error
|
||||||
|
if isinstance(cob_snapshot, list):
|
||||||
|
logger.error(f"COB snapshot for {symbol} is a list, expected object. Data: {cob_snapshot}")
|
||||||
|
return html.Div([
|
||||||
|
html.H6(f"{symbol} COB", className="mb-2"),
|
||||||
|
html.P("Invalid COB data format (list)", className="text-danger small"),
|
||||||
|
html.P(f"Mode: {cob_mode}", className="text-muted small")
|
||||||
|
])
|
||||||
|
|
||||||
|
# Debug: Log the type and structure of cob_snapshot
|
||||||
|
logger.debug(f"COB snapshot type for {symbol}: {type(cob_snapshot)}")
|
||||||
|
|
||||||
|
# Handle case where cob_snapshot is a list (error case)
|
||||||
|
if isinstance(cob_snapshot, list):
|
||||||
|
logger.error(f"COB snapshot is a list for {symbol}, expected object or dict")
|
||||||
|
return html.Div([
|
||||||
|
html.H6(f"{symbol} COB", className="mb-2"),
|
||||||
|
html.P("Invalid COB data format (list)", className="text-danger small"),
|
||||||
|
html.P(f"Mode: {cob_mode}", className="text-muted small")
|
||||||
|
])
|
||||||
|
|
||||||
# Handle both old format (with stats attribute) and new format (direct attributes)
|
# Handle both old format (with stats attribute) and new format (direct attributes)
|
||||||
if hasattr(cob_snapshot, 'stats'):
|
if hasattr(cob_snapshot, 'stats'):
|
||||||
# Old format with stats attribute
|
# Old format with stats attribute
|
||||||
@ -385,6 +406,18 @@ class DashboardComponentManager:
|
|||||||
html.Span(imbalance_text, className=f"fw-bold small {imbalance_color}")
|
html.Span(imbalance_text, className=f"fw-bold small {imbalance_color}")
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
# Multi-timeframe imbalance metrics
|
||||||
|
html.Div([
|
||||||
|
html.Strong("Timeframe Imbalances:", className="small d-block mt-2 mb-1")
|
||||||
|
]),
|
||||||
|
|
||||||
|
html.Div([
|
||||||
|
self._create_timeframe_imbalance("1s", stats.get('imbalance_1s', imbalance)),
|
||||||
|
self._create_timeframe_imbalance("5s", stats.get('imbalance_5s', imbalance)),
|
||||||
|
self._create_timeframe_imbalance("15s", stats.get('imbalance_15s', imbalance)),
|
||||||
|
self._create_timeframe_imbalance("60s", stats.get('imbalance_60s', imbalance)),
|
||||||
|
], className="d-flex justify-content-between mb-2"),
|
||||||
|
|
||||||
html.Div(imbalance_stats_display),
|
html.Div(imbalance_stats_display),
|
||||||
|
|
||||||
html.Hr(className="my-2"),
|
html.Hr(className="my-2"),
|
||||||
@ -417,6 +450,22 @@ class DashboardComponentManager:
|
|||||||
html.Div(title, className="small text-muted"),
|
html.Div(title, className="small text-muted"),
|
||||||
html.Div(value, className="fw-bold")
|
html.Div(value, className="fw-bold")
|
||||||
], className="text-center")
|
], className="text-center")
|
||||||
|
|
||||||
|
def _create_timeframe_imbalance(self, timeframe, value):
|
||||||
|
"""Helper for creating timeframe imbalance indicators."""
|
||||||
|
color = "text-success" if value > 0 else "text-danger" if value < 0 else "text-muted"
|
||||||
|
icon = "fas fa-chevron-up" if value > 0 else "fas fa-chevron-down" if value < 0 else "fas fa-minus"
|
||||||
|
|
||||||
|
# Format the value with sign and 2 decimal places
|
||||||
|
formatted_value = f"{value:+.2f}"
|
||||||
|
|
||||||
|
return html.Div([
|
||||||
|
html.Div(timeframe, className="small text-muted"),
|
||||||
|
html.Div([
|
||||||
|
html.I(className=f"{icon} me-1"),
|
||||||
|
html.Span(formatted_value, className="small")
|
||||||
|
], className=color)
|
||||||
|
], className="text-center")
|
||||||
|
|
||||||
def _create_cob_ladder_panel(self, bids, asks, mid_price, symbol=""):
|
def _create_cob_ladder_panel(self, bids, asks, mid_price, symbol=""):
|
||||||
"""Creates the right panel with the compact COB ladder."""
|
"""Creates the right panel with the compact COB ladder."""
|
||||||
|
@ -42,7 +42,7 @@ class DashboardLayoutManager:
|
|||||||
"""Create the auto-refresh interval component"""
|
"""Create the auto-refresh interval component"""
|
||||||
return dcc.Interval(
|
return dcc.Interval(
|
||||||
id='interval-component',
|
id='interval-component',
|
||||||
interval=1000, # Update every 1 second for maximum responsiveness
|
interval=250, # Update every 250 ms (4 Hz)
|
||||||
n_intervals=0
|
n_intervals=0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user