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

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