try to have sell actions
This commit is contained in:
@ -318,8 +318,9 @@ class TradingExecutor:
|
||||
"""Execute a sell order"""
|
||||
# Check if we have a position to sell
|
||||
if symbol not in self.positions:
|
||||
logger.info(f"No position to sell in {symbol}")
|
||||
return False
|
||||
logger.info(f"No position to sell in {symbol}. Opening short position")
|
||||
# TODO: Open short position
|
||||
|
||||
|
||||
position = self.positions[symbol]
|
||||
|
||||
|
@ -243,13 +243,39 @@ class CleanTradingDashboard:
|
||||
session_pnl_str = f"${self.session_pnl:.2f}"
|
||||
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"
|
||||
if self.current_position:
|
||||
side = self.current_position.get('side', 'UNKNOWN')
|
||||
size = self.current_position.get('size', 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
|
||||
initial_balance = self._get_initial_balance()
|
||||
@ -315,18 +341,13 @@ class CleanTradingDashboard:
|
||||
return html.P(f"Error: {str(e)}", className="text-danger")
|
||||
|
||||
@self.app.callback(
|
||||
[Output('cob-status-content', 'children'),
|
||||
Output('eth-cob-content', 'children'),
|
||||
[Output('eth-cob-content', 'children'),
|
||||
Output('btc-cob-content', 'children')],
|
||||
[Input('interval-component', 'n_intervals')]
|
||||
)
|
||||
def update_cob_data(n):
|
||||
"""Update COB data displays"""
|
||||
try:
|
||||
# COB Status
|
||||
cob_status = self._get_cob_status()
|
||||
status_components = self.component_manager.format_system_status(cob_status)
|
||||
|
||||
# ETH/USDT COB
|
||||
eth_cob = self._get_cob_snapshot('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_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:
|
||||
logger.error(f"Error updating COB data: {e}")
|
||||
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(
|
||||
Output('training-metrics', 'children'),
|
||||
@ -1151,8 +1172,9 @@ class CleanTradingDashboard:
|
||||
'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)
|
||||
metrics['cob_buckets'] = self._get_cob_dollar_buckets()
|
||||
# metrics['cob_buckets'] = self._get_cob_dollar_buckets()
|
||||
|
||||
return metrics
|
||||
|
||||
@ -1300,9 +1322,42 @@ class CleanTradingDashboard:
|
||||
signal['blocked'] = False
|
||||
signal['manual'] = False
|
||||
|
||||
# Execute signal if confidence is above threshold
|
||||
confidence_threshold = 0.25 # Lower threshold for testing (was too high)
|
||||
if signal.get('confidence', 0) >= confidence_threshold:
|
||||
# Smart confidence-based execution with different thresholds for opening vs closing
|
||||
confidence = signal.get('confidence', 0)
|
||||
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:
|
||||
# Attempt to execute the signal
|
||||
symbol = signal.get('symbol', 'ETH/USDT')
|
||||
@ -1314,7 +1369,7 @@ class CleanTradingDashboard:
|
||||
if result:
|
||||
signal['executed'] = True
|
||||
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
|
||||
trade_record = {
|
||||
@ -1329,6 +1384,45 @@ class CleanTradingDashboard:
|
||||
'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)
|
||||
|
||||
# Update session metrics immediately
|
||||
@ -1352,9 +1446,28 @@ class CleanTradingDashboard:
|
||||
signal['block_reason'] = str(e)
|
||||
logger.error(f"❌ EXECUTION ERROR for {signal.get('action', 'UNKNOWN')}: {e}")
|
||||
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['block_reason'] = f"Confidence {signal.get('confidence', 0):.3f} below threshold {confidence_threshold}"
|
||||
logger.debug(f"Signal confidence {signal.get('confidence', 0):.3f} below execution threshold {confidence_threshold}")
|
||||
signal['block_reason'] = f"Confidence {confidence:.3f} below threshold {required_threshold:.2f} to {operation}"
|
||||
logger.debug(f"Signal confidence {confidence:.3f} below {required_threshold:.2f} threshold to {operation}")
|
||||
|
||||
# Add to recent decisions for display
|
||||
self.recent_decisions.append(signal)
|
||||
@ -1376,22 +1489,23 @@ class CleanTradingDashboard:
|
||||
# Basic orchestrator doesn't have DQN features
|
||||
return
|
||||
|
||||
def _get_cob_dollar_buckets(self) -> List[Dict]:
|
||||
"""Get COB $1 price buckets with volume data"""
|
||||
try:
|
||||
# This would normally come from the COB integration
|
||||
# For now, return sample data structure
|
||||
sample_buckets = [
|
||||
{'price': 2000, 'total_volume': 150000, 'bid_pct': 45, 'ask_pct': 55},
|
||||
{'price': 2001, 'total_volume': 120000, 'bid_pct': 52, 'ask_pct': 48},
|
||||
{'price': 1999, 'total_volume': 98000, 'bid_pct': 38, 'ask_pct': 62},
|
||||
{'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:
|
||||
logger.debug(f"Error getting COB buckets: {e}")
|
||||
return []
|
||||
# EXAMPLE OF WHAT WE SHOULD NEVER DO!!! use only real data or report we have no data
|
||||
# def _get_cob_dollar_buckets(self) -> List[Dict]:
|
||||
# """Get COB $1 price buckets with volume data"""
|
||||
# try:
|
||||
# # This would normally come from the COB integration
|
||||
# # For now, return sample data structure
|
||||
# sample_buckets = [
|
||||
# {'price': 2000, 'total_volume': 150000, 'bid_pct': 45, 'ask_pct': 55},
|
||||
# {'price': 2001, 'total_volume': 120000, 'bid_pct': 52, 'ask_pct': 48},
|
||||
# {'price': 1999, 'total_volume': 98000, 'bid_pct': 38, 'ask_pct': 62},
|
||||
# {'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:
|
||||
# logger.debug(f"Error getting COB buckets: {e}")
|
||||
# return []
|
||||
|
||||
def _execute_manual_trade(self, action: str):
|
||||
"""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
|
||||
}
|
||||
|
||||
# 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
|
||||
self.closed_trades.append(trade_record)
|
||||
|
||||
@ -1712,11 +1856,70 @@ class CleanTradingDashboard:
|
||||
self.closed_trades = []
|
||||
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")
|
||||
|
||||
except Exception as 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):
|
||||
"""Initialize COB integration - Basic orchestrator has no COB features"""
|
||||
try:
|
||||
@ -1890,6 +2093,8 @@ class CleanTradingDashboard:
|
||||
self.tick_cache.append(tick_record)
|
||||
if len(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
|
||||
|
||||
@ -2026,6 +2231,8 @@ class CleanTradingDashboard:
|
||||
self.tick_cache.extend(data_packet['ticks'][-50:]) # Last 50 ticks
|
||||
if len(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:
|
||||
# Update multi-timeframe data for both ETH and BTC (BTC for reference)
|
||||
|
@ -51,17 +51,18 @@ class DashboardLayoutManager:
|
||||
return html.Div([
|
||||
self._create_metrics_and_signals_row(),
|
||||
self._create_charts_row(),
|
||||
self._create_analytics_row(),
|
||||
self._create_performance_row()
|
||||
self._create_analytics_and_performance_row()
|
||||
])
|
||||
|
||||
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([
|
||||
# Left side - Key metrics (compact cards)
|
||||
self._create_metrics_grid(),
|
||||
# Right side - Recent Signals & Model Training
|
||||
self._create_signals_and_training_panels()
|
||||
# Middle - Recent Signals
|
||||
self._create_signals_panel(),
|
||||
# Right side - Session Controls
|
||||
self._create_session_controls_panel()
|
||||
], className="d-flex mb-3")
|
||||
|
||||
def _create_metrics_grid(self):
|
||||
@ -96,10 +97,9 @@ class DashboardLayoutManager:
|
||||
}
|
||||
)
|
||||
|
||||
def _create_signals_and_training_panels(self):
|
||||
"""Create the signals and training panels"""
|
||||
def _create_signals_panel(self):
|
||||
"""Create the signals panel"""
|
||||
return html.Div([
|
||||
# Recent Trading Signals Column (50%)
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H6([
|
||||
@ -108,19 +108,39 @@ class DashboardLayoutManager:
|
||||
], className="card-title mb-2"),
|
||||
html.Div(id="recent-decisions", style={"height": "160px", "overflowY": "auto"})
|
||||
], 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.H6([
|
||||
html.I(className="fas fa-brain me-2"),
|
||||
"Training Progress & COB $1 Buckets"
|
||||
html.I(className="fas fa-cog me-2"),
|
||||
"Session Controls"
|
||||
], 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", style={"width": "48%", "marginLeft": "4%"}),
|
||||
], style={"width": "48%", "marginLeft": "2%", "display": "flex"})
|
||||
], className="card")
|
||||
], style={"width": "23%", "marginLeft": "2%"})
|
||||
|
||||
def _create_charts_row(self):
|
||||
"""Create the charts row with price chart and manual trading buttons"""
|
||||
@ -154,20 +174,13 @@ class DashboardLayoutManager:
|
||||
], className="card")
|
||||
])
|
||||
|
||||
def _create_analytics_row(self):
|
||||
"""Create the analytics row with COB data and system status"""
|
||||
def _create_analytics_and_performance_row(self):
|
||||
"""Create the combined analytics and performance row with COB data, trades, and training progress"""
|
||||
return html.Div([
|
||||
# COB Status
|
||||
# Left side - COB panels and trades (68% width)
|
||||
html.Div([
|
||||
# Top section - COB panels
|
||||
html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-server me-2"),
|
||||
"System Status"
|
||||
], className="card-title mb-2"),
|
||||
html.Div(id="cob-status-content")
|
||||
], className="card-body p-2")
|
||||
], className="card", style={"width": "32%"}),
|
||||
|
||||
# ETH/USDT COB
|
||||
html.Div([
|
||||
html.Div([
|
||||
@ -177,7 +190,7 @@ class DashboardLayoutManager:
|
||||
], className="card-title mb-2"),
|
||||
html.Div(id="eth-cob-content")
|
||||
], className="card-body p-2")
|
||||
], className="card", style={"width": "32%", "marginLeft": "2%"}),
|
||||
], className="card", style={"width": "48%"}),
|
||||
|
||||
# BTC/USDT COB
|
||||
html.Div([
|
||||
@ -188,34 +201,31 @@ class DashboardLayoutManager:
|
||||
], 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")
|
||||
], className="card", style={"width": "48%", "marginLeft": "4%"})
|
||||
], 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
|
||||
# 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": "200px", "overflowY": "auto"})
|
||||
html.Div(id="closed-trades-table", style={"height": "250px", "overflowY": "auto"})
|
||||
], className="card-body p-2")
|
||||
], className="card", style={"width": "70%"}),
|
||||
], className="card")
|
||||
], style={"width": "68%"}),
|
||||
|
||||
# Session Controls
|
||||
# Right side - Training Progress & COB $1 Buckets (30% width, spans full height)
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-cog me-2"),
|
||||
"Session Controls"
|
||||
html.I(className="fas fa-brain me-2"),
|
||||
"Training Progress & COB $1 Buckets"
|
||||
], 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")
|
||||
html.Div(id="training-metrics", style={"height": "550px", "overflowY": "auto"})
|
||||
], className="card-body p-2")
|
||||
], className="card", style={"width": "28%", "marginLeft": "2%"})
|
||||
], className="card", style={"width": "30%", "marginLeft": "2%"})
|
||||
], className="d-flex")
|
||||
|
||||
|
Reference in New Issue
Block a user