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

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