This commit is contained in:
Dobromir Popov
2025-05-26 23:04:52 +03:00
parent 374da1b8ac
commit 392dbb4b61
7 changed files with 894 additions and 17 deletions

View File

@ -358,16 +358,26 @@ class TradingDashboard:
pnl_text = f"${total_session_pnl:.2f}"
pnl_class = "text-success mb-0 small" if total_session_pnl >= 0 else "text-danger mb-0 small"
# Position info with real-time unrealized PnL
# Position info with real-time unrealized PnL and color coding
if self.current_position:
pos_side = self.current_position['side']
pos_size = self.current_position['size']
pos_price = self.current_position['price']
unrealized_pnl = self._calculate_unrealized_pnl(current_price) if current_price else 0.0
pnl_color = "text-success" if unrealized_pnl >= 0 else "text-danger"
position_text = f"{pos_side} {pos_size} @ ${pos_price:.2f} | P&L: ${unrealized_pnl:.2f}"
# Color code the position based on side and P&L (no Unicode for Windows compatibility)
if pos_side == 'LONG':
side_icon = "[LONG]" # ASCII indicator for long
side_color = "success" if unrealized_pnl >= 0 else "warning"
else: # SHORT
side_icon = "[SHORT]" # ASCII indicator for short
side_color = "danger" if unrealized_pnl >= 0 else "info"
position_text = f"{side_icon} {pos_size} @ ${pos_price:.2f} | P&L: ${unrealized_pnl:.2f}"
position_class = f"text-{side_color} fw-bold"
else:
position_text = "None"
position_text = "No Position"
position_class = "text-muted"
# Trade count
trade_count_text = f"{len(self.session_trades)}"
@ -906,6 +916,26 @@ class TradingDashboard:
confidence_pct = f"{confidence*100:.1f}%" if confidence else "N/A"
# Check if this is a trade with PnL information
pnl_info = ""
if isinstance(decision, dict) and 'pnl' in decision:
pnl = decision['pnl']
pnl_color = "text-success" if pnl >= 0 else "text-danger"
pnl_info = html.Span([
" • PnL: ",
html.Strong(f"${pnl:.2f}", className=pnl_color)
])
# Check for position action to show entry/exit info
position_info = ""
if isinstance(decision, dict) and 'position_action' in decision:
pos_action = decision['position_action']
if 'CLOSE' in pos_action and 'entry_price' in decision:
entry_price = decision['entry_price']
position_info = html.Small([
f" (Entry: ${entry_price:.2f})"
], className="text-muted")
decisions_html.append(
html.Div([
html.Div([
@ -913,11 +943,13 @@ class TradingDashboard:
html.Strong(action, className=action_class),
html.Span(f" {symbol} ", className="text-muted"),
html.Small(f"@${price:.2f}", className="text-muted"),
position_info,
html.Span(className=f"{badge_class} ms-2", children=badge_text, style={"fontSize": "0.7em"})
], className="d-flex align-items-center"),
html.Small([
html.Span(f"Confidence: {confidence_pct}", className="text-info"),
html.Span(time_str, className="text-muted")
html.Span(time_str, className="text-muted"),
pnl_info
])
], className="border-bottom pb-2 mb-2")
)
@ -1139,7 +1171,7 @@ class TradingDashboard:
return None
def _process_trading_decision(self, decision: Dict) -> None:
"""Process a trading decision and update PnL tracking"""
"""Process a trading decision and update PnL tracking with position flipping"""
try:
if not decision:
return
@ -1168,6 +1200,49 @@ class TradingDashboard:
logger.info(f"[TRADE] OPENED LONG: {decision['size']} @ ${decision['price']:.2f}")
elif self.current_position['side'] == 'SHORT':
# Close short position and flip to long
entry_price = self.current_position['price']
exit_price = decision['price']
size = self.current_position['size']
# Calculate PnL for closing short
gross_pnl = (entry_price - exit_price) * size # Short PnL calculation
fee = exit_price * size * fee_rate
net_pnl = gross_pnl - fee - self.current_position['fees']
self.total_realized_pnl += net_pnl
self.total_fees += fee
# Record the close trade
close_record = decision.copy()
close_record['position_action'] = 'CLOSE_SHORT'
close_record['entry_price'] = entry_price
close_record['pnl'] = net_pnl
close_record['fees'] = fee
self.session_trades.append(close_record)
logger.info(f"[TRADE] CLOSED SHORT: {size} @ ${exit_price:.2f} | PnL: ${net_pnl:.2f} | FLIPPING TO LONG")
# Open new long position
new_fee = decision['price'] * decision['size'] * fee_rate
self.current_position = {
'side': 'LONG',
'price': decision['price'],
'size': decision['size'],
'timestamp': current_time,
'fees': new_fee
}
self.total_fees += new_fee
# Record the new long position
open_record = decision.copy()
open_record['position_action'] = 'OPEN_LONG'
open_record['fees'] = new_fee
self.session_trades.append(open_record)
logger.info(f"[TRADE] OPENED LONG: {decision['size']} @ ${decision['price']:.2f}")
elif decision['action'] == 'SELL':
if self.current_position and self.current_position['side'] == 'LONG':
# Close long position
@ -1175,28 +1250,29 @@ class TradingDashboard:
exit_price = decision['price']
size = self.current_position['size']
# Calculate PnL
gross_pnl = (exit_price - entry_price) * size
# Calculate PnL for closing long
gross_pnl = (exit_price - entry_price) * size # Long PnL calculation
fee = exit_price * size * fee_rate
net_pnl = gross_pnl - fee - self.current_position['fees']
self.total_realized_pnl += net_pnl
self.total_fees += fee
trade_record = decision.copy()
trade_record['position_action'] = 'CLOSE_LONG'
trade_record['entry_price'] = entry_price
trade_record['pnl'] = net_pnl
trade_record['fees'] = fee
self.session_trades.append(trade_record)
# Record the close trade
close_record = decision.copy()
close_record['position_action'] = 'CLOSE_LONG'
close_record['entry_price'] = entry_price
close_record['pnl'] = net_pnl
close_record['fees'] = fee
self.session_trades.append(close_record)
logger.info(f"[TRADE] CLOSED LONG: {size} @ ${exit_price:.2f} | PnL: ${net_pnl:.2f}")
# Clear position
# Clear position (or could flip to short here if desired)
self.current_position = None
elif self.current_position is None:
# Open short position (for demo)
# Open short position
fee = decision['price'] * decision['size'] * fee_rate
self.current_position = {
'side': 'SHORT',
@ -1213,6 +1289,10 @@ class TradingDashboard:
self.session_trades.append(trade_record)
logger.info(f"[TRADE] OPENED SHORT: {decision['size']} @ ${decision['price']:.2f}")
elif self.current_position['side'] == 'LONG':
# This case is already handled above, but adding for completeness
pass
# Add to recent decisions
self.recent_decisions.append(decision)