position history with fees

This commit is contained in:
Dobromir Popov
2025-05-28 11:17:41 +03:00
parent f8681447e3
commit dd86d21854
11 changed files with 880 additions and 58 deletions

View File

@ -152,8 +152,8 @@ class TradingDashboard:
# Check if trading is enabled and not in dry run mode
if not self.trading_executor.trading_enabled:
logger.warning("MEXC: Trading not enabled - using default balance")
elif self.trading_executor.dry_run:
logger.warning("MEXC: Dry run mode enabled - using default balance")
elif self.trading_executor.simulation_mode:
logger.warning(f"MEXC: {self.trading_executor.trading_mode.upper()} mode enabled - using default balance")
else:
# Get USDT balance from MEXC
balance_info = self.trading_executor.get_account_balance()
@ -188,7 +188,7 @@ class TradingDashboard:
html.I(className="fas fa-chart-line me-2"),
"Live Trading Dashboard"
], className="text-white mb-1"),
html.P(f"Ultra-Fast Updates • Portfolio: ${self.starting_balance:,.0f}{'MEXC Live' if (self.trading_executor and self.trading_executor.trading_enabled and not self.trading_executor.dry_run) else 'Demo Mode'}",
html.P(f"Ultra-Fast Updates • Portfolio: ${self.starting_balance:,.0f}{'MEXC Live' if (self.trading_executor and self.trading_executor.trading_enabled and not self.trading_executor.simulation_mode) else 'Demo Mode'}",
className="text-light mb-0 opacity-75 small")
], className="bg-dark p-2 mb-2"),
@ -219,6 +219,13 @@ class TradingDashboard:
], className="card-body text-center p-2")
], className="card bg-light", style={"height": "60px"}),
html.Div([
html.Div([
html.H5(id="total-fees", className="text-warning mb-0 small"),
html.P("Total Fees", className="text-muted mb-0 tiny")
], className="card-body text-center p-2")
], className="card bg-light", style={"height": "60px"}),
html.Div([
html.Div([
html.H5(id="current-position", className="text-info mb-0 small"),
@ -246,7 +253,7 @@ class TradingDashboard:
html.P("MEXC API", className="text-muted mb-0 tiny")
], className="card-body text-center p-2")
], className="card bg-light", style={"height": "60px"}),
], style={"display": "grid", "gridTemplateColumns": "repeat(3, 1fr)", "gap": "8px", "width": "50%"}),
], style={"display": "grid", "gridTemplateColumns": "repeat(4, 1fr)", "gap": "8px", "width": "60%"}),
# Right side - Recent Signals & Executions
html.Div([
@ -350,6 +357,7 @@ class TradingDashboard:
Output('current-price', 'children'),
Output('session-pnl', 'children'),
Output('session-pnl', 'className'),
Output('total-fees', 'children'),
Output('current-position', 'children'),
Output('current-position', 'className'),
Output('trade-count', 'children'),
@ -511,6 +519,9 @@ 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"
# Total fees formatting
fees_text = f"${self.total_fees:.2f}"
# Position info with real-time unrealized PnL and proper color coding
if self.current_position:
pos_side = self.current_position['side']
@ -540,8 +551,8 @@ class TradingDashboard:
# MEXC status with detailed information
if self.trading_executor and self.trading_executor.trading_enabled:
if self.trading_executor.dry_run:
mexc_status = "DRY RUN"
if self.trading_executor.simulation_mode:
mexc_status = f"{self.trading_executor.trading_mode.upper()} MODE"
else:
mexc_status = "LIVE"
else:
@ -597,7 +608,7 @@ class TradingDashboard:
closed_trades_table = [html.P("Closed trades data unavailable", className="text-muted")]
return (
price_text, pnl_text, pnl_class, position_text, position_class, trade_count_text, portfolio_text, mexc_status,
price_text, pnl_text, pnl_class, fees_text, position_text, position_class, trade_count_text, portfolio_text, mexc_status,
price_chart, training_metrics, decisions_list, session_perf, closed_trades_table,
system_status['icon_class'], system_status['title'], system_status['details']
)
@ -608,7 +619,7 @@ class TradingDashboard:
empty_fig = self._create_empty_chart("Error", "Dashboard error - check logs")
return (
"Error", "$0.00", "text-muted mb-0 small", "None", "text-muted", "0", "$10,000.00", "OFFLINE",
"Error", "$0.00", "text-muted mb-0 small", "$0.00", "None", "text-muted", "0", "$10,000.00", "OFFLINE",
empty_fig,
[html.P("Error loading training metrics", className="text-danger")],
[html.P("Error loading decisions", className="text-danger")],
@ -1414,6 +1425,7 @@ class TradingDashboard:
decision['mexc_executed'] = mexc_success
# Calculate position size based on confidence and configuration
current_price = decision.get('price', 0)
if current_price and current_price > 0:
# Get position sizing from trading executor configuration
if self.trading_executor:
@ -1756,9 +1768,12 @@ class TradingDashboard:
if not self.closed_trades:
return [html.P("No closed trades yet", className="text-muted text-center")]
# Create table rows for recent closed trades
# Create table rows for recent closed trades (newest first)
table_rows = []
for trade in self.closed_trades[-20:]: # Show last 20 trades
recent_trades = self.closed_trades[-20:] # Get last 20 trades
recent_trades.reverse() # Newest first
for trade in recent_trades:
# Determine row color based on P&L
row_class = "table-success" if trade['net_pnl'] >= 0 else "table-danger"
@ -1768,12 +1783,18 @@ class TradingDashboard:
# Format side color
side_color = "text-success" if trade['side'] == 'LONG' else "text-danger"
# Format position size
position_size = trade.get('size', 0)
size_display = f"{position_size:.4f}" if position_size < 1 else f"{position_size:.2f}"
table_rows.append(
html.Tr([
html.Td(f"#{trade['trade_id']}", className="small"),
html.Td(trade['side'], className=f"small fw-bold {side_color}"),
html.Td(size_display, className="small text-info"),
html.Td(f"${trade['entry_price']:.2f}", className="small"),
html.Td(f"${trade['exit_price']:.2f}", className="small"),
html.Td(f"${trade.get('fees', 0):.2f}", className="small text-warning"),
html.Td(f"${trade['net_pnl']:.2f}", className="small fw-bold"),
html.Td(duration_str, className="small"),
html.Td("" if trade.get('mexc_executed', False) else "SIM",
@ -1787,8 +1808,10 @@ class TradingDashboard:
html.Tr([
html.Th("ID", className="small"),
html.Th("Side", className="small"),
html.Th("Size", className="small"),
html.Th("Entry", className="small"),
html.Th("Exit", className="small"),
html.Th("Fees", className="small"),
html.Th("P&L", className="small"),
html.Th("Duration", className="small"),
html.Th("MEXC", className="small")
@ -1801,12 +1824,14 @@ class TradingDashboard:
total_trades = len(self.closed_trades)
winning_trades = len([t for t in self.closed_trades if t['net_pnl'] > 0])
total_pnl = sum(t['net_pnl'] for t in self.closed_trades)
total_fees_closed = sum(t.get('fees', 0) for t in self.closed_trades)
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
summary = html.Div([
html.Small([
html.Strong(f"Total: {total_trades} | "),
html.Span(f"Win Rate: {win_rate:.1f}% | ", className="text-info"),
html.Span(f"Fees: ${total_fees_closed:.2f} | ", className="text-warning"),
html.Span(f"Total P&L: ${total_pnl:.2f}",
className="text-success" if total_pnl >= 0 else "text-danger")
], className="d-block mb-2")

View File

@ -49,18 +49,23 @@ class TradingSession:
self.starting_balance = 100.0 # $100 USD starting balance
self.current_balance = self.starting_balance
self.total_pnl = 0.0
self.total_fees = 0.0 # Track total fees paid (opening + closing)
self.total_trades = 0
self.winning_trades = 0
self.losing_trades = 0
self.positions = {} # symbol -> {'size': float, 'entry_price': float, 'side': str}
self.positions = {} # symbol -> {'size': float, 'entry_price': float, 'side': str, 'fees': float}
self.trade_history = []
self.last_action = None
self.trading_executor = trading_executor
# Fee configuration - MEXC spot trading fees
self.fee_rate = 0.001 # 0.1% trading fee (typical for MEXC spot)
logger.info(f"NEW TRADING SESSION STARTED WITH MEXC INTEGRATION")
logger.info(f"Session ID: {self.session_id}")
logger.info(f"Starting Balance: ${self.starting_balance:.2f}")
logger.info(f"MEXC Trading: {'ENABLED' if trading_executor and trading_executor.trading_enabled else 'DISABLED'}")
logger.info(f"Trading Fee Rate: {self.fee_rate*100:.1f}%")
logger.info(f"Start Time: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
def execute_trade(self, action: TradingAction, current_price: float):
@ -105,14 +110,20 @@ class TradingSession:
if action.action == 'BUY':
# Close any existing short position
if symbol in self.positions and self.positions[symbol]['side'] == 'SHORT':
self._close_position(symbol, current_price, 'BUY')
pnl = self._close_position(symbol, current_price, 'BUY')
trade_info['pnl'] = pnl
# Open new long position with opening fee
opening_fee = current_price * position_size * self.fee_rate
self.total_fees += opening_fee
# Open new long position
self.positions[symbol] = {
'size': position_size,
'entry_price': current_price,
'side': 'LONG'
'side': 'LONG',
'fees': opening_fee # Track opening fee
}
trade_info['opening_fee'] = opening_fee
trade_info['pnl'] = 0 # No immediate P&L on entry
elif action.action == 'SELL':
@ -121,12 +132,17 @@ class TradingSession:
pnl = self._close_position(symbol, current_price, 'SELL')
trade_info['pnl'] = pnl
else:
# Open new short position
# Open new short position with opening fee
opening_fee = current_price * position_size * self.fee_rate
self.total_fees += opening_fee
self.positions[symbol] = {
'size': position_size,
'entry_price': current_price,
'side': 'SHORT'
'side': 'SHORT',
'fees': opening_fee # Track opening fee
}
trade_info['opening_fee'] = opening_fee
trade_info['pnl'] = 0
elif action.action == 'HOLD':
@ -154,7 +170,7 @@ class TradingSession:
return None
def _close_position(self, symbol: str, exit_price: float, close_action: str) -> float:
"""Close an existing position and calculate P&L"""
"""Close an existing position and calculate P&L with fees"""
if symbol not in self.positions:
return 0.0
@ -162,18 +178,27 @@ class TradingSession:
entry_price = position['entry_price']
size = position['size']
side = position['side']
opening_fee = position.get('fees', 0.0)
# Calculate P&L
# Calculate closing fee
closing_fee = exit_price * size * self.fee_rate
total_fees = opening_fee + closing_fee
self.total_fees += closing_fee
# Calculate gross P&L
if side == 'LONG':
pnl = (exit_price - entry_price) * size
gross_pnl = (exit_price - entry_price) * size
else: # SHORT
pnl = (entry_price - exit_price) * size
gross_pnl = (entry_price - exit_price) * size
# Calculate net P&L (after fees)
net_pnl = gross_pnl - total_fees
# Update session P&L
self.total_pnl += pnl
self.total_pnl += net_pnl
# Track win/loss
if pnl > 0:
# Track win/loss based on net P&L
if net_pnl > 0:
self.winning_trades += 1
else:
self.losing_trades += 1
@ -183,9 +208,10 @@ class TradingSession:
logger.info(f"CHART: POSITION CLOSED: {side} {symbol}")
logger.info(f"CHART: Entry: ${entry_price:.2f} | Exit: ${exit_price:.2f}")
logger.info(f"MONEY: Trade P&L: ${pnl:+.2f}")
logger.info(f"FEES: Opening: ${opening_fee:.4f} | Closing: ${closing_fee:.4f} | Total: ${total_fees:.4f}")
logger.info(f"MONEY: Gross P&L: ${gross_pnl:+.2f} | Net P&L: ${net_pnl:+.2f}")
return pnl
return net_pnl
def get_win_rate(self) -> float:
"""Calculate current win rate"""
@ -203,6 +229,7 @@ class TradingSession:
'starting_balance': self.starting_balance,
'current_balance': self.current_balance,
'total_pnl': self.total_pnl,
'total_fees': self.total_fees,
'total_trades': self.total_trades,
'winning_trades': self.winning_trades,
'losing_trades': self.losing_trades,
@ -605,22 +632,27 @@ class RealTimeScalpingDashboard:
html.Div([
html.H3(id="live-pnl", className="text-success"),
html.P("Session P&L", className="text-white")
], className="col-md-3 text-center"),
], className="col-md-2 text-center"),
html.Div([
html.H3(id="total-fees", className="text-warning"),
html.P("Total Fees", className="text-white")
], className="col-md-2 text-center"),
html.Div([
html.H3(id="win-rate", className="text-info"),
html.P("Win Rate", className="text-white")
], className="col-md-3 text-center"),
], className="col-md-2 text-center"),
html.Div([
html.H3(id="total-trades", className="text-primary"),
html.P("Total Trades", className="text-white")
], className="col-md-3 text-center"),
], className="col-md-2 text-center"),
html.Div([
html.H3(id="last-action", className="text-warning"),
html.P("Last Action", className="text-white")
], className="col-md-3 text-center")
], className="col-md-4 text-center")
], className="col-md-4"),
# Middle - Price displays (2 columns, 2/12 width)
@ -732,6 +764,7 @@ class RealTimeScalpingDashboard:
Output('session-duration', 'children'),
Output('open-positions', 'children'),
Output('live-pnl', 'children'),
Output('total-fees', 'children'),
Output('win-rate', 'children'),
Output('total-trades', 'children'),
Output('last-action', 'children'),
@ -823,8 +856,8 @@ class RealTimeScalpingDashboard:
# MEXC status
if dashboard_instance.trading_executor and dashboard_instance.trading_executor.trading_enabled:
mexc_status = "LIVE"
elif dashboard_instance.trading_executor and dashboard_instance.trading_executor.dry_run:
mexc_status = "DRY RUN"
elif dashboard_instance.trading_executor and dashboard_instance.trading_executor.simulation_mode:
mexc_status = f"{dashboard_instance.trading_executor.trading_mode.upper()} MODE"
else:
mexc_status = "OFFLINE"
@ -2579,4 +2612,4 @@ def create_scalping_dashboard(data_provider=None, orchestrator=None, trading_exe
return RealTimeScalpingDashboard(data_provider, orchestrator, trading_executor)
# For backward compatibility
ScalpingDashboard = RealTimeScalpingDashboard
ScalpingDashboard = RealTimeScalpingDashboard