try to have sell actions

This commit is contained in:
Dobromir Popov
2025-06-25 17:02:21 +03:00
parent 2e084f03b7
commit 249fdace73
3 changed files with 324 additions and 106 deletions

View File

@ -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]

View File

@ -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)

View File

@ -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")