better fees calc
This commit is contained in:
parent
398aca32ad
commit
de01d3665c
@ -2,60 +2,34 @@
|
|||||||
{
|
{
|
||||||
"trade_id": 1,
|
"trade_id": 1,
|
||||||
"side": "LONG",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-28T11:43:13.550522+00:00",
|
"entry_time": "2025-05-28T12:22:26.566103+00:00",
|
||||||
"exit_time": "2025-05-28T11:43:44.025990+00:00",
|
"exit_time": "2025-05-28T12:22:46.701903+00:00",
|
||||||
"entry_price": 2652.59,
|
"entry_price": 2670.5,
|
||||||
"exit_price": 2651.9,
|
"exit_price": 2673.89,
|
||||||
"size": 0.003343,
|
"size": 0.003557,
|
||||||
"gross_pnl": -0.0023066700000001824,
|
"gross_pnl": 0.012058229999999547,
|
||||||
"fees": 0.0,
|
"fees": 0.009504997615,
|
||||||
"net_pnl": -0.0023066700000001824,
|
"fee_type": "taker",
|
||||||
"duration": "0:00:30.475468",
|
"fee_rate": 0.0005,
|
||||||
|
"net_pnl": 0.002553232384999547,
|
||||||
|
"duration": "0:00:20.135800",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 2,
|
"trade_id": 2,
|
||||||
"side": "SHORT",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-28T11:43:44.025990+00:00",
|
"entry_time": "2025-05-28T12:22:46.701903+00:00",
|
||||||
"exit_time": "2025-05-28T11:44:14.341821+00:00",
|
"exit_time": "2025-05-28T12:23:01.804341+00:00",
|
||||||
"entry_price": 2651.9,
|
"entry_price": 2673.89,
|
||||||
"exit_price": 2651.09,
|
"exit_price": 2678.29,
|
||||||
"size": 0.003136,
|
"size": 0.003553,
|
||||||
"gross_pnl": 0.0025401599999998288,
|
"gross_pnl": -0.015633200000000323,
|
||||||
"fees": 0.0,
|
"fees": 0.009508147770000001,
|
||||||
"net_pnl": 0.0025401599999998288,
|
"fee_type": "taker",
|
||||||
"duration": "0:00:30.315831",
|
"fee_rate": 0.0005,
|
||||||
"symbol": "ETH/USDC",
|
"net_pnl": -0.025141347770000322,
|
||||||
"mexc_executed": false
|
"duration": "0:00:15.102438",
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 3,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-28T11:46:26.737826+00:00",
|
|
||||||
"exit_time": "2025-05-28T11:46:42.810205+00:00",
|
|
||||||
"entry_price": 2651.89,
|
|
||||||
"exit_price": 2651.03,
|
|
||||||
"size": 0.003551,
|
|
||||||
"gross_pnl": -0.003053859999998837,
|
|
||||||
"fees": 0.0,
|
|
||||||
"net_pnl": -0.003053859999998837,
|
|
||||||
"duration": "0:00:16.072379",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 4,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-28T11:46:42.810205+00:00",
|
|
||||||
"exit_time": "2025-05-28T11:47:12.016524+00:00",
|
|
||||||
"entry_price": 2651.03,
|
|
||||||
"exit_price": 2651.49,
|
|
||||||
"size": 0.002849,
|
|
||||||
"gross_pnl": -0.001310539999998808,
|
|
||||||
"fees": 0.0,
|
|
||||||
"net_pnl": -0.001310539999998808,
|
|
||||||
"duration": "0:00:29.206319",
|
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
}
|
}
|
||||||
|
@ -127,13 +127,13 @@ trading:
|
|||||||
max_position_size: 0.05 # Maximum position size (5% of balance)
|
max_position_size: 0.05 # Maximum position size (5% of balance)
|
||||||
stop_loss: 0.02 # 2% stop loss
|
stop_loss: 0.02 # 2% stop loss
|
||||||
take_profit: 0.05 # 5% take profit
|
take_profit: 0.05 # 5% take profit
|
||||||
trading_fee: 0.0005 # 0.05% trading fee (MEXC taker fee)
|
trading_fee: 0.0005 # 0.05% trading fee (MEXC taker fee - fallback)
|
||||||
|
|
||||||
# MEXC Fee Structure (asymmetrical)
|
# MEXC Fee Structure (asymmetrical) - Updated 2025-05-28
|
||||||
trading_fees:
|
trading_fees:
|
||||||
maker: 0.0000 # 0.00% maker fee (adds liquidity)
|
maker: 0.0000 # 0.00% maker fee (adds liquidity)
|
||||||
taker: 0.0005 # 0.05% taker fee (takes liquidity)
|
taker: 0.0005 # 0.05% taker fee (takes liquidity)
|
||||||
default: 0.0005 # Default fallback fee
|
default: 0.0005 # Default fallback fee (taker rate)
|
||||||
|
|
||||||
# Risk management
|
# Risk management
|
||||||
max_daily_trades: 20 # Maximum trades per day
|
max_daily_trades: 20 # Maximum trades per day
|
||||||
@ -146,7 +146,7 @@ trading:
|
|||||||
# MEXC Trading API Configuration
|
# MEXC Trading API Configuration
|
||||||
mexc_trading:
|
mexc_trading:
|
||||||
enabled: true # Set to true to enable live trading
|
enabled: true # Set to true to enable live trading
|
||||||
trading_mode: "live" # Options: "simulation", "testnet", "live"
|
trading_mode: "simulation" # Options: "simulation", "testnet", "live"
|
||||||
# - simulation: No real trades, just logging (safest)
|
# - simulation: No real trades, just logging (safest)
|
||||||
# - testnet: Use exchange testnet if available (MEXC doesn't have true testnet)
|
# - testnet: Use exchange testnet if available (MEXC doesn't have true testnet)
|
||||||
# - live: Execute real trades with real money
|
# - live: Execute real trades with real money
|
||||||
|
34
docs/requirements.md
Normal file
34
docs/requirements.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Overview
|
||||||
|
Our main goal is to have multi modal model with decision making module (orchestrator ) interconnected CNN and RL modules. the inputs will be multi timeframe and multi symbol (ETH AND BTC, but extendable/configurable) and the outputs will be trading actions. in our RL module loop we will evaluate trading actions from the orchestrator to close the loop and addapt to the current market environment and learn form past experience. in our CNN training pipeline we should be able to use marked data to do back propagation on the perfect moves we had to do looking at the past data (when we know the future move we want to predict) CNN module will have predictions on each timeframe as outputs - paired with it's own confidence.
|
||||||
|
|
||||||
|
|
||||||
|
# RL model
|
||||||
|
what data do the RL model take as input?
|
||||||
|
in the dash i see:
|
||||||
|
Training Data Stream
|
||||||
|
Tick Cache: 129 ticks
|
||||||
|
1s Bars: 128 bars
|
||||||
|
Stream: LIVE
|
||||||
|
|
||||||
|
RL (and CNN , and all models) should have available the following data:
|
||||||
|
ETH:
|
||||||
|
300s max of raw ticks data - this is important for detecting single big moves and momentum
|
||||||
|
300s of 1s OHLCV data (5 min)
|
||||||
|
300 OHLCV + indicatros bars of each 1m 1h 1d and 1s BTC
|
||||||
|
|
||||||
|
RL model should have also access of the last hidden layers of the CNN model where patterns are learned. it can be empty if CNN model is not active or missing. as well as the output (predictions) of the CNN model for each timeframe (1s 1m 1h 1d) and next expected pivot point
|
||||||
|
|
||||||
|
## CNN model
|
||||||
|
CNN modell will take the same market data as the RL model but it will learn patterns and predict the next pivot point for each timeframe (1s 1m 1h 1d)
|
||||||
|
during retrospective training we will programatically calculate pivot points and compare the output of the CNN model with the actual pivot points to calculate the accuracy of the model.
|
||||||
|
we will have 2 types of pivot points:
|
||||||
|
1: standard for the 1s 1m 1h 1d (primary) ETH and (reference) BTC tickers. each as array of 50 pivot points max.
|
||||||
|
2: 5 pivot point recursively calculated for 1s OHLCV data following the following description:
|
||||||
|
"Let me explain. This logic is based on Larry Williams market structure. Swing high, swing lows. This is swing low. Swing low is candle with higher lows on the both side of it. And swing high is a candle with lower highs on the both side of it. By using these swing points, I'm able to determine next trend. In my case, blue trend. And by using blue trend swing points, I'm able to determine purple trend and so forth.
|
||||||
|
And that's the easiest way to determine a trend. As we can see orange trend that is a little bit smaller trend that yellow trend is up but orange trend failed its higher low creation right now and as we can see magenta trend here is going down. It's very probable that magenta trend is going to create lower high here above 59,34.
|
||||||
|
If this high breaks then there is a still a possibility that this orange trend creates higher lows here and then continues to go higher. "
|
||||||
|
|
||||||
|
so the first shortest trend pivot points is the 1s OHLCV data. where a pivot point is defined as bar with higher low (or lower high) on the both side of it.
|
||||||
|
next trend pivot points are calculated from THE FIVE PIVOT POINTS OF THE PREVIOUS TREND.
|
||||||
|
this way we can have a recursive pivot points calculation that will be used to predict the next trend. each trend will be more and more long term.
|
||||||
|
theese pivot points will define the trend direction and the trend strength.
|
95
test_enhanced_fee_tracking.py
Normal file
95
test_enhanced_fee_tracking.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify enhanced fee tracking with maker/taker fees
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from web.dashboard import TradingDashboard
|
||||||
|
from core.data_provider import DataProvider
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_enhanced_fee_tracking():
|
||||||
|
"""Test enhanced fee tracking with maker/taker fees"""
|
||||||
|
|
||||||
|
logger.info("Testing enhanced fee tracking...")
|
||||||
|
|
||||||
|
# Create dashboard instance
|
||||||
|
data_provider = DataProvider()
|
||||||
|
dashboard = TradingDashboard(data_provider=data_provider)
|
||||||
|
|
||||||
|
# Create test trading decisions with different fee types
|
||||||
|
test_decisions = [
|
||||||
|
{
|
||||||
|
'action': 'BUY',
|
||||||
|
'symbol': 'ETH/USDT',
|
||||||
|
'price': 3500.0,
|
||||||
|
'confidence': 0.8,
|
||||||
|
'timestamp': datetime.now(timezone.utc),
|
||||||
|
'order_type': 'market', # Should use taker fee
|
||||||
|
'filled_as_maker': False
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'action': 'SELL',
|
||||||
|
'symbol': 'ETH/USDT',
|
||||||
|
'price': 3520.0,
|
||||||
|
'confidence': 0.9,
|
||||||
|
'timestamp': datetime.now(timezone.utc),
|
||||||
|
'order_type': 'limit', # Should use maker fee if filled as maker
|
||||||
|
'filled_as_maker': True
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Process the trading decisions
|
||||||
|
for i, decision in enumerate(test_decisions):
|
||||||
|
logger.info(f"Processing decision {i+1}: {decision['action']} @ ${decision['price']}")
|
||||||
|
dashboard._process_trading_decision(decision)
|
||||||
|
|
||||||
|
# Check session trades
|
||||||
|
if dashboard.session_trades:
|
||||||
|
latest_trade = dashboard.session_trades[-1]
|
||||||
|
fee_type = latest_trade.get('fee_type', 'unknown')
|
||||||
|
fee_rate = latest_trade.get('fee_rate', 0)
|
||||||
|
fees = latest_trade.get('fees', 0)
|
||||||
|
|
||||||
|
logger.info(f" Trade recorded: {latest_trade.get('position_action', 'unknown')}")
|
||||||
|
logger.info(f" Fee Type: {fee_type}")
|
||||||
|
logger.info(f" Fee Rate: {fee_rate*100:.3f}%")
|
||||||
|
logger.info(f" Fee Amount: ${fees:.4f}")
|
||||||
|
|
||||||
|
# Check closed trades
|
||||||
|
if dashboard.closed_trades:
|
||||||
|
logger.info(f"\nClosed trades: {len(dashboard.closed_trades)}")
|
||||||
|
for trade in dashboard.closed_trades:
|
||||||
|
logger.info(f" Trade #{trade['trade_id']}: {trade['side']}")
|
||||||
|
logger.info(f" Fee Type: {trade.get('fee_type', 'unknown')}")
|
||||||
|
logger.info(f" Fee Rate: {trade.get('fee_rate', 0)*100:.3f}%")
|
||||||
|
logger.info(f" Total Fees: ${trade.get('fees', 0):.4f}")
|
||||||
|
logger.info(f" Net P&L: ${trade.get('net_pnl', 0):.2f}")
|
||||||
|
|
||||||
|
# Test session performance with fee breakdown
|
||||||
|
logger.info("\nTesting session performance display...")
|
||||||
|
performance = dashboard._create_session_performance()
|
||||||
|
logger.info(f"Session performance components: {len(performance)}")
|
||||||
|
|
||||||
|
# Test closed trades table
|
||||||
|
logger.info("\nTesting enhanced trades table...")
|
||||||
|
table_components = dashboard._create_closed_trades_table()
|
||||||
|
logger.info(f"Table components: {len(table_components)}")
|
||||||
|
|
||||||
|
logger.info("Enhanced fee tracking test completed!")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_enhanced_fee_tracking()
|
230
web/dashboard.py
230
web/dashboard.py
@ -649,11 +649,17 @@ class TradingDashboard:
|
|||||||
prevent_initial_call=True
|
prevent_initial_call=True
|
||||||
)
|
)
|
||||||
def clear_trade_history(n_clicks):
|
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:
|
if n_clicks and n_clicks > 0:
|
||||||
self.clear_closed_trades_history()
|
try:
|
||||||
return [html.P("Trade history cleared", className="text-muted text-center")]
|
# Clear both closed trades and session stats (they're the same now)
|
||||||
return self._create_closed_trades_table()
|
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:
|
def _simulate_price_update(self, symbol: str, base_price: float) -> float:
|
||||||
"""
|
"""
|
||||||
@ -1407,14 +1413,37 @@ class TradingDashboard:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _process_trading_decision(self, decision: Dict) -> 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:
|
try:
|
||||||
if not decision:
|
if not decision:
|
||||||
return
|
return
|
||||||
|
|
||||||
current_time = datetime.now(timezone.utc) # Use UTC for consistency
|
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
|
# Execute trade through MEXC if available
|
||||||
mexc_success = False
|
mexc_success = False
|
||||||
@ -1479,6 +1508,8 @@ class TradingDashboard:
|
|||||||
close_record['entry_price'] = entry_price
|
close_record['entry_price'] = entry_price
|
||||||
close_record['pnl'] = net_pnl
|
close_record['pnl'] = net_pnl
|
||||||
close_record['fees'] = fee
|
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
|
close_record['size'] = size # Use original position size for close
|
||||||
self.session_trades.append(close_record)
|
self.session_trades.append(close_record)
|
||||||
|
|
||||||
@ -1493,6 +1524,8 @@ class TradingDashboard:
|
|||||||
'size': size,
|
'size': size,
|
||||||
'gross_pnl': gross_pnl,
|
'gross_pnl': gross_pnl,
|
||||||
'fees': fee + self.current_position['fees'],
|
'fees': fee + self.current_position['fees'],
|
||||||
|
'fee_type': fee_type,
|
||||||
|
'fee_rate': fee_rate,
|
||||||
'net_pnl': net_pnl,
|
'net_pnl': net_pnl,
|
||||||
'duration': current_time - entry_time,
|
'duration': current_time - entry_time,
|
||||||
'symbol': decision.get('symbol', 'ETH/USDT'),
|
'symbol': decision.get('symbol', 'ETH/USDT'),
|
||||||
@ -1527,6 +1560,8 @@ class TradingDashboard:
|
|||||||
trade_record = decision.copy()
|
trade_record = decision.copy()
|
||||||
trade_record['position_action'] = 'OPEN_LONG'
|
trade_record['position_action'] = 'OPEN_LONG'
|
||||||
trade_record['fees'] = fee
|
trade_record['fees'] = fee
|
||||||
|
trade_record['fee_type'] = fee_type
|
||||||
|
trade_record['fee_rate'] = fee_rate
|
||||||
self.session_trades.append(trade_record)
|
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%})")
|
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['entry_price'] = entry_price
|
||||||
close_record['pnl'] = net_pnl
|
close_record['pnl'] = net_pnl
|
||||||
close_record['fees'] = fee
|
close_record['fees'] = fee
|
||||||
|
close_record['fee_type'] = fee_type
|
||||||
|
close_record['fee_rate'] = fee_rate
|
||||||
self.session_trades.append(close_record)
|
self.session_trades.append(close_record)
|
||||||
|
|
||||||
# Add to closed trades accounting list
|
# Add to closed trades accounting list
|
||||||
@ -1569,6 +1606,8 @@ class TradingDashboard:
|
|||||||
'size': size,
|
'size': size,
|
||||||
'gross_pnl': gross_pnl,
|
'gross_pnl': gross_pnl,
|
||||||
'fees': fee + self.current_position['fees'],
|
'fees': fee + self.current_position['fees'],
|
||||||
|
'fee_type': fee_type,
|
||||||
|
'fee_rate': fee_rate,
|
||||||
'net_pnl': net_pnl,
|
'net_pnl': net_pnl,
|
||||||
'duration': current_time - entry_time,
|
'duration': current_time - entry_time,
|
||||||
'symbol': decision.get('symbol', 'ETH/USDT'),
|
'symbol': decision.get('symbol', 'ETH/USDT'),
|
||||||
@ -1610,6 +1649,8 @@ class TradingDashboard:
|
|||||||
close_record['entry_price'] = entry_price
|
close_record['entry_price'] = entry_price
|
||||||
close_record['pnl'] = net_pnl
|
close_record['pnl'] = net_pnl
|
||||||
close_record['fees'] = fee
|
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
|
close_record['size'] = size # Use original position size for close
|
||||||
self.session_trades.append(close_record)
|
self.session_trades.append(close_record)
|
||||||
|
|
||||||
@ -1624,6 +1665,8 @@ class TradingDashboard:
|
|||||||
'size': size,
|
'size': size,
|
||||||
'gross_pnl': gross_pnl,
|
'gross_pnl': gross_pnl,
|
||||||
'fees': fee + self.current_position['fees'],
|
'fees': fee + self.current_position['fees'],
|
||||||
|
'fee_type': fee_type,
|
||||||
|
'fee_rate': fee_rate,
|
||||||
'net_pnl': net_pnl,
|
'net_pnl': net_pnl,
|
||||||
'duration': current_time - entry_time,
|
'duration': current_time - entry_time,
|
||||||
'symbol': decision.get('symbol', 'ETH/USDT'),
|
'symbol': decision.get('symbol', 'ETH/USDT'),
|
||||||
@ -1655,6 +1698,8 @@ class TradingDashboard:
|
|||||||
trade_record = decision.copy()
|
trade_record = decision.copy()
|
||||||
trade_record['position_action'] = 'OPEN_SHORT'
|
trade_record['position_action'] = 'OPEN_SHORT'
|
||||||
trade_record['fees'] = fee
|
trade_record['fees'] = fee
|
||||||
|
trade_record['fee_type'] = fee_type
|
||||||
|
trade_record['fee_rate'] = fee_rate
|
||||||
self.session_trades.append(trade_record)
|
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%})")
|
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")
|
logger.info("[ORCHESTRATOR] Trading loop started in background")
|
||||||
|
|
||||||
def _create_closed_trades_table(self) -> List:
|
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:
|
try:
|
||||||
if not self.closed_trades:
|
if not self.closed_trades:
|
||||||
return [html.P("No closed trades yet", className="text-muted text-center")]
|
return [html.P("No closed trades yet", className="text-muted text-center")]
|
||||||
@ -1805,6 +1850,9 @@ class TradingDashboard:
|
|||||||
position_size = trade.get('size', 0)
|
position_size = trade.get('size', 0)
|
||||||
size_display = f"{position_size:.4f}" if position_size < 1 else f"{position_size:.2f}"
|
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(
|
table_rows.append(
|
||||||
html.Tr([
|
html.Tr([
|
||||||
html.Td(f"#{trade['trade_id']}", className="small"),
|
html.Td(f"#{trade['trade_id']}", className="small"),
|
||||||
@ -1812,7 +1860,7 @@ class TradingDashboard:
|
|||||||
html.Td(size_display, className="small text-info"),
|
html.Td(size_display, className="small text-info"),
|
||||||
html.Td(f"${trade['entry_price']:.2f}", className="small"),
|
html.Td(f"${trade['entry_price']:.2f}", className="small"),
|
||||||
html.Td(f"${trade['exit_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(f"${trade['net_pnl']:.2f}", className="small fw-bold"),
|
||||||
html.Td(duration_str, className="small"),
|
html.Td(duration_str, className="small"),
|
||||||
html.Td("✓" if trade.get('mexc_executed', False) else "SIM",
|
html.Td("✓" if trade.get('mexc_executed', False) else "SIM",
|
||||||
@ -1820,7 +1868,7 @@ class TradingDashboard:
|
|||||||
], className=row_class)
|
], className=row_class)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create table
|
# Create simple table
|
||||||
table = html.Table([
|
table = html.Table([
|
||||||
html.Thead([
|
html.Thead([
|
||||||
html.Tr([
|
html.Tr([
|
||||||
@ -1829,8 +1877,8 @@ class TradingDashboard:
|
|||||||
html.Th("Size", className="small"),
|
html.Th("Size", className="small"),
|
||||||
html.Th("Entry", className="small"),
|
html.Th("Entry", className="small"),
|
||||||
html.Th("Exit", className="small"),
|
html.Th("Exit", className="small"),
|
||||||
html.Th("Fees", className="small"),
|
html.Th("Total Fees", className="small"),
|
||||||
html.Th("P&L", className="small"),
|
html.Th("Net P&L", className="small"),
|
||||||
html.Th("Duration", className="small"),
|
html.Th("Duration", className="small"),
|
||||||
html.Th("MEXC", className="small")
|
html.Th("MEXC", className="small")
|
||||||
])
|
])
|
||||||
@ -1838,24 +1886,7 @@ class TradingDashboard:
|
|||||||
html.Tbody(table_rows)
|
html.Tbody(table_rows)
|
||||||
], className="table table-sm table-striped")
|
], className="table table-sm table-striped")
|
||||||
|
|
||||||
# Add summary stats
|
return [table]
|
||||||
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]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating closed trades table: {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}")
|
logger.error(f"Error clearing closed trades history: {e}")
|
||||||
|
|
||||||
def _create_session_performance(self) -> List:
|
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:
|
try:
|
||||||
session_duration = datetime.now() - self.session_start
|
# Calculate comprehensive session metrics from closed trades
|
||||||
duration_str = f"{session_duration.seconds//3600:02d}:{(session_duration.seconds//60)%60:02d}:{session_duration.seconds%60:02d}"
|
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
|
# Calculate total volume (price * size for each trade)
|
||||||
winning_trades = [t for t in self.session_trades if 'pnl' in t and t['pnl'] > 0]
|
total_volume = 0
|
||||||
losing_trades = [t for t in self.session_trades if 'pnl' in t and t['pnl'] < 0]
|
for trade in self.closed_trades:
|
||||||
closed_trades = len(winning_trades) + len(losing_trades)
|
entry_volume = trade.get('entry_price', 0) * trade.get('size', 0)
|
||||||
win_rate = (len(winning_trades) / closed_trades * 100) if closed_trades > 0 else 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
|
# Calculate fee breakdown
|
||||||
executed_signals = [d for d in self.recent_decisions if isinstance(d, dict) and d.get('signal_type') == 'EXECUTED']
|
maker_fees = sum(t.get('fees', 0) for t in self.closed_trades if t.get('fee_type') == 'maker')
|
||||||
ignored_signals = [d for d in self.recent_decisions if isinstance(d, dict) and d.get('signal_type') == 'IGNORED']
|
taker_fees = sum(t.get('fees', 0) for t in self.closed_trades if t.get('fee_type') != 'maker')
|
||||||
total_signals = len(executed_signals) + len(ignored_signals)
|
|
||||||
execution_rate = (len(executed_signals) / total_signals * 100) if total_signals > 0 else 0
|
|
||||||
|
|
||||||
# Calculate other metrics
|
# Calculate gross P&L (before fees)
|
||||||
total_volume = sum(t.get('price', 0) * t.get('size', 0) for t in self.session_trades)
|
gross_pnl = total_net_pnl + total_fees_paid
|
||||||
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
|
|
||||||
|
|
||||||
performance_items = [
|
# Calculate rates and percentages
|
||||||
# Row 1: Duration and Portfolio Value
|
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.Div([
|
html.Small([
|
||||||
html.Strong("Duration: "),
|
html.Strong(f"Total: {total_trades} trades | "),
|
||||||
html.Span(duration_str, className="text-info")
|
html.Span(f"Win Rate: {win_rate:.1f}% | ", className="text-info"),
|
||||||
], className="col-6 small"),
|
html.Span(f"Avg P&L: ${avg_trade_pnl:.2f}",
|
||||||
html.Div([
|
className="text-success" if avg_trade_pnl >= 0 else "text-danger")
|
||||||
html.Strong("Portfolio: "),
|
])
|
||||||
html.Span(f"${portfolio_value:,.2f}",
|
], className="mb-1"),
|
||||||
className="text-success" if portfolio_value >= self.starting_balance else "text-danger")
|
|
||||||
], className="col-6 small")
|
|
||||||
], className="row mb-1"),
|
|
||||||
|
|
||||||
# Row 2: Trades and Win Rate
|
# Line 2: P&L breakdown (Gross vs Net)
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Div([
|
html.Small([
|
||||||
html.Strong("Trades: "),
|
html.Strong("P&L: "),
|
||||||
html.Span(f"{len(self.session_trades)}", className="text-info")
|
html.Span(f"Gross: ${gross_pnl:.2f} | ",
|
||||||
], className="col-6 small"),
|
className="text-success" if gross_pnl >= 0 else "text-danger"),
|
||||||
html.Div([
|
html.Span(f"Net: ${total_net_pnl:.2f} | ",
|
||||||
html.Strong("Win Rate: "),
|
className="text-success" if total_net_pnl >= 0 else "text-danger"),
|
||||||
html.Span(f"{win_rate:.1f}%",
|
html.Span(f"Fee Impact: {fee_impact:.1f}%", className="text-warning")
|
||||||
className="text-success" if win_rate >= 50 else "text-warning")
|
])
|
||||||
], className="col-6 small")
|
], className="mb-1"),
|
||||||
], className="row mb-1"),
|
|
||||||
|
|
||||||
# Row 3: Signals and Execution Rate
|
# Line 3: Fee breakdown with volume for validation
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Div([
|
html.Small([
|
||||||
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.Strong("Fees: "),
|
html.Strong("Fees: "),
|
||||||
html.Span(f"${self.total_fees:.2f}", className="text-muted")
|
html.Span(f"Total: ${total_fees_paid:.3f} | ", className="text-warning"),
|
||||||
], className="col-6 small")
|
html.Span(f"Maker: ${maker_fees:.3f} (0.00%) | ", className="text-success"),
|
||||||
], className="row")
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error creating session performance: {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:
|
def _force_demo_signal(self, symbol: str, current_price: float) -> None:
|
||||||
"""DISABLED - No demo signals, only real market data"""
|
"""DISABLED - No demo signals, only real market data"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user