try to have sell actions
This commit is contained in:
@ -318,8 +318,9 @@ class TradingExecutor:
|
|||||||
"""Execute a sell order"""
|
"""Execute a sell order"""
|
||||||
# Check if we have a position to sell
|
# Check if we have a position to sell
|
||||||
if symbol not in self.positions:
|
if symbol not in self.positions:
|
||||||
logger.info(f"No position to sell in {symbol}")
|
logger.info(f"No position to sell in {symbol}. Opening short position")
|
||||||
return False
|
# TODO: Open short position
|
||||||
|
|
||||||
|
|
||||||
position = self.positions[symbol]
|
position = self.positions[symbol]
|
||||||
|
|
||||||
|
@ -243,13 +243,39 @@ class CleanTradingDashboard:
|
|||||||
session_pnl_str = f"${self.session_pnl:.2f}"
|
session_pnl_str = f"${self.session_pnl:.2f}"
|
||||||
session_pnl_class = "text-success" if self.session_pnl >= 0 else "text-danger"
|
session_pnl_class = "text-success" if self.session_pnl >= 0 else "text-danger"
|
||||||
|
|
||||||
# Current position
|
# Current position with unrealized P&L (x50 leverage)
|
||||||
position_str = "No Position"
|
position_str = "No Position"
|
||||||
if self.current_position:
|
if self.current_position:
|
||||||
side = self.current_position.get('side', 'UNKNOWN')
|
side = self.current_position.get('side', 'UNKNOWN')
|
||||||
size = self.current_position.get('size', 0)
|
size = self.current_position.get('size', 0)
|
||||||
entry_price = self.current_position.get('price', 0)
|
entry_price = self.current_position.get('price', 0)
|
||||||
position_str = f"{side} {size:.3f} @ ${entry_price:.2f}"
|
|
||||||
|
# Calculate unrealized P&L with x50 leverage
|
||||||
|
unrealized_pnl = 0.0
|
||||||
|
pnl_str = ""
|
||||||
|
pnl_class = ""
|
||||||
|
|
||||||
|
if current_price and entry_price and size > 0:
|
||||||
|
# Calculate raw P&L per unit
|
||||||
|
if side.upper() == 'LONG' or side.upper() == 'BUY':
|
||||||
|
raw_pnl_per_unit = current_price - entry_price
|
||||||
|
else: # SHORT or SELL
|
||||||
|
raw_pnl_per_unit = entry_price - current_price
|
||||||
|
|
||||||
|
# Apply x50 leverage to P&L calculation
|
||||||
|
# With leverage, P&L is amplified by the leverage factor
|
||||||
|
leveraged_pnl_per_unit = raw_pnl_per_unit * 50
|
||||||
|
unrealized_pnl = leveraged_pnl_per_unit * size
|
||||||
|
|
||||||
|
# Format P&L string with color
|
||||||
|
if unrealized_pnl >= 0:
|
||||||
|
pnl_str = f" (+${unrealized_pnl:.2f})"
|
||||||
|
pnl_class = "text-success"
|
||||||
|
else:
|
||||||
|
pnl_str = f" (${unrealized_pnl:.2f})"
|
||||||
|
pnl_class = "text-danger"
|
||||||
|
|
||||||
|
position_str = f"{side.upper()} {size:.3f} @ ${entry_price:.2f}{pnl_str}"
|
||||||
|
|
||||||
# Portfolio value
|
# Portfolio value
|
||||||
initial_balance = self._get_initial_balance()
|
initial_balance = self._get_initial_balance()
|
||||||
@ -315,18 +341,13 @@ class CleanTradingDashboard:
|
|||||||
return html.P(f"Error: {str(e)}", className="text-danger")
|
return html.P(f"Error: {str(e)}", className="text-danger")
|
||||||
|
|
||||||
@self.app.callback(
|
@self.app.callback(
|
||||||
[Output('cob-status-content', 'children'),
|
[Output('eth-cob-content', 'children'),
|
||||||
Output('eth-cob-content', 'children'),
|
|
||||||
Output('btc-cob-content', 'children')],
|
Output('btc-cob-content', 'children')],
|
||||||
[Input('interval-component', 'n_intervals')]
|
[Input('interval-component', 'n_intervals')]
|
||||||
)
|
)
|
||||||
def update_cob_data(n):
|
def update_cob_data(n):
|
||||||
"""Update COB data displays"""
|
"""Update COB data displays"""
|
||||||
try:
|
try:
|
||||||
# COB Status
|
|
||||||
cob_status = self._get_cob_status()
|
|
||||||
status_components = self.component_manager.format_system_status(cob_status)
|
|
||||||
|
|
||||||
# ETH/USDT COB
|
# ETH/USDT COB
|
||||||
eth_cob = self._get_cob_snapshot('ETH/USDT')
|
eth_cob = self._get_cob_snapshot('ETH/USDT')
|
||||||
eth_components = self.component_manager.format_cob_data(eth_cob, 'ETH/USDT')
|
eth_components = self.component_manager.format_cob_data(eth_cob, 'ETH/USDT')
|
||||||
@ -335,12 +356,12 @@ class CleanTradingDashboard:
|
|||||||
btc_cob = self._get_cob_snapshot('BTC/USDT')
|
btc_cob = self._get_cob_snapshot('BTC/USDT')
|
||||||
btc_components = self.component_manager.format_cob_data(btc_cob, 'BTC/USDT')
|
btc_components = self.component_manager.format_cob_data(btc_cob, 'BTC/USDT')
|
||||||
|
|
||||||
return status_components, eth_components, btc_components
|
return eth_components, btc_components
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating COB data: {e}")
|
logger.error(f"Error updating COB data: {e}")
|
||||||
error_msg = html.P(f"Error: {str(e)}", className="text-danger")
|
error_msg = html.P(f"Error: {str(e)}", className="text-danger")
|
||||||
return error_msg, error_msg, error_msg
|
return error_msg, error_msg
|
||||||
|
|
||||||
@self.app.callback(
|
@self.app.callback(
|
||||||
Output('training-metrics', 'children'),
|
Output('training-metrics', 'children'),
|
||||||
@ -1151,8 +1172,9 @@ class CleanTradingDashboard:
|
|||||||
'orchestrator_type': 'Basic'
|
'orchestrator_type': 'Basic'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# EXAMPLE OF WHAT WE SHOULD NEVER DO!!! use only real data or report we have no data
|
||||||
# COB $1 Buckets (sample data for now)
|
# COB $1 Buckets (sample data for now)
|
||||||
metrics['cob_buckets'] = self._get_cob_dollar_buckets()
|
# metrics['cob_buckets'] = self._get_cob_dollar_buckets()
|
||||||
|
|
||||||
return metrics
|
return metrics
|
||||||
|
|
||||||
@ -1300,9 +1322,42 @@ class CleanTradingDashboard:
|
|||||||
signal['blocked'] = False
|
signal['blocked'] = False
|
||||||
signal['manual'] = False
|
signal['manual'] = False
|
||||||
|
|
||||||
# Execute signal if confidence is above threshold
|
# Smart confidence-based execution with different thresholds for opening vs closing
|
||||||
confidence_threshold = 0.25 # Lower threshold for testing (was too high)
|
confidence = signal.get('confidence', 0)
|
||||||
if signal.get('confidence', 0) >= confidence_threshold:
|
action = signal.get('action', 'HOLD')
|
||||||
|
should_execute = False
|
||||||
|
execution_reason = ""
|
||||||
|
|
||||||
|
# Define confidence thresholds
|
||||||
|
CLOSE_POSITION_THRESHOLD = 0.25 # Lower threshold to close positions
|
||||||
|
OPEN_POSITION_THRESHOLD = 0.60 # Higher threshold to open new positions
|
||||||
|
|
||||||
|
# Determine if we should execute based on current position and action
|
||||||
|
if action == 'BUY':
|
||||||
|
if self.current_position and self.current_position.get('side') == 'SHORT':
|
||||||
|
# Closing SHORT position - use lower threshold
|
||||||
|
if confidence >= CLOSE_POSITION_THRESHOLD:
|
||||||
|
should_execute = True
|
||||||
|
execution_reason = f"Closing SHORT position (threshold: {CLOSE_POSITION_THRESHOLD})"
|
||||||
|
else:
|
||||||
|
# Opening new LONG position - use higher threshold
|
||||||
|
if confidence >= OPEN_POSITION_THRESHOLD:
|
||||||
|
should_execute = True
|
||||||
|
execution_reason = f"Opening LONG position (threshold: {OPEN_POSITION_THRESHOLD})"
|
||||||
|
|
||||||
|
elif action == 'SELL':
|
||||||
|
if self.current_position and self.current_position.get('side') == 'LONG':
|
||||||
|
# Closing LONG position - use lower threshold
|
||||||
|
if confidence >= CLOSE_POSITION_THRESHOLD:
|
||||||
|
should_execute = True
|
||||||
|
execution_reason = f"Closing LONG position (threshold: {CLOSE_POSITION_THRESHOLD})"
|
||||||
|
else:
|
||||||
|
# Opening new SHORT position - use higher threshold
|
||||||
|
if confidence >= OPEN_POSITION_THRESHOLD:
|
||||||
|
should_execute = True
|
||||||
|
execution_reason = f"Opening SHORT position (threshold: {OPEN_POSITION_THRESHOLD})"
|
||||||
|
|
||||||
|
if should_execute:
|
||||||
try:
|
try:
|
||||||
# Attempt to execute the signal
|
# Attempt to execute the signal
|
||||||
symbol = signal.get('symbol', 'ETH/USDT')
|
symbol = signal.get('symbol', 'ETH/USDT')
|
||||||
@ -1314,7 +1369,7 @@ class CleanTradingDashboard:
|
|||||||
if result:
|
if result:
|
||||||
signal['executed'] = True
|
signal['executed'] = True
|
||||||
logger.info(f"✅ EXECUTED {action} signal: {symbol} @ ${signal.get('price', 0):.2f} "
|
logger.info(f"✅ EXECUTED {action} signal: {symbol} @ ${signal.get('price', 0):.2f} "
|
||||||
f"(conf: {signal['confidence']:.2f}, size: {size})")
|
f"(conf: {signal['confidence']:.2f}, size: {size}) - {execution_reason}")
|
||||||
|
|
||||||
# Create trade record for tracking
|
# Create trade record for tracking
|
||||||
trade_record = {
|
trade_record = {
|
||||||
@ -1329,6 +1384,45 @@ class CleanTradingDashboard:
|
|||||||
'trade_type': 'auto_signal'
|
'trade_type': 'auto_signal'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create/update current position for unrealized P&L tracking
|
||||||
|
current_price = signal.get('price', 0)
|
||||||
|
if action == 'BUY':
|
||||||
|
# Create or add to LONG position
|
||||||
|
self.current_position = {
|
||||||
|
'side': 'LONG',
|
||||||
|
'size': size,
|
||||||
|
'price': current_price,
|
||||||
|
'symbol': symbol,
|
||||||
|
'entry_time': datetime.now(),
|
||||||
|
'leverage': 50
|
||||||
|
}
|
||||||
|
logger.info(f"Auto-signal created LONG position: {size:.3f} @ ${current_price:.2f}")
|
||||||
|
else: # SELL
|
||||||
|
# Create SHORT position or close LONG
|
||||||
|
if self.current_position and self.current_position.get('side') == 'LONG':
|
||||||
|
# Close LONG position and calculate realized P&L
|
||||||
|
entry_price = self.current_position.get('price', 0)
|
||||||
|
position_size = self.current_position.get('size', 0)
|
||||||
|
if entry_price and position_size:
|
||||||
|
# Calculate leveraged P&L for position close
|
||||||
|
raw_pnl = (current_price - entry_price) * position_size
|
||||||
|
leveraged_pnl = raw_pnl * 50 # x50 leverage
|
||||||
|
self.session_pnl += leveraged_pnl
|
||||||
|
trade_record['pnl'] = leveraged_pnl
|
||||||
|
logger.info(f"Closed LONG position: P&L ${leveraged_pnl:.2f} (x50 leverage)")
|
||||||
|
self.current_position = None
|
||||||
|
else:
|
||||||
|
# Create SHORT position
|
||||||
|
self.current_position = {
|
||||||
|
'side': 'SHORT',
|
||||||
|
'size': size,
|
||||||
|
'price': current_price,
|
||||||
|
'symbol': symbol,
|
||||||
|
'entry_time': datetime.now(),
|
||||||
|
'leverage': 50
|
||||||
|
}
|
||||||
|
logger.info(f"Auto-signal created SHORT position: {size:.3f} @ ${current_price:.2f}")
|
||||||
|
|
||||||
self.closed_trades.append(trade_record)
|
self.closed_trades.append(trade_record)
|
||||||
|
|
||||||
# Update session metrics immediately
|
# Update session metrics immediately
|
||||||
@ -1352,9 +1446,28 @@ class CleanTradingDashboard:
|
|||||||
signal['block_reason'] = str(e)
|
signal['block_reason'] = str(e)
|
||||||
logger.error(f"❌ EXECUTION ERROR for {signal.get('action', 'UNKNOWN')}: {e}")
|
logger.error(f"❌ EXECUTION ERROR for {signal.get('action', 'UNKNOWN')}: {e}")
|
||||||
else:
|
else:
|
||||||
|
# Determine which threshold was not met
|
||||||
|
if action == 'BUY':
|
||||||
|
if self.current_position and self.current_position.get('side') == 'SHORT':
|
||||||
|
required_threshold = CLOSE_POSITION_THRESHOLD
|
||||||
|
operation = "close SHORT position"
|
||||||
|
else:
|
||||||
|
required_threshold = OPEN_POSITION_THRESHOLD
|
||||||
|
operation = "open LONG position"
|
||||||
|
elif action == 'SELL':
|
||||||
|
if self.current_position and self.current_position.get('side') == 'LONG':
|
||||||
|
required_threshold = CLOSE_POSITION_THRESHOLD
|
||||||
|
operation = "close LONG position"
|
||||||
|
else:
|
||||||
|
required_threshold = OPEN_POSITION_THRESHOLD
|
||||||
|
operation = "open SHORT position"
|
||||||
|
else:
|
||||||
|
required_threshold = 0.25
|
||||||
|
operation = "execute signal"
|
||||||
|
|
||||||
signal['blocked'] = True
|
signal['blocked'] = True
|
||||||
signal['block_reason'] = f"Confidence {signal.get('confidence', 0):.3f} below threshold {confidence_threshold}"
|
signal['block_reason'] = f"Confidence {confidence:.3f} below threshold {required_threshold:.2f} to {operation}"
|
||||||
logger.debug(f"Signal confidence {signal.get('confidence', 0):.3f} below execution threshold {confidence_threshold}")
|
logger.debug(f"Signal confidence {confidence:.3f} below {required_threshold:.2f} threshold to {operation}")
|
||||||
|
|
||||||
# Add to recent decisions for display
|
# Add to recent decisions for display
|
||||||
self.recent_decisions.append(signal)
|
self.recent_decisions.append(signal)
|
||||||
@ -1376,22 +1489,23 @@ class CleanTradingDashboard:
|
|||||||
# Basic orchestrator doesn't have DQN features
|
# Basic orchestrator doesn't have DQN features
|
||||||
return
|
return
|
||||||
|
|
||||||
def _get_cob_dollar_buckets(self) -> List[Dict]:
|
# EXAMPLE OF WHAT WE SHOULD NEVER DO!!! use only real data or report we have no data
|
||||||
"""Get COB $1 price buckets with volume data"""
|
# def _get_cob_dollar_buckets(self) -> List[Dict]:
|
||||||
try:
|
# """Get COB $1 price buckets with volume data"""
|
||||||
# This would normally come from the COB integration
|
# try:
|
||||||
# For now, return sample data structure
|
# # This would normally come from the COB integration
|
||||||
sample_buckets = [
|
# # For now, return sample data structure
|
||||||
{'price': 2000, 'total_volume': 150000, 'bid_pct': 45, 'ask_pct': 55},
|
# sample_buckets = [
|
||||||
{'price': 2001, 'total_volume': 120000, 'bid_pct': 52, 'ask_pct': 48},
|
# {'price': 2000, 'total_volume': 150000, 'bid_pct': 45, 'ask_pct': 55},
|
||||||
{'price': 1999, 'total_volume': 98000, 'bid_pct': 38, 'ask_pct': 62},
|
# {'price': 2001, 'total_volume': 120000, 'bid_pct': 52, 'ask_pct': 48},
|
||||||
{'price': 2002, 'total_volume': 87000, 'bid_pct': 60, 'ask_pct': 40},
|
# {'price': 1999, 'total_volume': 98000, 'bid_pct': 38, 'ask_pct': 62},
|
||||||
{'price': 1998, 'total_volume': 76000, 'bid_pct': 35, 'ask_pct': 65}
|
# {'price': 2002, 'total_volume': 87000, 'bid_pct': 60, 'ask_pct': 40},
|
||||||
]
|
# {'price': 1998, 'total_volume': 76000, 'bid_pct': 35, 'ask_pct': 65}
|
||||||
return sample_buckets
|
# ]
|
||||||
except Exception as e:
|
# return sample_buckets
|
||||||
logger.debug(f"Error getting COB buckets: {e}")
|
# except Exception as e:
|
||||||
return []
|
# logger.debug(f"Error getting COB buckets: {e}")
|
||||||
|
# return []
|
||||||
|
|
||||||
def _execute_manual_trade(self, action: str):
|
def _execute_manual_trade(self, action: str):
|
||||||
"""Execute manual trading action - FIXED to properly execute and track trades"""
|
"""Execute manual trading action - FIXED to properly execute and track trades"""
|
||||||
@ -1467,6 +1581,36 @@ class CleanTradingDashboard:
|
|||||||
'training_ready': True # Mark as ready for cold start training
|
'training_ready': True # Mark as ready for cold start training
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create/update current position for unrealized P&L tracking
|
||||||
|
if action == 'BUY':
|
||||||
|
# Create or add to LONG position
|
||||||
|
self.current_position = {
|
||||||
|
'side': 'LONG',
|
||||||
|
'size': 0.004, # Example position size as mentioned by user
|
||||||
|
'price': current_price,
|
||||||
|
'symbol': symbol,
|
||||||
|
'entry_time': datetime.now(),
|
||||||
|
'leverage': 50
|
||||||
|
}
|
||||||
|
logger.info(f"Created LONG position: {self.current_position['size']:.3f} @ ${current_price:.2f}")
|
||||||
|
else: # SELL
|
||||||
|
# Create SHORT position or close LONG
|
||||||
|
if self.current_position and self.current_position.get('side') == 'LONG':
|
||||||
|
# Close LONG position
|
||||||
|
self.current_position = None
|
||||||
|
logger.info("Closed LONG position")
|
||||||
|
else:
|
||||||
|
# Create SHORT position
|
||||||
|
self.current_position = {
|
||||||
|
'side': 'SHORT',
|
||||||
|
'size': 0.004,
|
||||||
|
'price': current_price,
|
||||||
|
'symbol': symbol,
|
||||||
|
'entry_time': datetime.now(),
|
||||||
|
'leverage': 50
|
||||||
|
}
|
||||||
|
logger.info(f"Created SHORT position: {self.current_position['size']:.3f} @ ${current_price:.2f}")
|
||||||
|
|
||||||
# Add to closed trades for display
|
# Add to closed trades for display
|
||||||
self.closed_trades.append(trade_record)
|
self.closed_trades.append(trade_record)
|
||||||
|
|
||||||
@ -1712,11 +1856,70 @@ class CleanTradingDashboard:
|
|||||||
self.closed_trades = []
|
self.closed_trades = []
|
||||||
self.recent_decisions = []
|
self.recent_decisions = []
|
||||||
|
|
||||||
|
# Clear tick cache and associated signals
|
||||||
|
self.tick_cache = []
|
||||||
|
self.ws_price_cache = {}
|
||||||
|
self.current_prices = {}
|
||||||
|
|
||||||
|
# Clear current position
|
||||||
|
self.current_position = None
|
||||||
|
|
||||||
logger.info("Session data cleared")
|
logger.info("Session data cleared")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error clearing session: {e}")
|
logger.error(f"Error clearing session: {e}")
|
||||||
|
|
||||||
|
def _clear_old_signals_for_tick_range(self):
|
||||||
|
"""Clear old signals that are outside the current tick cache time range"""
|
||||||
|
try:
|
||||||
|
if not self.tick_cache or len(self.tick_cache) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the time range of the current tick cache
|
||||||
|
oldest_tick_time = self.tick_cache[0].get('datetime')
|
||||||
|
if not oldest_tick_time:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Filter recent_decisions to only keep signals within the tick cache time range
|
||||||
|
filtered_decisions = []
|
||||||
|
for signal in self.recent_decisions:
|
||||||
|
signal_time = signal.get('timestamp')
|
||||||
|
if signal_time:
|
||||||
|
# Convert signal timestamp to datetime for comparison
|
||||||
|
try:
|
||||||
|
if isinstance(signal_time, str):
|
||||||
|
# Handle time-only format (HH:MM:SS)
|
||||||
|
if ':' in signal_time and len(signal_time.split(':')) == 3:
|
||||||
|
signal_datetime = datetime.now().replace(
|
||||||
|
hour=int(signal_time.split(':')[0]),
|
||||||
|
minute=int(signal_time.split(':')[1]),
|
||||||
|
second=int(signal_time.split(':')[2]),
|
||||||
|
microsecond=0
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
signal_datetime = pd.to_datetime(signal_time)
|
||||||
|
else:
|
||||||
|
signal_datetime = signal_time
|
||||||
|
|
||||||
|
# Keep signal if it's within the tick cache time range
|
||||||
|
if signal_datetime >= oldest_tick_time:
|
||||||
|
filtered_decisions.append(signal)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# Keep signal if we can't parse the timestamp
|
||||||
|
filtered_decisions.append(signal)
|
||||||
|
else:
|
||||||
|
# Keep signal if no timestamp
|
||||||
|
filtered_decisions.append(signal)
|
||||||
|
|
||||||
|
# Update the decisions list
|
||||||
|
self.recent_decisions = filtered_decisions
|
||||||
|
|
||||||
|
logger.debug(f"Cleared old signals: kept {len(filtered_decisions)} signals within tick range")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error clearing old signals: {e}")
|
||||||
|
|
||||||
def _initialize_cob_integration_proper(self):
|
def _initialize_cob_integration_proper(self):
|
||||||
"""Initialize COB integration - Basic orchestrator has no COB features"""
|
"""Initialize COB integration - Basic orchestrator has no COB features"""
|
||||||
try:
|
try:
|
||||||
@ -1890,6 +2093,8 @@ class CleanTradingDashboard:
|
|||||||
self.tick_cache.append(tick_record)
|
self.tick_cache.append(tick_record)
|
||||||
if len(self.tick_cache) > 1000:
|
if len(self.tick_cache) > 1000:
|
||||||
self.tick_cache = self.tick_cache[-1000:]
|
self.tick_cache = self.tick_cache[-1000:]
|
||||||
|
# Clear old signals when tick cache is trimmed
|
||||||
|
self._clear_old_signals_for_tick_range()
|
||||||
|
|
||||||
# NO COB SIMULATION - Real COB data comes from enhanced orchestrator
|
# NO COB SIMULATION - Real COB data comes from enhanced orchestrator
|
||||||
|
|
||||||
@ -2026,6 +2231,8 @@ class CleanTradingDashboard:
|
|||||||
self.tick_cache.extend(data_packet['ticks'][-50:]) # Last 50 ticks
|
self.tick_cache.extend(data_packet['ticks'][-50:]) # Last 50 ticks
|
||||||
if len(self.tick_cache) > 1000:
|
if len(self.tick_cache) > 1000:
|
||||||
self.tick_cache = self.tick_cache[-1000:]
|
self.tick_cache = self.tick_cache[-1000:]
|
||||||
|
# Clear old signals when tick cache is trimmed
|
||||||
|
self._clear_old_signals_for_tick_range()
|
||||||
|
|
||||||
if 'ohlcv' in data_packet:
|
if 'ohlcv' in data_packet:
|
||||||
# Update multi-timeframe data for both ETH and BTC (BTC for reference)
|
# Update multi-timeframe data for both ETH and BTC (BTC for reference)
|
||||||
|
@ -51,17 +51,18 @@ class DashboardLayoutManager:
|
|||||||
return html.Div([
|
return html.Div([
|
||||||
self._create_metrics_and_signals_row(),
|
self._create_metrics_and_signals_row(),
|
||||||
self._create_charts_row(),
|
self._create_charts_row(),
|
||||||
self._create_analytics_row(),
|
self._create_analytics_and_performance_row()
|
||||||
self._create_performance_row()
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def _create_metrics_and_signals_row(self):
|
def _create_metrics_and_signals_row(self):
|
||||||
"""Create the top row with key metrics and recent signals"""
|
"""Create the top row with key metrics, recent signals, and session controls"""
|
||||||
return html.Div([
|
return html.Div([
|
||||||
# Left side - Key metrics (compact cards)
|
# Left side - Key metrics (compact cards)
|
||||||
self._create_metrics_grid(),
|
self._create_metrics_grid(),
|
||||||
# Right side - Recent Signals & Model Training
|
# Middle - Recent Signals
|
||||||
self._create_signals_and_training_panels()
|
self._create_signals_panel(),
|
||||||
|
# Right side - Session Controls
|
||||||
|
self._create_session_controls_panel()
|
||||||
], className="d-flex mb-3")
|
], className="d-flex mb-3")
|
||||||
|
|
||||||
def _create_metrics_grid(self):
|
def _create_metrics_grid(self):
|
||||||
@ -96,10 +97,9 @@ class DashboardLayoutManager:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_signals_and_training_panels(self):
|
def _create_signals_panel(self):
|
||||||
"""Create the signals and training panels"""
|
"""Create the signals panel"""
|
||||||
return html.Div([
|
return html.Div([
|
||||||
# Recent Trading Signals Column (50%)
|
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H6([
|
html.H6([
|
||||||
@ -108,19 +108,39 @@ class DashboardLayoutManager:
|
|||||||
], className="card-title mb-2"),
|
], className="card-title mb-2"),
|
||||||
html.Div(id="recent-decisions", style={"height": "160px", "overflowY": "auto"})
|
html.Div(id="recent-decisions", style={"height": "160px", "overflowY": "auto"})
|
||||||
], className="card-body p-2")
|
], className="card-body p-2")
|
||||||
], className="card", style={"width": "48%"}),
|
], className="card")
|
||||||
|
], style={"width": "35%", "marginLeft": "2%"})
|
||||||
# Model Training + COB Buckets Column (50%)
|
|
||||||
|
def _create_session_controls_panel(self):
|
||||||
|
"""Create the session controls panel"""
|
||||||
|
return html.Div([
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H6([
|
html.H6([
|
||||||
html.I(className="fas fa-brain me-2"),
|
html.I(className="fas fa-cog me-2"),
|
||||||
"Training Progress & COB $1 Buckets"
|
"Session Controls"
|
||||||
], className="card-title mb-2"),
|
], className="card-title mb-2"),
|
||||||
html.Div(id="training-metrics", style={"height": "160px", "overflowY": "auto"})
|
html.Button([
|
||||||
|
html.I(className="fas fa-trash me-1"),
|
||||||
|
"Clear Session"
|
||||||
|
], id="clear-session-btn", className="btn btn-warning btn-sm w-100"),
|
||||||
|
html.Hr(className="my-2"),
|
||||||
|
html.Small("System Status", className="text-muted d-block mb-1"),
|
||||||
|
html.Div([
|
||||||
|
html.Span("Trading: ", className="small"),
|
||||||
|
html.Span("SIMULATION", className="badge bg-info small")
|
||||||
|
], className="mb-1"),
|
||||||
|
html.Div([
|
||||||
|
html.Span("Data: ", className="small"),
|
||||||
|
html.Span("Active", className="badge bg-success small")
|
||||||
|
], className="mb-1"),
|
||||||
|
html.Div([
|
||||||
|
html.Span("WebSocket: ", className="small"),
|
||||||
|
html.Span("Connected", className="badge bg-success small")
|
||||||
|
])
|
||||||
], className="card-body p-2")
|
], className="card-body p-2")
|
||||||
], className="card", style={"width": "48%", "marginLeft": "4%"}),
|
], className="card")
|
||||||
], style={"width": "48%", "marginLeft": "2%", "display": "flex"})
|
], style={"width": "23%", "marginLeft": "2%"})
|
||||||
|
|
||||||
def _create_charts_row(self):
|
def _create_charts_row(self):
|
||||||
"""Create the charts row with price chart and manual trading buttons"""
|
"""Create the charts row with price chart and manual trading buttons"""
|
||||||
@ -154,68 +174,58 @@ class DashboardLayoutManager:
|
|||||||
], className="card")
|
], className="card")
|
||||||
])
|
])
|
||||||
|
|
||||||
def _create_analytics_row(self):
|
def _create_analytics_and_performance_row(self):
|
||||||
"""Create the analytics row with COB data and system status"""
|
"""Create the combined analytics and performance row with COB data, trades, and training progress"""
|
||||||
return html.Div([
|
return html.Div([
|
||||||
# COB Status
|
# Left side - COB panels and trades (68% width)
|
||||||
html.Div([
|
html.Div([
|
||||||
|
# Top section - COB panels
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H6([
|
# ETH/USDT COB
|
||||||
html.I(className="fas fa-server me-2"),
|
html.Div([
|
||||||
"System Status"
|
html.Div([
|
||||||
], className="card-title mb-2"),
|
html.H6([
|
||||||
html.Div(id="cob-status-content")
|
html.I(className="fab fa-ethereum me-2"),
|
||||||
], className="card-body p-2")
|
"ETH/USDT COB"
|
||||||
], className="card", style={"width": "32%"}),
|
], className="card-title mb-2"),
|
||||||
|
html.Div(id="eth-cob-content")
|
||||||
|
], className="card-body p-2")
|
||||||
|
], className="card", style={"width": "48%"}),
|
||||||
|
|
||||||
|
# BTC/USDT COB
|
||||||
|
html.Div([
|
||||||
|
html.Div([
|
||||||
|
html.H6([
|
||||||
|
html.I(className="fab fa-bitcoin me-2"),
|
||||||
|
"BTC/USDT COB"
|
||||||
|
], className="card-title mb-2"),
|
||||||
|
html.Div(id="btc-cob-content")
|
||||||
|
], className="card-body p-2")
|
||||||
|
], className="card", style={"width": "48%", "marginLeft": "4%"})
|
||||||
|
], className="d-flex mb-3"),
|
||||||
|
|
||||||
|
# Bottom section - Closed Trades
|
||||||
|
html.Div([
|
||||||
|
html.Div([
|
||||||
|
html.H6([
|
||||||
|
html.I(className="fas fa-history me-2"),
|
||||||
|
"Closed Trades"
|
||||||
|
], className="card-title mb-2"),
|
||||||
|
html.Div(id="closed-trades-table", style={"height": "250px", "overflowY": "auto"})
|
||||||
|
], className="card-body p-2")
|
||||||
|
], className="card")
|
||||||
|
], style={"width": "68%"}),
|
||||||
|
|
||||||
# ETH/USDT COB
|
# Right side - Training Progress & COB $1 Buckets (30% width, spans full height)
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H6([
|
html.H6([
|
||||||
html.I(className="fab fa-ethereum me-2"),
|
html.I(className="fas fa-brain me-2"),
|
||||||
"ETH/USDT COB"
|
"Training Progress & COB $1 Buckets"
|
||||||
], className="card-title mb-2"),
|
], className="card-title mb-2"),
|
||||||
html.Div(id="eth-cob-content")
|
html.Div(id="training-metrics", style={"height": "550px", "overflowY": "auto"})
|
||||||
], className="card-body p-2")
|
], className="card-body p-2")
|
||||||
], className="card", style={"width": "32%", "marginLeft": "2%"}),
|
], className="card", style={"width": "30%", "marginLeft": "2%"})
|
||||||
|
], className="d-flex")
|
||||||
# BTC/USDT COB
|
|
||||||
html.Div([
|
|
||||||
html.Div([
|
|
||||||
html.H6([
|
|
||||||
html.I(className="fab fa-bitcoin me-2"),
|
|
||||||
"BTC/USDT COB"
|
|
||||||
], className="card-title mb-2"),
|
|
||||||
html.Div(id="btc-cob-content")
|
|
||||||
], className="card-body p-2")
|
|
||||||
], className="card", style={"width": "32%", "marginLeft": "2%"})
|
|
||||||
], className="d-flex mb-3")
|
|
||||||
|
|
||||||
def _create_performance_row(self):
|
|
||||||
"""Create the performance row with closed trades and session controls"""
|
|
||||||
return html.Div([
|
|
||||||
# Closed Trades Table
|
|
||||||
html.Div([
|
|
||||||
html.Div([
|
|
||||||
html.H6([
|
|
||||||
html.I(className="fas fa-history me-2"),
|
|
||||||
"Closed Trades"
|
|
||||||
], className="card-title mb-2"),
|
|
||||||
html.Div(id="closed-trades-table", style={"height": "200px", "overflowY": "auto"})
|
|
||||||
], className="card-body p-2")
|
|
||||||
], className="card", style={"width": "70%"}),
|
|
||||||
|
|
||||||
# Session Controls
|
|
||||||
html.Div([
|
|
||||||
html.Div([
|
|
||||||
html.H6([
|
|
||||||
html.I(className="fas fa-cog me-2"),
|
|
||||||
"Session Controls"
|
|
||||||
], className="card-title mb-2"),
|
|
||||||
html.Button([
|
|
||||||
html.I(className="fas fa-trash me-1"),
|
|
||||||
"Clear Session"
|
|
||||||
], id="clear-session-btn", className="btn btn-warning btn-sm w-100")
|
|
||||||
], className="card-body p-2")
|
|
||||||
], className="card", style={"width": "28%", "marginLeft": "2%"})
|
|
||||||
], className="d-flex")
|
|
Reference in New Issue
Block a user