minor UI changes
This commit is contained in:
@ -397,17 +397,26 @@ class CleanTradingDashboard:
|
|||||||
|
|
||||||
@self.app.callback(
|
@self.app.callback(
|
||||||
Output('price-chart', 'figure'),
|
Output('price-chart', 'figure'),
|
||||||
[Input('interval-component', 'n_intervals')]
|
[Input('interval-component', 'n_intervals')],
|
||||||
|
[State('price-chart', 'relayoutData')]
|
||||||
)
|
)
|
||||||
def update_price_chart(n):
|
def update_price_chart(n, relayout_data):
|
||||||
"""Update price chart every second (1000ms interval)"""
|
"""Update price chart every second, persisting user zoom/pan"""
|
||||||
try:
|
try:
|
||||||
return self._create_price_chart('ETH/USDT')
|
fig = self._create_price_chart('ETH/USDT')
|
||||||
|
|
||||||
|
if relayout_data:
|
||||||
|
if 'xaxis.range[0]' in relayout_data and 'xaxis.range[1]' in relayout_data:
|
||||||
|
fig.update_xaxes(range=[relayout_data['xaxis.range[0]'], relayout_data['xaxis.range[1]']])
|
||||||
|
if 'yaxis.range[0]' in relayout_data and 'yaxis.range[1]' in relayout_data:
|
||||||
|
fig.update_yaxes(range=[relayout_data['yaxis.range[0]'], relayout_data['yaxis.range[1]']])
|
||||||
|
|
||||||
|
return fig
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating chart: {e}")
|
logger.error(f"Error updating chart: {e}")
|
||||||
return go.Figure().add_annotation(text=f"Chart Error: {str(e)}",
|
return go.Figure().add_annotation(text=f"Chart Error: {str(e)}",
|
||||||
xref="paper", yref="paper",
|
xref="paper", yref="paper",
|
||||||
x=0.5, y=0.5, showarrow=False)
|
x=0.5, y=0.5, showarrow=False)
|
||||||
|
|
||||||
@self.app.callback(
|
@self.app.callback(
|
||||||
Output('closed-trades-table', 'children'),
|
Output('closed-trades-table', 'children'),
|
||||||
@ -1059,7 +1068,7 @@ class CleanTradingDashboard:
|
|||||||
mode='markers',
|
mode='markers',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
symbol='diamond',
|
symbol='diamond',
|
||||||
size=[15 + p['confidence'] * 20 for p in up_predictions],
|
size=[2 + p['confidence'] * 12 for p in up_predictions],
|
||||||
color=[f'rgba(0, 150, 255, {0.4 + p["confidence"] * 0.6})' for p in up_predictions],
|
color=[f'rgba(0, 150, 255, {0.4 + p["confidence"] * 0.6})' for p in up_predictions],
|
||||||
line=dict(width=2, color='darkblue')
|
line=dict(width=2, color='darkblue')
|
||||||
),
|
),
|
||||||
@ -1084,7 +1093,7 @@ class CleanTradingDashboard:
|
|||||||
mode='markers',
|
mode='markers',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
symbol='diamond',
|
symbol='diamond',
|
||||||
size=[15 + p['confidence'] * 20 for p in down_predictions],
|
size=[2 + p['confidence'] * 12 for p in down_predictions],
|
||||||
color=[f'rgba(255, 140, 0, {0.4 + p["confidence"] * 0.6})' for p in down_predictions],
|
color=[f'rgba(255, 140, 0, {0.4 + p["confidence"] * 0.6})' for p in down_predictions],
|
||||||
line=dict(width=2, color='darkorange')
|
line=dict(width=2, color='darkorange')
|
||||||
),
|
),
|
||||||
@ -1109,7 +1118,7 @@ class CleanTradingDashboard:
|
|||||||
mode='markers',
|
mode='markers',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
symbol='diamond',
|
symbol='diamond',
|
||||||
size=[12 + p['confidence'] * 15 for p in sideways_predictions],
|
size=[6 + p['confidence'] * 10 for p in sideways_predictions],
|
||||||
color=[f'rgba(128, 128, 128, {0.3 + p["confidence"] * 0.5})' for p in sideways_predictions],
|
color=[f'rgba(128, 128, 128, {0.3 + p["confidence"] * 0.5})' for p in sideways_predictions],
|
||||||
line=dict(width=1, color='gray')
|
line=dict(width=1, color='gray')
|
||||||
),
|
),
|
||||||
@ -2674,6 +2683,21 @@ class CleanTradingDashboard:
|
|||||||
# Sync current position from trading executor first
|
# Sync current position from trading executor first
|
||||||
self._sync_position_from_executor(symbol)
|
self._sync_position_from_executor(symbol)
|
||||||
|
|
||||||
|
# DEBUG: Log current position state before trade
|
||||||
|
if self.current_position:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Current position before {action}: "
|
||||||
|
f"{self.current_position['side']} {self.current_position['size']:.3f} @ ${self.current_position['price']:.2f}")
|
||||||
|
else:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: No current position before {action}")
|
||||||
|
|
||||||
|
# Log the trading executor's position state
|
||||||
|
if hasattr(self.trading_executor, 'get_current_position'):
|
||||||
|
executor_pos = self.trading_executor.get_current_position(symbol)
|
||||||
|
if executor_pos:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Executor position: {executor_pos}")
|
||||||
|
else:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: No position in executor")
|
||||||
|
|
||||||
# CAPTURE ALL MODEL INPUTS INCLUDING COB DATA FOR RETROSPECTIVE TRAINING
|
# CAPTURE ALL MODEL INPUTS INCLUDING COB DATA FOR RETROSPECTIVE TRAINING
|
||||||
try:
|
try:
|
||||||
from core.trade_data_manager import TradeDataManager
|
from core.trade_data_manager import TradeDataManager
|
||||||
@ -2727,7 +2751,10 @@ class CleanTradingDashboard:
|
|||||||
|
|
||||||
# Execute through trading executor
|
# Execute through trading executor
|
||||||
try:
|
try:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Attempting to execute {action} trade via executor...")
|
||||||
result = self.trading_executor.execute_trade(symbol, action, 0.01) # Small size for testing
|
result = self.trading_executor.execute_trade(symbol, action, 0.01) # Small size for testing
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Execute trade result: {result}")
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
decision['executed'] = True
|
decision['executed'] = True
|
||||||
decision['execution_time'] = datetime.now() # Track execution time
|
decision['execution_time'] = datetime.now() # Track execution time
|
||||||
@ -2736,12 +2763,28 @@ class CleanTradingDashboard:
|
|||||||
# Sync position from trading executor after execution
|
# Sync position from trading executor after execution
|
||||||
self._sync_position_from_executor(symbol)
|
self._sync_position_from_executor(symbol)
|
||||||
|
|
||||||
|
# DEBUG: Log position state after trade
|
||||||
|
if self.current_position:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Position after {action}: "
|
||||||
|
f"{self.current_position['side']} {self.current_position['size']:.3f} @ ${self.current_position['price']:.2f}")
|
||||||
|
else:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: No position after {action} - position was closed")
|
||||||
|
|
||||||
|
# Check trading executor's position after execution
|
||||||
|
if hasattr(self.trading_executor, 'get_current_position'):
|
||||||
|
executor_pos_after = self.trading_executor.get_current_position(symbol)
|
||||||
|
if executor_pos_after:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Executor position after trade: {executor_pos_after}")
|
||||||
|
else:
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: No position in executor after trade")
|
||||||
|
|
||||||
# Get trade history from executor for completed trades
|
# Get trade history from executor for completed trades
|
||||||
executor_trades = self.trading_executor.get_trade_history() if hasattr(self.trading_executor, 'get_trade_history') else []
|
executor_trades = self.trading_executor.get_trade_history() if hasattr(self.trading_executor, 'get_trade_history') else []
|
||||||
|
|
||||||
# Only add completed trades to closed_trades (not position opens)
|
# Only add completed trades to closed_trades (not position opens)
|
||||||
if executor_trades:
|
if executor_trades:
|
||||||
latest_trade = executor_trades[-1]
|
latest_trade = executor_trades[-1]
|
||||||
|
logger.info(f"MANUAL TRADE DEBUG: Latest trade from executor: {latest_trade}")
|
||||||
# Check if this is a completed trade (has exit price/time)
|
# Check if this is a completed trade (has exit price/time)
|
||||||
if hasattr(latest_trade, 'exit_time') and latest_trade.exit_time:
|
if hasattr(latest_trade, 'exit_time') and latest_trade.exit_time:
|
||||||
trade_record = {
|
trade_record = {
|
||||||
@ -2864,43 +2907,21 @@ class CleanTradingDashboard:
|
|||||||
logger.warning(f"Failed to store opening trade as base case: {e}")
|
logger.warning(f"Failed to store opening trade as base case: {e}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
decision['executed'] = False
|
|
||||||
decision['blocked'] = True
|
decision['blocked'] = True
|
||||||
decision['block_reason'] = "Trading executor returned False"
|
decision['block_reason'] = "Trading executor failed"
|
||||||
logger.warning(f"Manual {action} failed - executor returned False")
|
logger.warning(f"BLOCKED manual {action}: executor returned False")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
decision['executed'] = False
|
|
||||||
decision['blocked'] = True
|
decision['blocked'] = True
|
||||||
decision['block_reason'] = str(e)
|
decision['block_reason'] = str(e)
|
||||||
logger.error(f"Manual {action} failed with error: {e}")
|
logger.error(f"Error executing manual {action}: {e}")
|
||||||
|
|
||||||
# ENHANCED: Add to recent decisions with PRIORITY INSERTION for better persistence
|
# Add to recent decisions for dashboard display
|
||||||
self.recent_decisions.append(decision)
|
self.recent_decisions.append(decision)
|
||||||
|
if len(self.recent_decisions) > 200:
|
||||||
# CONSERVATIVE: Keep MORE decisions for longer history - extend to 300 decisions
|
self.recent_decisions = self.recent_decisions[-200:]
|
||||||
if len(self.recent_decisions) > 300:
|
|
||||||
# When trimming, PRESERVE MANUAL TRADES at higher priority
|
|
||||||
manual_decisions = [d for d in self.recent_decisions if self._get_signal_attribute(d, 'manual', False)]
|
|
||||||
other_decisions = [d for d in self.recent_decisions if not self._get_signal_attribute(d, 'manual', False)]
|
|
||||||
|
|
||||||
# Keep all manual decisions + most recent other decisions
|
|
||||||
max_other_decisions = 300 - len(manual_decisions)
|
|
||||||
if max_other_decisions > 0:
|
|
||||||
trimmed_decisions = manual_decisions + other_decisions[-max_other_decisions:]
|
|
||||||
else:
|
|
||||||
# If too many manual decisions, keep most recent ones
|
|
||||||
trimmed_decisions = manual_decisions[-300:]
|
|
||||||
|
|
||||||
self.recent_decisions = trimmed_decisions
|
|
||||||
logger.debug(f"Trimmed decisions: kept {len(manual_decisions)} manual + {len(trimmed_decisions) - len(manual_decisions)} other")
|
|
||||||
|
|
||||||
# LOG the manual trade execution with enhanced details
|
|
||||||
status = "EXECUTED" if decision['executed'] else ("BLOCKED" if decision['blocked'] else "PENDING")
|
|
||||||
logger.info(f"[MANUAL-{status}] {action} trade at ${current_price:.2f} - Decision stored with enhanced persistence")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error executing manual {action}: {e}")
|
logger.error(f"Error in manual trade execution: {e}")
|
||||||
|
|
||||||
# Model input capture moved to core.trade_data_manager.TradeDataManager
|
# Model input capture moved to core.trade_data_manager.TradeDataManager
|
||||||
|
|
||||||
@ -3599,6 +3620,7 @@ class CleanTradingDashboard:
|
|||||||
if hasattr(self.orchestrator, '_on_cob_dashboard_data'):
|
if hasattr(self.orchestrator, '_on_cob_dashboard_data'):
|
||||||
try:
|
try:
|
||||||
self.orchestrator._on_cob_dashboard_data(symbol, history_data)
|
self.orchestrator._on_cob_dashboard_data(symbol, history_data)
|
||||||
|
logger.debug(f"COB data fed to orchestrator for {symbol}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error feeding COB data to orchestrator: {e}")
|
logger.debug(f"Error feeding COB data to orchestrator: {e}")
|
||||||
|
|
||||||
@ -3676,7 +3698,7 @@ class CleanTradingDashboard:
|
|||||||
'training_steps': len(model.losses),
|
'training_steps': len(model.losses),
|
||||||
'last_update': datetime.now().isoformat()
|
'last_update': datetime.now().isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error updating training progress: {e}")
|
logger.debug(f"Error updating training progress: {e}")
|
||||||
|
|
||||||
@ -3801,7 +3823,7 @@ class CleanTradingDashboard:
|
|||||||
stats[name] = 0.0
|
stats[name] = 0.0
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
def _connect_to_orchestrator(self):
|
def _connect_to_orchestrator(self):
|
||||||
"""Connect to orchestrator for real trading signals"""
|
"""Connect to orchestrator for real trading signals"""
|
||||||
try:
|
try:
|
||||||
@ -3820,7 +3842,7 @@ class CleanTradingDashboard:
|
|||||||
logger.warning("Orchestrator not available or doesn't support callbacks")
|
logger.warning("Orchestrator not available or doesn't support callbacks")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error initiating orchestrator connection: {e}")
|
logger.error(f"Error initiating orchestrator connection: {e}")
|
||||||
|
|
||||||
async def _on_trading_decision(self, decision):
|
async def _on_trading_decision(self, decision):
|
||||||
"""Handle trading decision from orchestrator."""
|
"""Handle trading decision from orchestrator."""
|
||||||
try:
|
try:
|
||||||
@ -3839,7 +3861,7 @@ class CleanTradingDashboard:
|
|||||||
logger.info(f"[ORCHESTRATOR SIGNAL] Received: {action} for {symbol}")
|
logger.info(f"[ORCHESTRATOR SIGNAL] Received: {action} for {symbol}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error handling trading decision: {e}")
|
logger.error(f"Error handling trading decision: {e}")
|
||||||
|
|
||||||
def _initialize_streaming(self):
|
def _initialize_streaming(self):
|
||||||
"""Initialize data streaming"""
|
"""Initialize data streaming"""
|
||||||
try:
|
try:
|
||||||
@ -3848,7 +3870,7 @@ class CleanTradingDashboard:
|
|||||||
logger.info("Data streaming initialized")
|
logger.info("Data streaming initialized")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error initializing streaming: {e}")
|
logger.error(f"Error initializing streaming: {e}")
|
||||||
|
|
||||||
def _start_websocket_streaming(self):
|
def _start_websocket_streaming(self):
|
||||||
"""Start WebSocket streaming for real-time data."""
|
"""Start WebSocket streaming for real-time data."""
|
||||||
ws_thread = threading.Thread(target=self._ws_worker, daemon=True)
|
ws_thread = threading.Thread(target=self._ws_worker, daemon=True)
|
||||||
@ -3894,7 +3916,7 @@ class CleanTradingDashboard:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"WebSocket worker error: {e}")
|
logger.error(f"WebSocket worker error: {e}")
|
||||||
self.is_streaming = False
|
self.is_streaming = False
|
||||||
|
|
||||||
def _start_data_collection(self):
|
def _start_data_collection(self):
|
||||||
"""Start background data collection"""
|
"""Start background data collection"""
|
||||||
data_thread = threading.Thread(target=self._data_worker, daemon=True)
|
data_thread = threading.Thread(target=self._data_worker, daemon=True)
|
||||||
@ -3935,7 +3957,7 @@ class CleanTradingDashboard:
|
|||||||
self._start_real_training_system()
|
self._start_real_training_system()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error starting comprehensive training system: {e}")
|
logger.error(f"Error starting comprehensive training system: {e}")
|
||||||
|
|
||||||
def _start_real_training_system(self):
|
def _start_real_training_system(self):
|
||||||
"""Start real training system with data collection and actual model training"""
|
"""Start real training system with data collection and actual model training"""
|
||||||
try:
|
try:
|
||||||
@ -3969,7 +3991,7 @@ class CleanTradingDashboard:
|
|||||||
logger.info("TRAINING: Real training system started successfully")
|
logger.info("TRAINING: Real training system started successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error starting real training system: {e}")
|
logger.error(f"Error starting real training system: {e}")
|
||||||
|
|
||||||
def _collect_training_data(self) -> List[Dict]:
|
def _collect_training_data(self) -> List[Dict]:
|
||||||
"""Collect real market data for training"""
|
"""Collect real market data for training"""
|
||||||
try:
|
try:
|
||||||
@ -4001,7 +4023,7 @@ class CleanTradingDashboard:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error collecting training data: {e}")
|
logger.error(f"Error collecting training data: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _perform_real_dqn_training(self, market_data: List[Dict]):
|
def _perform_real_dqn_training(self, market_data: List[Dict]):
|
||||||
"""Perform actual DQN training with real market experiences"""
|
"""Perform actual DQN training with real market experiences"""
|
||||||
try:
|
try:
|
||||||
@ -4044,7 +4066,7 @@ class CleanTradingDashboard:
|
|||||||
logger.info(f"DQN TRAINING: Added {training_samples} experiences, memory size: {len(agent.memory)}")
|
logger.info(f"DQN TRAINING: Added {training_samples} experiences, memory size: {len(agent.memory)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in real DQN training: {e}")
|
logger.error(f"Error in real DQN training: {e}")
|
||||||
|
|
||||||
def _perform_real_cnn_training(self, market_data: List[Dict]):
|
def _perform_real_cnn_training(self, market_data: List[Dict]):
|
||||||
"""Perform actual CNN training with real price prediction"""
|
"""Perform actual CNN training with real price prediction"""
|
||||||
try:
|
try:
|
||||||
@ -4088,7 +4110,7 @@ class CleanTradingDashboard:
|
|||||||
logger.info(f"CNN TRAINING: Processed {training_samples} price prediction samples")
|
logger.info(f"CNN TRAINING: Processed {training_samples} price prediction samples")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in real CNN training: {e}")
|
logger.error(f"Error in real CNN training: {e}")
|
||||||
|
|
||||||
def _update_training_progress(self, iteration: int):
|
def _update_training_progress(self, iteration: int):
|
||||||
"""Update training progress and metrics"""
|
"""Update training progress and metrics"""
|
||||||
try:
|
try:
|
||||||
|
@ -273,13 +273,13 @@ class DashboardComponentManager:
|
|||||||
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats)
|
overview_panel = self._create_cob_overview_panel(symbol, stats, cumulative_imbalance_stats)
|
||||||
|
|
||||||
# --- Right Panel: Compact Ladder ---
|
# --- Right Panel: Compact Ladder ---
|
||||||
ladder_panel = self._create_cob_ladder_panel(bids, asks, mid_price)
|
ladder_panel = self._create_cob_ladder_panel(bids, asks, mid_price, symbol)
|
||||||
|
|
||||||
return dbc.Row([
|
return dbc.Row([
|
||||||
dbc.Col(overview_panel, width=5, className="pe-1"),
|
dbc.Col(overview_panel, width=5, className="pe-1"),
|
||||||
dbc.Col(ladder_panel, width=7, className="ps-1")
|
dbc.Col(ladder_panel, width=7, className="ps-1")
|
||||||
], className="g-0") # g-0 removes gutters
|
], className="g-0") # g-0 removes gutters
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error formatting split COB data: {e}")
|
logger.error(f"Error formatting split COB data: {e}")
|
||||||
return html.P(f"Error: {str(e)}", className="text-danger small")
|
return html.P(f"Error: {str(e)}", className="text-danger small")
|
||||||
@ -347,7 +347,7 @@ class DashboardComponentManager:
|
|||||||
html.Div(value, className="fw-bold")
|
html.Div(value, className="fw-bold")
|
||||||
], className="text-center")
|
], className="text-center")
|
||||||
|
|
||||||
def _create_cob_ladder_panel(self, bids, asks, mid_price):
|
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."""
|
||||||
bucket_size = 10
|
bucket_size = 10
|
||||||
num_levels = 5
|
num_levels = 5
|
||||||
@ -356,52 +356,77 @@ class DashboardComponentManager:
|
|||||||
buckets = {}
|
buckets = {}
|
||||||
for order in orders:
|
for order in orders:
|
||||||
price = order.get('price', 0)
|
price = order.get('price', 0)
|
||||||
size = order.get('size', 0)
|
# Handle both old format (size) and new format (total_size)
|
||||||
|
size = order.get('total_size', order.get('size', 0))
|
||||||
|
volume_usd = order.get('total_volume_usd', size * price)
|
||||||
if price > 0:
|
if price > 0:
|
||||||
bucket_key = round(price / bucket_size) * bucket_size
|
bucket_key = round(price / bucket_size) * bucket_size
|
||||||
if bucket_key not in buckets:
|
if bucket_key not in buckets:
|
||||||
buckets[bucket_key] = 0
|
buckets[bucket_key] = {'usd_volume': 0, 'crypto_volume': 0}
|
||||||
buckets[bucket_key] += size * price
|
buckets[bucket_key]['usd_volume'] += volume_usd
|
||||||
|
buckets[bucket_key]['crypto_volume'] += size
|
||||||
return buckets
|
return buckets
|
||||||
|
|
||||||
bid_buckets = aggregate_buckets(bids)
|
bid_buckets = aggregate_buckets(bids)
|
||||||
ask_buckets = aggregate_buckets(asks)
|
ask_buckets = aggregate_buckets(asks)
|
||||||
|
|
||||||
all_volumes = list(bid_buckets.values()) + list(ask_buckets.values())
|
all_usd_volumes = [b['usd_volume'] for b in bid_buckets.values()] + [a['usd_volume'] for a in ask_buckets.values()]
|
||||||
max_volume = max(all_volumes) if all_volumes else 1
|
max_volume = max(all_usd_volumes) if all_usd_volumes else 1
|
||||||
|
|
||||||
center_bucket = round(mid_price / bucket_size) * bucket_size
|
center_bucket = round(mid_price / bucket_size) * bucket_size
|
||||||
ask_levels = [center_bucket + i * bucket_size for i in range(1, num_levels + 1)]
|
ask_levels = [center_bucket + i * bucket_size for i in range(1, num_levels + 1)]
|
||||||
bid_levels = [center_bucket - i * bucket_size for i in range(num_levels)]
|
bid_levels = [center_bucket - i * bucket_size for i in range(num_levels)]
|
||||||
|
|
||||||
def create_ladder_row(price, volume, max_vol, row_type):
|
def create_ladder_row(price, bucket_data, max_vol, row_type):
|
||||||
progress = (volume / max_vol) * 100 if max_vol > 0 else 0
|
usd_volume = bucket_data.get('usd_volume', 0)
|
||||||
|
crypto_volume = bucket_data.get('crypto_volume', 0)
|
||||||
|
|
||||||
|
progress = (usd_volume / max_vol) * 100 if max_vol > 0 else 0
|
||||||
color = "danger" if row_type == 'ask' else "success"
|
color = "danger" if row_type == 'ask' else "success"
|
||||||
text_color = "text-danger" if row_type == 'ask' else "text-success"
|
text_color = "text-danger" if row_type == 'ask' else "text-success"
|
||||||
|
|
||||||
vol_str = f"${volume/1e3:.0f}K" if volume > 1e3 else f"${volume:,.0f}"
|
# Format USD volume (no $ symbol)
|
||||||
|
if usd_volume > 1e6:
|
||||||
|
usd_str = f"{usd_volume/1e6:.1f}M"
|
||||||
|
elif usd_volume > 1e3:
|
||||||
|
usd_str = f"{usd_volume/1e3:.0f}K"
|
||||||
|
else:
|
||||||
|
usd_str = f"{usd_volume:,.0f}"
|
||||||
|
|
||||||
|
# Format crypto volume (no unit symbol)
|
||||||
|
if crypto_volume > 1000:
|
||||||
|
crypto_str = f"{crypto_volume/1000:.1f}K"
|
||||||
|
elif crypto_volume > 1:
|
||||||
|
crypto_str = f"{crypto_volume:.1f}"
|
||||||
|
else:
|
||||||
|
crypto_str = f"{crypto_volume:.3f}"
|
||||||
|
|
||||||
return html.Tr([
|
return html.Tr([
|
||||||
html.Td(f"${price:,.2f}", className=f"{text_color} price-level"),
|
html.Td(f"${price:,.0f}", className=f"{text_color} price-level small"),
|
||||||
html.Td(
|
html.Td(
|
||||||
dbc.Progress(value=progress, color=color, className="vh-25 compact-progress"),
|
dbc.Progress(value=progress, color=color, className="vh-25 compact-progress"),
|
||||||
className="progress-cell"
|
className="progress-cell p-0"
|
||||||
),
|
),
|
||||||
html.Td(vol_str, className="volume-level text-end")
|
html.Td(usd_str, className="volume-level text-end fw-bold small p-0 pe-1"),
|
||||||
], className="compact-ladder-row")
|
html.Td(crypto_str, className="volume-level text-start small text-muted p-0 ps-1")
|
||||||
|
], className="compact-ladder-row p-0")
|
||||||
|
|
||||||
ask_rows = [create_ladder_row(p, ask_buckets.get(p, 0), max_volume, 'ask') for p in sorted(ask_levels, reverse=True)]
|
def get_bucket_data(buckets, price):
|
||||||
bid_rows = [create_ladder_row(p, bid_buckets.get(p, 0), max_volume, 'bid') for p in sorted(bid_levels, reverse=True)]
|
return buckets.get(price, {'usd_volume': 0, 'crypto_volume': 0})
|
||||||
|
|
||||||
|
ask_rows = [create_ladder_row(p, get_bucket_data(ask_buckets, p), max_volume, 'ask') for p in sorted(ask_levels, reverse=True)]
|
||||||
|
bid_rows = [create_ladder_row(p, get_bucket_data(bid_buckets, p), max_volume, 'bid') for p in sorted(bid_levels, reverse=True)]
|
||||||
|
|
||||||
mid_row = html.Tr([
|
mid_row = html.Tr([
|
||||||
html.Td(f"${mid_price:,.2f}", colSpan=3, className="text-center fw-bold small mid-price-row")
|
html.Td(f"${mid_price:,.0f}", colSpan=4, className="text-center fw-bold small mid-price-row p-0")
|
||||||
])
|
])
|
||||||
|
|
||||||
ladder_table = html.Table([
|
ladder_table = html.Table([
|
||||||
html.Thead(html.Tr([
|
html.Thead(html.Tr([
|
||||||
html.Th("Price", className="small"),
|
html.Th("Price", className="small p-0"),
|
||||||
html.Th("Volume", className="small"),
|
html.Th("Volume", className="small p-0"),
|
||||||
html.Th("Total", className="small text-end")
|
html.Th("USD", className="small text-end p-0 pe-1"),
|
||||||
|
html.Th("Crypto", className="small text-start p-0 ps-1")
|
||||||
])),
|
])),
|
||||||
html.Tbody(ask_rows + [mid_row] + bid_rows)
|
html.Tbody(ask_rows + [mid_row] + bid_rows)
|
||||||
], className="table table-sm table-borderless cob-ladder-table-compact m-0 p-0") # Compact classes
|
], className="table table-sm table-borderless cob-ladder-table-compact m-0 p-0") # Compact classes
|
||||||
@ -477,7 +502,10 @@ class DashboardComponentManager:
|
|||||||
bid_pct = bucket['bid_pct']
|
bid_pct = bucket['bid_pct']
|
||||||
ask_pct = bucket['ask_pct']
|
ask_pct = bucket['ask_pct']
|
||||||
|
|
||||||
# Format volume
|
# Get crypto volume if available (some bucket formats include crypto_volume)
|
||||||
|
crypto_vol = bucket.get('crypto_volume', bucket.get('size', 0))
|
||||||
|
|
||||||
|
# Format USD volume
|
||||||
if total_vol > 1000000:
|
if total_vol > 1000000:
|
||||||
vol_str = f"${total_vol/1000000:.1f}M"
|
vol_str = f"${total_vol/1000000:.1f}M"
|
||||||
elif total_vol > 1000:
|
elif total_vol > 1000:
|
||||||
@ -485,6 +513,17 @@ class DashboardComponentManager:
|
|||||||
else:
|
else:
|
||||||
vol_str = f"${total_vol:.0f}"
|
vol_str = f"${total_vol:.0f}"
|
||||||
|
|
||||||
|
# Format crypto volume based on symbol
|
||||||
|
crypto_unit = "BTC" if "BTC" in symbol else "ETH" if "ETH" in symbol else "CRYPTO"
|
||||||
|
if crypto_vol > 1000:
|
||||||
|
crypto_str = f"{crypto_vol/1000:.1f}K {crypto_unit}"
|
||||||
|
elif crypto_vol > 1:
|
||||||
|
crypto_str = f"{crypto_vol:.1f} {crypto_unit}"
|
||||||
|
elif crypto_vol > 0:
|
||||||
|
crypto_str = f"{crypto_vol:.3f} {crypto_unit}"
|
||||||
|
else:
|
||||||
|
crypto_str = ""
|
||||||
|
|
||||||
# Color based on bid/ask dominance
|
# Color based on bid/ask dominance
|
||||||
if bid_pct > 60:
|
if bid_pct > 60:
|
||||||
row_class = "border-success"
|
row_class = "border-success"
|
||||||
@ -503,8 +542,9 @@ class DashboardComponentManager:
|
|||||||
html.Div([
|
html.Div([
|
||||||
html.Span(f"${price:.0f}", className="fw-bold me-2"),
|
html.Span(f"${price:.0f}", className="fw-bold me-2"),
|
||||||
html.Span(vol_str, className="text-info me-2"),
|
html.Span(vol_str, className="text-info me-2"),
|
||||||
|
html.Span(crypto_str, className="small text-muted me-2") if crypto_str else "",
|
||||||
html.Span(f"{dominance}", className=f"small {dominance_class}")
|
html.Span(f"{dominance}", className=f"small {dominance_class}")
|
||||||
], className="d-flex justify-content-between"),
|
], className="d-flex justify-content-between align-items-center"),
|
||||||
html.Div([
|
html.Div([
|
||||||
# Bid bar
|
# Bid bar
|
||||||
html.Div(
|
html.Div(
|
||||||
|
Reference in New Issue
Block a user