better fees calc
This commit is contained in:
230
web/dashboard.py
230
web/dashboard.py
@ -649,11 +649,17 @@ class TradingDashboard:
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def clear_trade_history(n_clicks):
|
||||
"""Clear the closed trades history"""
|
||||
"""Clear trade history and reset session stats"""
|
||||
if n_clicks and n_clicks > 0:
|
||||
self.clear_closed_trades_history()
|
||||
return [html.P("Trade history cleared", className="text-muted text-center")]
|
||||
return self._create_closed_trades_table()
|
||||
try:
|
||||
# Clear both closed trades and session stats (they're the same now)
|
||||
self.clear_closed_trades_history()
|
||||
logger.info("DASHBOARD: Trade history and session stats cleared by user")
|
||||
return [html.P("Trade history cleared", className="text-success text-center")]
|
||||
except Exception as e:
|
||||
logger.error(f"Error clearing trade history: {e}")
|
||||
return [html.P(f"Error clearing history: {str(e)}", className="text-danger text-center")]
|
||||
return dash.no_update
|
||||
|
||||
def _simulate_price_update(self, symbol: str, base_price: float) -> float:
|
||||
"""
|
||||
@ -1407,14 +1413,37 @@ class TradingDashboard:
|
||||
return None
|
||||
|
||||
def _process_trading_decision(self, decision: Dict) -> None:
|
||||
"""Process a trading decision and update PnL tracking with position flipping"""
|
||||
"""Process a trading decision and update PnL tracking with enhanced fee calculation"""
|
||||
try:
|
||||
if not decision:
|
||||
return
|
||||
|
||||
current_time = datetime.now(timezone.utc) # Use UTC for consistency
|
||||
fee_rate = 0.001 # 0.1% trading fee
|
||||
fee_rate = 0.0 # 0% PROMO FEE (Current, but temporary)
|
||||
|
||||
# Get fee structure from config (fallback to hardcoded values)
|
||||
try:
|
||||
from core.config import get_config
|
||||
config = get_config()
|
||||
trading_fees = config.get('trading', {}).get('trading_fees', {})
|
||||
maker_fee_rate = trading_fees.get('maker', 0.0000) # 0.00% maker
|
||||
taker_fee_rate = trading_fees.get('taker', 0.0005) # 0.05% taker
|
||||
default_fee_rate = trading_fees.get('default', 0.0005) # 0.05% default
|
||||
except:
|
||||
# Fallback to hardcoded asymmetrical fees
|
||||
maker_fee_rate = 0.0000 # 0.00% maker fee
|
||||
taker_fee_rate = 0.0005 # 0.05% taker fee
|
||||
default_fee_rate = 0.0005 # 0.05% default
|
||||
|
||||
# For simulation, assume most trades are taker orders (market orders)
|
||||
# In real trading, this would be determined by order type
|
||||
fee_rate = taker_fee_rate # Default to taker fee
|
||||
fee_type = 'taker' # Default to taker
|
||||
|
||||
# If using limit orders that get filled (maker), use maker fee
|
||||
# This could be enhanced based on actual order execution data
|
||||
if decision.get('order_type') == 'limit' and decision.get('filled_as_maker', False):
|
||||
fee_rate = maker_fee_rate
|
||||
fee_type = 'maker'
|
||||
|
||||
# Execute trade through MEXC if available
|
||||
mexc_success = False
|
||||
@ -1479,6 +1508,8 @@ class TradingDashboard:
|
||||
close_record['entry_price'] = entry_price
|
||||
close_record['pnl'] = net_pnl
|
||||
close_record['fees'] = fee
|
||||
close_record['fee_type'] = fee_type
|
||||
close_record['fee_rate'] = fee_rate
|
||||
close_record['size'] = size # Use original position size for close
|
||||
self.session_trades.append(close_record)
|
||||
|
||||
@ -1493,6 +1524,8 @@ class TradingDashboard:
|
||||
'size': size,
|
||||
'gross_pnl': gross_pnl,
|
||||
'fees': fee + self.current_position['fees'],
|
||||
'fee_type': fee_type,
|
||||
'fee_rate': fee_rate,
|
||||
'net_pnl': net_pnl,
|
||||
'duration': current_time - entry_time,
|
||||
'symbol': decision.get('symbol', 'ETH/USDT'),
|
||||
@ -1527,6 +1560,8 @@ class TradingDashboard:
|
||||
trade_record = decision.copy()
|
||||
trade_record['position_action'] = 'OPEN_LONG'
|
||||
trade_record['fees'] = fee
|
||||
trade_record['fee_type'] = fee_type
|
||||
trade_record['fee_rate'] = fee_rate
|
||||
self.session_trades.append(trade_record)
|
||||
|
||||
logger.info(f"[TRADE] OPENED LONG: {decision['size']:.6f} (${decision.get('usd_size', 0.1):.2f}) @ ${decision['price']:.2f} (confidence: {decision['confidence']:.1%})")
|
||||
@ -1556,6 +1591,8 @@ class TradingDashboard:
|
||||
close_record['entry_price'] = entry_price
|
||||
close_record['pnl'] = net_pnl
|
||||
close_record['fees'] = fee
|
||||
close_record['fee_type'] = fee_type
|
||||
close_record['fee_rate'] = fee_rate
|
||||
self.session_trades.append(close_record)
|
||||
|
||||
# Add to closed trades accounting list
|
||||
@ -1569,6 +1606,8 @@ class TradingDashboard:
|
||||
'size': size,
|
||||
'gross_pnl': gross_pnl,
|
||||
'fees': fee + self.current_position['fees'],
|
||||
'fee_type': fee_type,
|
||||
'fee_rate': fee_rate,
|
||||
'net_pnl': net_pnl,
|
||||
'duration': current_time - entry_time,
|
||||
'symbol': decision.get('symbol', 'ETH/USDT'),
|
||||
@ -1610,6 +1649,8 @@ class TradingDashboard:
|
||||
close_record['entry_price'] = entry_price
|
||||
close_record['pnl'] = net_pnl
|
||||
close_record['fees'] = fee
|
||||
close_record['fee_type'] = fee_type
|
||||
close_record['fee_rate'] = fee_rate
|
||||
close_record['size'] = size # Use original position size for close
|
||||
self.session_trades.append(close_record)
|
||||
|
||||
@ -1624,6 +1665,8 @@ class TradingDashboard:
|
||||
'size': size,
|
||||
'gross_pnl': gross_pnl,
|
||||
'fees': fee + self.current_position['fees'],
|
||||
'fee_type': fee_type,
|
||||
'fee_rate': fee_rate,
|
||||
'net_pnl': net_pnl,
|
||||
'duration': current_time - entry_time,
|
||||
'symbol': decision.get('symbol', 'ETH/USDT'),
|
||||
@ -1655,6 +1698,8 @@ class TradingDashboard:
|
||||
trade_record = decision.copy()
|
||||
trade_record['position_action'] = 'OPEN_SHORT'
|
||||
trade_record['fees'] = fee
|
||||
trade_record['fee_type'] = fee_type
|
||||
trade_record['fee_rate'] = fee_rate
|
||||
self.session_trades.append(trade_record)
|
||||
|
||||
logger.info(f"[TRADE] OPENED SHORT: {decision['size']:.6f} (${decision.get('usd_size', 0.1):.2f}) @ ${decision['price']:.2f} (confidence: {decision['confidence']:.1%})")
|
||||
@ -1781,7 +1826,7 @@ class TradingDashboard:
|
||||
logger.info("[ORCHESTRATOR] Trading loop started in background")
|
||||
|
||||
def _create_closed_trades_table(self) -> List:
|
||||
"""Create closed trades history table with persistence"""
|
||||
"""Create simplified closed trades history table focusing on total fees per closed position"""
|
||||
try:
|
||||
if not self.closed_trades:
|
||||
return [html.P("No closed trades yet", className="text-muted text-center")]
|
||||
@ -1805,6 +1850,9 @@ class TradingDashboard:
|
||||
position_size = trade.get('size', 0)
|
||||
size_display = f"{position_size:.4f}" if position_size < 1 else f"{position_size:.2f}"
|
||||
|
||||
# Simple total fees display
|
||||
total_fees = trade.get('fees', 0)
|
||||
|
||||
table_rows.append(
|
||||
html.Tr([
|
||||
html.Td(f"#{trade['trade_id']}", className="small"),
|
||||
@ -1812,7 +1860,7 @@ class TradingDashboard:
|
||||
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"${total_fees:.3f}", 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",
|
||||
@ -1820,7 +1868,7 @@ class TradingDashboard:
|
||||
], className=row_class)
|
||||
)
|
||||
|
||||
# Create table
|
||||
# Create simple table
|
||||
table = html.Table([
|
||||
html.Thead([
|
||||
html.Tr([
|
||||
@ -1829,8 +1877,8 @@ class TradingDashboard:
|
||||
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("Total Fees", className="small"),
|
||||
html.Th("Net P&L", className="small"),
|
||||
html.Th("Duration", className="small"),
|
||||
html.Th("MEXC", className="small")
|
||||
])
|
||||
@ -1838,24 +1886,7 @@ class TradingDashboard:
|
||||
html.Tbody(table_rows)
|
||||
], className="table table-sm table-striped")
|
||||
|
||||
# Add summary stats
|
||||
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")
|
||||
])
|
||||
|
||||
return [summary, table]
|
||||
return [table]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating closed trades table: {e}")
|
||||
@ -1935,88 +1966,95 @@ class TradingDashboard:
|
||||
logger.error(f"Error clearing closed trades history: {e}")
|
||||
|
||||
def _create_session_performance(self) -> List:
|
||||
"""Create compact session performance display with signal statistics"""
|
||||
"""Create enhanced session performance display with multiline format and total volume"""
|
||||
try:
|
||||
session_duration = datetime.now() - self.session_start
|
||||
duration_str = f"{session_duration.seconds//3600:02d}:{(session_duration.seconds//60)%60:02d}:{session_duration.seconds%60:02d}"
|
||||
# Calculate comprehensive session metrics from closed trades
|
||||
total_trades = len(self.closed_trades)
|
||||
winning_trades = len([t for t in self.closed_trades if t['net_pnl'] > 0])
|
||||
total_net_pnl = sum(t['net_pnl'] for t in self.closed_trades)
|
||||
total_fees_paid = sum(t.get('fees', 0) for t in self.closed_trades)
|
||||
|
||||
# Calculate win rate
|
||||
winning_trades = [t for t in self.session_trades if 'pnl' in t and t['pnl'] > 0]
|
||||
losing_trades = [t for t in self.session_trades if 'pnl' in t and t['pnl'] < 0]
|
||||
closed_trades = len(winning_trades) + len(losing_trades)
|
||||
win_rate = (len(winning_trades) / closed_trades * 100) if closed_trades > 0 else 0
|
||||
# Calculate total volume (price * size for each trade)
|
||||
total_volume = 0
|
||||
for trade in self.closed_trades:
|
||||
entry_volume = trade.get('entry_price', 0) * trade.get('size', 0)
|
||||
exit_volume = trade.get('exit_price', 0) * trade.get('size', 0)
|
||||
total_volume += entry_volume + exit_volume # Both entry and exit contribute to volume
|
||||
|
||||
# Calculate signal statistics
|
||||
executed_signals = [d for d in self.recent_decisions if isinstance(d, dict) and d.get('signal_type') == 'EXECUTED']
|
||||
ignored_signals = [d for d in self.recent_decisions if isinstance(d, dict) and d.get('signal_type') == 'IGNORED']
|
||||
total_signals = len(executed_signals) + len(ignored_signals)
|
||||
execution_rate = (len(executed_signals) / total_signals * 100) if total_signals > 0 else 0
|
||||
# Calculate fee breakdown
|
||||
maker_fees = sum(t.get('fees', 0) for t in self.closed_trades if t.get('fee_type') == 'maker')
|
||||
taker_fees = sum(t.get('fees', 0) for t in self.closed_trades if t.get('fee_type') != 'maker')
|
||||
|
||||
# Calculate other metrics
|
||||
total_volume = sum(t.get('price', 0) * t.get('size', 0) for t in self.session_trades)
|
||||
avg_trade_pnl = (self.total_realized_pnl / closed_trades) if closed_trades > 0 else 0
|
||||
portfolio_value = self.starting_balance + self.total_realized_pnl
|
||||
portfolio_return = (self.total_realized_pnl / self.starting_balance * 100) if self.starting_balance > 0 else 0
|
||||
# Calculate gross P&L (before fees)
|
||||
gross_pnl = total_net_pnl + total_fees_paid
|
||||
|
||||
performance_items = [
|
||||
# Row 1: Duration and Portfolio Value
|
||||
# Calculate rates and percentages
|
||||
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
|
||||
avg_trade_pnl = (total_net_pnl / total_trades) if total_trades > 0 else 0
|
||||
fee_impact = (total_fees_paid / gross_pnl * 100) if gross_pnl > 0 else 0
|
||||
fee_percentage_of_volume = (total_fees_paid / total_volume * 100) if total_volume > 0 else 0
|
||||
|
||||
# Calculate signal stats from recent decisions
|
||||
total_signals = len([d for d in self.recent_decisions if d.get('signal')])
|
||||
executed_signals = len([d for d in self.recent_decisions if d.get('signal') and d.get('executed')])
|
||||
signal_efficiency = (executed_signals / total_signals * 100) if total_signals > 0 else 0
|
||||
|
||||
# Create enhanced multiline performance display
|
||||
metrics = [
|
||||
# Line 1: Basic trade statistics
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Strong("Duration: "),
|
||||
html.Span(duration_str, className="text-info")
|
||||
], className="col-6 small"),
|
||||
html.Div([
|
||||
html.Strong("Portfolio: "),
|
||||
html.Span(f"${portfolio_value:,.2f}",
|
||||
className="text-success" if portfolio_value >= self.starting_balance else "text-danger")
|
||||
], className="col-6 small")
|
||||
], className="row mb-1"),
|
||||
html.Small([
|
||||
html.Strong(f"Total: {total_trades} trades | "),
|
||||
html.Span(f"Win Rate: {win_rate:.1f}% | ", className="text-info"),
|
||||
html.Span(f"Avg P&L: ${avg_trade_pnl:.2f}",
|
||||
className="text-success" if avg_trade_pnl >= 0 else "text-danger")
|
||||
])
|
||||
], className="mb-1"),
|
||||
|
||||
# Row 2: Trades and Win Rate
|
||||
# Line 2: P&L breakdown (Gross vs Net)
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Strong("Trades: "),
|
||||
html.Span(f"{len(self.session_trades)}", className="text-info")
|
||||
], className="col-6 small"),
|
||||
html.Div([
|
||||
html.Strong("Win Rate: "),
|
||||
html.Span(f"{win_rate:.1f}%",
|
||||
className="text-success" if win_rate >= 50 else "text-warning")
|
||||
], className="col-6 small")
|
||||
], className="row mb-1"),
|
||||
html.Small([
|
||||
html.Strong("P&L: "),
|
||||
html.Span(f"Gross: ${gross_pnl:.2f} | ",
|
||||
className="text-success" if gross_pnl >= 0 else "text-danger"),
|
||||
html.Span(f"Net: ${total_net_pnl:.2f} | ",
|
||||
className="text-success" if total_net_pnl >= 0 else "text-danger"),
|
||||
html.Span(f"Fee Impact: {fee_impact:.1f}%", className="text-warning")
|
||||
])
|
||||
], className="mb-1"),
|
||||
|
||||
# Row 3: Signals and Execution Rate
|
||||
# Line 3: Fee breakdown with volume for validation
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Strong("Signals: "),
|
||||
html.Span(f"{total_signals}", className="text-info")
|
||||
], className="col-6 small"),
|
||||
html.Div([
|
||||
html.Strong("Exec Rate: "),
|
||||
html.Span(f"{execution_rate:.1f}%",
|
||||
className="text-success" if execution_rate >= 30 else "text-warning")
|
||||
], className="col-6 small")
|
||||
], className="row mb-1"),
|
||||
|
||||
# Row 4: Portfolio Return and Fees
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Strong("Return: "),
|
||||
html.Span(f"{portfolio_return:+.2f}%",
|
||||
className="text-success" if portfolio_return >= 0 else "text-danger")
|
||||
], className="col-6 small"),
|
||||
html.Div([
|
||||
html.Small([
|
||||
html.Strong("Fees: "),
|
||||
html.Span(f"${self.total_fees:.2f}", className="text-muted")
|
||||
], className="col-6 small")
|
||||
], className="row")
|
||||
html.Span(f"Total: ${total_fees_paid:.3f} | ", className="text-warning"),
|
||||
html.Span(f"Maker: ${maker_fees:.3f} (0.00%) | ", className="text-success"),
|
||||
html.Span(f"Taker: ${taker_fees:.3f} (0.05%)", className="text-danger")
|
||||
])
|
||||
], className="mb-1"),
|
||||
|
||||
# Line 4: Volume and fee percentage for validation
|
||||
html.Div([
|
||||
html.Small([
|
||||
html.Strong("Volume: "),
|
||||
html.Span(f"${total_volume:,.0f} | ", className="text-muted"),
|
||||
html.Strong("Fee %: "),
|
||||
html.Span(f"{fee_percentage_of_volume:.4f}% | ", className="text-warning"),
|
||||
html.Strong("Signals: "),
|
||||
html.Span(f"{executed_signals}/{total_signals} ({signal_efficiency:.1f}%)", className="text-info")
|
||||
])
|
||||
], className="mb-2")
|
||||
]
|
||||
|
||||
return performance_items
|
||||
return metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating session performance: {e}")
|
||||
return [html.P(f"Error: {str(e)}", className="text-danger")]
|
||||
return [html.Div([
|
||||
html.Strong("Session Performance", className="text-primary"),
|
||||
html.Br(),
|
||||
html.Small(f"Error loading metrics: {str(e)}", className="text-danger")
|
||||
])]
|
||||
|
||||
def _force_demo_signal(self, symbol: str, current_price: float) -> None:
|
||||
"""DISABLED - No demo signals, only real market data"""
|
||||
|
Reference in New Issue
Block a user