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

@ -1,17 +1,23 @@
import logging import logging
import time import time
from typing import Dict, Any, List, Optional import asyncio
import json
import websockets
from typing import Dict, Any, List, Optional, Callable
import requests import requests
import hmac import hmac
import hashlib import hashlib
from urllib.parse import urlencode from urllib.parse import urlencode
from datetime import datetime
from threading import Thread, Lock
from collections import deque
from .exchange_interface import ExchangeInterface from .exchange_interface import ExchangeInterface
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class MEXCInterface(ExchangeInterface): class MEXCInterface(ExchangeInterface):
"""MEXC Exchange API Interface""" """MEXC Exchange API Interface with WebSocket support"""
def __init__(self, api_key: str = None, api_secret: str = None, test_mode: bool = True): def __init__(self, api_key: str = None, api_secret: str = None, test_mode: bool = True):
"""Initialize MEXC exchange interface. """Initialize MEXC exchange interface.
@ -25,6 +31,247 @@ class MEXCInterface(ExchangeInterface):
self.base_url = "https://api.mexc.com" self.base_url = "https://api.mexc.com"
self.api_version = "api/v3" self.api_version = "api/v3"
# WebSocket configuration
self.ws_base_url = "wss://wbs.mexc.com/ws"
self.websocket_tasks = {}
self.is_streaming = False
self.stream_lock = Lock()
self.tick_callbacks = []
self.ticker_callbacks = []
# Data buffers for reliability
self.recent_ticks = {} # {symbol: deque}
self.current_prices = {} # {symbol: price}
self.buffer_size = 1000
def add_tick_callback(self, callback: Callable[[Dict[str, Any]], None]):
"""Add callback for real-time tick data"""
self.tick_callbacks.append(callback)
logger.info(f"Added MEXC tick callback: {len(self.tick_callbacks)} total")
def add_ticker_callback(self, callback: Callable[[Dict[str, Any]], None]):
"""Add callback for real-time ticker data"""
self.ticker_callbacks.append(callback)
logger.info(f"Added MEXC ticker callback: {len(self.ticker_callbacks)} total")
def _notify_tick_callbacks(self, tick_data: Dict[str, Any]):
"""Notify all tick callbacks with new data"""
for callback in self.tick_callbacks:
try:
callback(tick_data)
except Exception as e:
logger.error(f"Error in MEXC tick callback: {e}")
def _notify_ticker_callbacks(self, ticker_data: Dict[str, Any]):
"""Notify all ticker callbacks with new data"""
for callback in self.ticker_callbacks:
try:
callback(ticker_data)
except Exception as e:
logger.error(f"Error in MEXC ticker callback: {e}")
async def start_websocket_streams(self, symbols: List[str], stream_types: List[str] = None):
"""Start WebSocket streams for multiple symbols
Args:
symbols: List of symbols in 'BTC/USDT' format
stream_types: List of stream types ['trade', 'ticker', 'depth'] (default: ['trade', 'ticker'])
"""
if stream_types is None:
stream_types = ['trade', 'ticker']
self.is_streaming = True
logger.info(f"Starting MEXC WebSocket streams for {symbols} with types {stream_types}")
# Initialize buffers for symbols
for symbol in symbols:
mexc_symbol = symbol.replace('/', '').upper()
self.recent_ticks[mexc_symbol] = deque(maxlen=self.buffer_size)
# Start streams for each symbol and stream type combination
for symbol in symbols:
for stream_type in stream_types:
task = asyncio.create_task(self._websocket_stream(symbol, stream_type))
task_key = f"{symbol}_{stream_type}"
self.websocket_tasks[task_key] = task
async def stop_websocket_streams(self):
"""Stop all WebSocket streams"""
logger.info("Stopping MEXC WebSocket streams")
self.is_streaming = False
# Cancel all tasks
for task_key, task in self.websocket_tasks.items():
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
self.websocket_tasks.clear()
async def _websocket_stream(self, symbol: str, stream_type: str):
"""Individual WebSocket stream for a symbol and stream type"""
mexc_symbol = symbol.replace('/', '').upper()
# MEXC WebSocket stream naming convention
if stream_type == 'trade':
stream_name = f"{mexc_symbol}@trade"
elif stream_type == 'ticker':
stream_name = f"{mexc_symbol}@ticker"
elif stream_type == 'depth':
stream_name = f"{mexc_symbol}@depth"
else:
logger.error(f"Unsupported MEXC stream type: {stream_type}")
return
url = f"{self.ws_base_url}"
while self.is_streaming:
try:
logger.info(f"Connecting to MEXC WebSocket: {stream_name}")
async with websockets.connect(url) as websocket:
# Subscribe to the stream
subscribe_msg = {
"method": "SUBSCRIPTION",
"params": [stream_name]
}
await websocket.send(json.dumps(subscribe_msg))
logger.info(f"Subscribed to MEXC stream: {stream_name}")
async for message in websocket:
if not self.is_streaming:
break
try:
await self._process_websocket_message(mexc_symbol, stream_type, message)
except Exception as e:
logger.warning(f"Error processing MEXC message for {stream_name}: {e}")
except Exception as e:
logger.error(f"MEXC WebSocket error for {stream_name}: {e}")
if self.is_streaming:
logger.info(f"Reconnecting MEXC WebSocket for {stream_name} in 5 seconds...")
await asyncio.sleep(5)
async def _process_websocket_message(self, symbol: str, stream_type: str, message: str):
"""Process incoming WebSocket message"""
try:
data = json.loads(message)
# Handle subscription confirmation
if data.get('id') is not None:
logger.info(f"MEXC WebSocket subscription confirmed for {symbol} {stream_type}")
return
# Process data based on stream type
if stream_type == 'trade' and 'data' in data:
await self._process_trade_data(symbol, data['data'])
elif stream_type == 'ticker' and 'data' in data:
await self._process_ticker_data(symbol, data['data'])
elif stream_type == 'depth' and 'data' in data:
await self._process_depth_data(symbol, data['data'])
except Exception as e:
logger.error(f"Error processing MEXC WebSocket message: {e}")
async def _process_trade_data(self, symbol: str, trade_data: Dict[str, Any]):
"""Process trade data from WebSocket"""
try:
# MEXC trade data format
price = float(trade_data.get('p', 0))
quantity = float(trade_data.get('q', 0))
timestamp = datetime.fromtimestamp(int(trade_data.get('t', 0)) / 1000)
is_buyer_maker = trade_data.get('m', False)
trade_id = trade_data.get('i', '')
# Create standardized tick
tick = {
'symbol': symbol,
'timestamp': timestamp,
'price': price,
'volume': price * quantity, # Volume in quote currency
'quantity': quantity,
'side': 'sell' if is_buyer_maker else 'buy',
'trade_id': str(trade_id),
'is_buyer_maker': is_buyer_maker,
'exchange': 'MEXC',
'raw_data': trade_data
}
# Update buffers
self.recent_ticks[symbol].append(tick)
self.current_prices[symbol] = price
# Notify callbacks
self._notify_tick_callbacks(tick)
except Exception as e:
logger.error(f"Error processing MEXC trade data: {e}")
async def _process_ticker_data(self, symbol: str, ticker_data: Dict[str, Any]):
"""Process ticker data from WebSocket"""
try:
# MEXC ticker data format
ticker = {
'symbol': symbol,
'timestamp': datetime.now(),
'price': float(ticker_data.get('c', 0)), # Current price
'bid': float(ticker_data.get('b', 0)), # Best bid
'ask': float(ticker_data.get('a', 0)), # Best ask
'volume': float(ticker_data.get('v', 0)), # Volume
'high': float(ticker_data.get('h', 0)), # 24h high
'low': float(ticker_data.get('l', 0)), # 24h low
'change': float(ticker_data.get('P', 0)), # Price change %
'exchange': 'MEXC',
'raw_data': ticker_data
}
# Update current price
self.current_prices[symbol] = ticker['price']
# Notify callbacks
self._notify_ticker_callbacks(ticker)
except Exception as e:
logger.error(f"Error processing MEXC ticker data: {e}")
async def _process_depth_data(self, symbol: str, depth_data: Dict[str, Any]):
"""Process order book depth data from WebSocket"""
try:
# Process depth data if needed for future features
logger.debug(f"MEXC depth data received for {symbol}")
except Exception as e:
logger.error(f"Error processing MEXC depth data: {e}")
def get_current_price(self, symbol: str) -> Optional[float]:
"""Get current price for a symbol from WebSocket data or REST API fallback"""
mexc_symbol = symbol.replace('/', '').upper()
# Try from WebSocket data first
if mexc_symbol in self.current_prices:
return self.current_prices[mexc_symbol]
# Fallback to REST API
try:
ticker = self.get_ticker(symbol)
if ticker and 'price' in ticker:
return float(ticker['price'])
except Exception as e:
logger.warning(f"Failed to get current price for {symbol} from MEXC: {e}")
return None
def get_recent_ticks(self, symbol: str, count: int = 100) -> List[Dict[str, Any]]:
"""Get recent ticks for a symbol"""
mexc_symbol = symbol.replace('/', '').upper()
if mexc_symbol in self.recent_ticks:
return list(self.recent_ticks[mexc_symbol])[-count:]
return []
def connect(self) -> bool: def connect(self) -> bool:
"""Connect to MEXC API.""" """Connect to MEXC API."""
if not self.api_key or not self.api_secret: if not self.api_key or not self.api_secret:
@ -84,9 +331,15 @@ class MEXCInterface(ExchangeInterface):
params = {} params = {}
# Add timestamp and recvWindow as required by MEXC # Add timestamp and recvWindow as required by MEXC
params['timestamp'] = int(time.time() * 1000) # Use server time for better synchronization
try:
server_time_response = self._send_public_request('GET', 'time')
params['timestamp'] = server_time_response['serverTime']
except:
params['timestamp'] = int(time.time() * 1000)
if 'recvWindow' not in params: if 'recvWindow' not in params:
params['recvWindow'] = 5000 params['recvWindow'] = 10000 # Increased receive window
# Generate signature using the correct MEXC format # Generate signature using the correct MEXC format
signature = self._generate_signature(params) signature = self._generate_signature(params)
@ -94,18 +347,18 @@ class MEXCInterface(ExchangeInterface):
# Set headers as required by MEXC documentation # Set headers as required by MEXC documentation
headers = { headers = {
'X-MEXC-APIKEY': self.api_key, 'X-MEXC-APIKEY': self.api_key
'Content-Type': 'application/x-www-form-urlencoded'
} }
url = f"{self.base_url}/{self.api_version}/{endpoint}" url = f"{self.base_url}/{self.api_version}/{endpoint}"
try: try:
if method.upper() == 'GET': if method.upper() == 'GET':
# For GET requests, send parameters as query string # For GET requests with authentication, signature goes in query string
response = requests.get(url, params=params, headers=headers) response = requests.get(url, params=params, headers=headers)
elif method.upper() == 'POST': elif method.upper() == 'POST':
# For POST requests, send as form data in request body per MEXC documentation # For POST requests, send as form data in request body
headers['Content-Type'] = 'application/x-www-form-urlencoded'
response = requests.post(url, data=params, headers=headers) response = requests.post(url, data=params, headers=headers)
elif method.upper() == 'DELETE': elif method.upper() == 'DELETE':
# For DELETE requests, send parameters as query string # For DELETE requests, send parameters as query string

View File

@ -0,0 +1,62 @@
[
{
"trade_id": 1,
"side": "SHORT",
"entry_time": "2025-05-27T12:39:29.459137+00:00",
"exit_time": "2025-05-27T12:39:54.638968+00:00",
"entry_price": 2643.51,
"exit_price": 2643.51,
"size": 0.003294,
"gross_pnl": 0.0,
"fees": 0.0,
"net_pnl": 0.0,
"duration": "0:00:25.179831",
"symbol": "ETH/USDT",
"mexc_executed": false
},
{
"trade_id": 2,
"side": "LONG",
"entry_time": "2025-05-27T12:39:54.638968+00:00",
"exit_time": "2025-05-27T12:39:59.705733+00:00",
"entry_price": 2643.51,
"exit_price": 2642.76,
"size": 0.00301,
"gross_pnl": -0.0022575,
"fees": 0.0,
"net_pnl": -0.0022575,
"duration": "0:00:05.066765",
"symbol": "ETH/USDT",
"mexc_executed": false
},
{
"trade_id": 3,
"side": "SHORT",
"entry_time": "2025-05-27T12:39:59.705733+00:00",
"exit_time": "2025-05-27T12:40:09.752342+00:00",
"entry_price": 2642.76,
"exit_price": 2643.01,
"size": 0.003126,
"gross_pnl": -0.0007815,
"fees": 0.0,
"net_pnl": -0.0007815,
"duration": "0:00:10.046609",
"symbol": "ETH/USDT",
"mexc_executed": false
},
{
"trade_id": 4,
"side": "LONG",
"entry_time": "2025-05-27T12:40:09.752342+00:00",
"exit_time": "2025-05-27T12:40:14.810331+00:00",
"entry_price": 2643.01,
"exit_price": 2642.21,
"size": 0.003493,
"gross_pnl": -0.002794400000000635,
"fees": 0.0,
"net_pnl": -0.002794400000000635,
"duration": "0:00:05.057989",
"symbol": "ETH/USDT",
"mexc_executed": false
}
]

View File

@ -148,9 +148,9 @@ mexc_trading:
api_secret: "" # Set in .env file as MEXC_SECRET_KEY api_secret: "" # Set in .env file as MEXC_SECRET_KEY
# Position sizing (conservative for live trading) # Position sizing (conservative for live trading)
max_position_value_usd: 1.0 # Maximum $1 per position for testing max_position_value_usd: 10.0 # Maximum $1 per position for testing
min_position_value_usd: 0.1 # Minimum $0.10 per position min_position_value_usd: 5 # Minimum $0.10 per position
position_size_percent: 0.001 # 0.1% of balance per trade (very conservative) position_size_percent: 0.01 # 1% of balance per trade (conservative)
# Risk management # Risk management
max_daily_loss_usd: 5.0 # Stop trading if daily loss exceeds $5 max_daily_loss_usd: 5.0 # Stop trading if daily loss exceeds $5

View File

@ -144,10 +144,15 @@ class DataProvider:
logger.info(f"Preloading 300s of data for {symbol} {timeframe}") logger.info(f"Preloading 300s of data for {symbol} {timeframe}")
df = self._preload_300s_data(symbol, timeframe) df = self._preload_300s_data(symbol, timeframe)
else: else:
# Fetch from API with requested limit # Fetch from API with requested limit (Binance primary, MEXC fallback)
logger.info(f"Fetching historical data for {symbol} {timeframe}") logger.info(f"Fetching historical data for {symbol} {timeframe}")
df = self._fetch_from_binance(symbol, timeframe, limit) df = self._fetch_from_binance(symbol, timeframe, limit)
# Fallback to MEXC if Binance fails
if df is None or df.empty:
logger.info(f"Binance failed, trying MEXC fallback for {symbol}")
df = self._fetch_from_mexc(symbol, timeframe, limit)
if df is not None and not df.empty: if df is not None and not df.empty:
# Add technical indicators # Add technical indicators
df = self._add_technical_indicators(df) df = self._add_technical_indicators(df)
@ -217,9 +222,14 @@ class DataProvider:
logger.info(f"Preloading {candles_needed} candles for {symbol} {timeframe} (300s worth)") logger.info(f"Preloading {candles_needed} candles for {symbol} {timeframe} (300s worth)")
# Fetch the data # Fetch the data (Binance primary, MEXC fallback)
df = self._fetch_from_binance(symbol, timeframe, candles_needed) df = self._fetch_from_binance(symbol, timeframe, candles_needed)
# Fallback to MEXC if Binance fails
if df is None or df.empty:
logger.info(f"Binance failed, trying MEXC fallback for preload {symbol}")
df = self._fetch_from_mexc(symbol, timeframe, candles_needed)
if df is not None and not df.empty: if df is not None and not df.empty:
logger.info(f"Successfully preloaded {len(df)} candles for {symbol} {timeframe}") logger.info(f"Successfully preloaded {len(df)} candles for {symbol} {timeframe}")
return df return df
@ -289,8 +299,65 @@ class DataProvider:
logger.error(f"Error in preload_all_symbols_data: {e}") logger.error(f"Error in preload_all_symbols_data: {e}")
return {} return {}
def _fetch_from_mexc(self, symbol: str, timeframe: str, limit: int) -> Optional[pd.DataFrame]:
"""Fetch data from MEXC API (fallback data source when Binance is unavailable)"""
try:
# MEXC doesn't support 1s intervals
if timeframe == '1s':
logger.warning(f"MEXC doesn't support 1s intervals, skipping {symbol}")
return None
# Convert symbol format
mexc_symbol = symbol.replace('/', '').upper()
# Convert timeframe for MEXC (excluding 1s)
timeframe_map = {
'1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m',
'1h': '1h', '4h': '4h', '1d': '1d'
}
mexc_timeframe = timeframe_map.get(timeframe)
if mexc_timeframe is None:
logger.warning(f"MEXC doesn't support timeframe {timeframe}, skipping {symbol}")
return None
# MEXC API request
url = "https://api.mexc.com/api/v3/klines"
params = {
'symbol': mexc_symbol,
'interval': mexc_timeframe,
'limit': limit
}
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
# Convert to DataFrame (MEXC uses 8 columns vs Binance's 12)
df = pd.DataFrame(data, columns=[
'timestamp', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_volume'
])
# Process columns
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
for col in ['open', 'high', 'low', 'close', 'volume']:
df[col] = df[col].astype(float)
# Keep only OHLCV columns
df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']]
df = df.sort_values('timestamp').reset_index(drop=True)
logger.info(f"✅ MEXC: Fetched {len(df)} candles for {symbol} {timeframe}")
return df
except Exception as e:
logger.error(f"❌ MEXC: Error fetching data: {e}")
return None
def _fetch_from_binance(self, symbol: str, timeframe: str, limit: int) -> Optional[pd.DataFrame]: def _fetch_from_binance(self, symbol: str, timeframe: str, limit: int) -> Optional[pd.DataFrame]:
"""Fetch data from Binance API""" """Fetch data from Binance API (primary data source)"""
try: try:
# Convert symbol format # Convert symbol format
binance_symbol = symbol.replace('/', '').upper() binance_symbol = symbol.replace('/', '').upper()
@ -331,7 +398,7 @@ class DataProvider:
df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']] df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']]
df = df.sort_values('timestamp').reset_index(drop=True) df = df.sort_values('timestamp').reset_index(drop=True)
logger.info(f"Fetched {len(df)} candles for {symbol} {timeframe}") logger.info(f"Binance: Fetched {len(df)} candles for {symbol} {timeframe}")
return df return df
except Exception as e: except Exception as e:

View File

@ -67,10 +67,28 @@ class TradingExecutor:
api_key = os.getenv('MEXC_API_KEY', self.mexc_config.get('api_key', '')) api_key = os.getenv('MEXC_API_KEY', self.mexc_config.get('api_key', ''))
api_secret = os.getenv('MEXC_SECRET_KEY', self.mexc_config.get('api_secret', '')) api_secret = os.getenv('MEXC_SECRET_KEY', self.mexc_config.get('api_secret', ''))
# Determine trading mode from unified config
trading_mode = self.mexc_config.get('trading_mode', 'simulation')
# Map trading mode to exchange test_mode and execution mode
if trading_mode == 'simulation':
exchange_test_mode = True
self.simulation_mode = True
elif trading_mode == 'testnet':
exchange_test_mode = True
self.simulation_mode = False
elif trading_mode == 'live':
exchange_test_mode = False
self.simulation_mode = False
else:
logger.warning(f"Unknown trading_mode '{trading_mode}', defaulting to simulation")
exchange_test_mode = True
self.simulation_mode = True
self.exchange = MEXCInterface( self.exchange = MEXCInterface(
api_key=api_key, api_key=api_key,
api_secret=api_secret, api_secret=api_secret,
test_mode=self.mexc_config.get('test_mode', True) test_mode=exchange_test_mode
) )
# Trading state # Trading state
@ -80,7 +98,10 @@ class TradingExecutor:
self.daily_loss = 0.0 self.daily_loss = 0.0
self.last_trade_time = {} self.last_trade_time = {}
self.trading_enabled = self.mexc_config.get('enabled', False) self.trading_enabled = self.mexc_config.get('enabled', False)
self.dry_run = self.mexc_config.get('dry_run_mode', True) self.trading_mode = trading_mode
# Legacy compatibility (deprecated)
self.dry_run = self.simulation_mode
# Thread safety # Thread safety
self.lock = Lock() self.lock = Lock()
@ -98,7 +119,8 @@ class TradingExecutor:
return True return True
else: else:
logger.error("Failed to connect to MEXC exchange") logger.error("Failed to connect to MEXC exchange")
self.trading_enabled = False if not self.dry_run:
self.trading_enabled = False
return False return False
except Exception as e: except Exception as e:
logger.error(f"Error connecting to MEXC exchange: {e}") logger.error(f"Error connecting to MEXC exchange: {e}")
@ -204,8 +226,8 @@ class TradingExecutor:
logger.info(f"Executing BUY: {quantity:.6f} {symbol} at ${current_price:.2f} " logger.info(f"Executing BUY: {quantity:.6f} {symbol} at ${current_price:.2f} "
f"(value: ${position_value:.2f}, confidence: {confidence:.2f})") f"(value: ${position_value:.2f}, confidence: {confidence:.2f})")
if self.dry_run: if self.simulation_mode:
logger.info("DRY RUN MODE - Trade logged but not executed") logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Trade logged but not executed")
# Create mock position for tracking # Create mock position for tracking
self.positions[symbol] = Position( self.positions[symbol] = Position(
symbol=symbol, symbol=symbol,
@ -213,7 +235,7 @@ class TradingExecutor:
quantity=quantity, quantity=quantity,
entry_price=current_price, entry_price=current_price,
entry_time=datetime.now(), entry_time=datetime.now(),
order_id=f"dry_run_{int(time.time())}" order_id=f"sim_{int(time.time())}"
) )
self.last_trade_time[symbol] = datetime.now() self.last_trade_time[symbol] = datetime.now()
self.daily_trades += 1 self.daily_trades += 1
@ -264,8 +286,8 @@ class TradingExecutor:
logger.info(f"Executing SELL: {position.quantity:.6f} {symbol} at ${current_price:.2f} " logger.info(f"Executing SELL: {position.quantity:.6f} {symbol} at ${current_price:.2f} "
f"(confidence: {confidence:.2f})") f"(confidence: {confidence:.2f})")
if self.dry_run: if self.simulation_mode:
logger.info("DRY RUN MODE - Trade logged but not executed") logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Trade logged but not executed")
# Calculate P&L # Calculate P&L
pnl = position.calculate_pnl(current_price) pnl = position.calculate_pnl(current_price)

149
debug_mexc_auth.py Normal file
View File

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
Debug script for MEXC API authentication
"""
import os
import hmac
import hashlib
import time
import requests
from urllib.parse import urlencode
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
def debug_mexc_auth():
"""Debug MEXC API authentication step by step"""
api_key = os.getenv('MEXC_API_KEY')
api_secret = os.getenv('MEXC_SECRET_KEY')
print("="*60)
print("MEXC API AUTHENTICATION DEBUG")
print("="*60)
print(f"API Key: {api_key}")
print(f"API Secret: {api_secret[:10]}...{api_secret[-10:]}")
print()
# Test 1: Public API (no auth required)
print("1. Testing Public API (ping)...")
try:
response = requests.get("https://api.mexc.com/api/v3/ping")
print(f" Status: {response.status_code}")
print(f" Response: {response.json()}")
print(" ✅ Public API works")
except Exception as e:
print(f" ❌ Public API failed: {e}")
return
print()
# Test 2: Get server time
print("2. Testing Server Time...")
try:
response = requests.get("https://api.mexc.com/api/v3/time")
server_time_data = response.json()
server_time = server_time_data['serverTime']
print(f" Server Time: {server_time}")
print(" ✅ Server time retrieved")
except Exception as e:
print(f" ❌ Server time failed: {e}")
return
print()
# Test 3: Manual signature generation and account request
print("3. Testing Authentication (manual signature)...")
# Get server time for accurate timestamp
try:
server_response = requests.get("https://api.mexc.com/api/v3/time")
server_time = server_response.json()['serverTime']
print(f" Using Server Time: {server_time}")
except:
server_time = int(time.time() * 1000)
print(f" Using Local Time: {server_time}")
# Parameters for account endpoint
params = {
'timestamp': server_time,
'recvWindow': 10000 # Increased receive window
}
print(f" Timestamp: {server_time}")
print(f" Params: {params}")
# Generate signature manually
# According to MEXC documentation, parameters should be sorted
sorted_params = sorted(params.items())
query_string = urlencode(sorted_params)
print(f" Query String: {query_string}")
# MEXC documentation shows signature in lowercase
signature = hmac.new(
api_secret.encode('utf-8'),
query_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
print(f" Generated Signature (hex): {signature}")
print(f" API Secret used: {api_secret[:5]}...{api_secret[-5:]}")
print(f" Query string length: {len(query_string)}")
print(f" Signature length: {len(signature)}")
print(f" Generated Signature: {signature}")
# Add signature to params
params['signature'] = signature
# Make the request
headers = {
'X-MEXC-APIKEY': api_key
}
print(f" Headers: {headers}")
print(f" Final Params: {params}")
try:
response = requests.get(
"https://api.mexc.com/api/v3/account",
params=params,
headers=headers
)
print(f" Status Code: {response.status_code}")
print(f" Response Headers: {dict(response.headers)}")
if response.status_code == 200:
account_data = response.json()
print(f" ✅ Authentication successful!")
print(f" Account Type: {account_data.get('accountType', 'N/A')}")
print(f" Can Trade: {account_data.get('canTrade', 'N/A')}")
print(f" Can Withdraw: {account_data.get('canWithdraw', 'N/A')}")
print(f" Can Deposit: {account_data.get('canDeposit', 'N/A')}")
print(f" Number of balances: {len(account_data.get('balances', []))}")
# Show USDT balance
for balance in account_data.get('balances', []):
if balance['asset'] == 'USDT':
print(f" 💰 USDT Balance: {balance['free']} (locked: {balance['locked']})")
break
else:
print(f" ❌ Authentication failed!")
print(f" Response: {response.text}")
# Try to parse error
try:
error_data = response.json()
print(f" Error Code: {error_data.get('code', 'N/A')}")
print(f" Error Message: {error_data.get('msg', 'N/A')}")
except:
pass
except Exception as e:
print(f" ❌ Request failed: {e}")
if __name__ == "__main__":
debug_mexc_auth()

93
test_binance_data.py Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Test script to check Binance data availability
"""
import sys
import logging
from datetime import datetime
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def test_binance_data():
"""Test Binance data fetching"""
print("="*60)
print("BINANCE DATA TEST")
print("="*60)
try:
print("1. Testing DataProvider import...")
from core.data_provider import DataProvider
print(" ✅ DataProvider imported successfully")
print("\n2. Creating DataProvider instance...")
dp = DataProvider()
print(f" ✅ DataProvider created")
print(f" Symbols: {dp.symbols}")
print(f" Timeframes: {dp.timeframes}")
print("\n3. Testing historical data fetch...")
try:
data = dp.get_historical_data('ETH/USDT', '1m', 10)
if data is not None:
print(f" ✅ Historical data fetched: {data.shape}")
print(f" Latest price: ${data['close'].iloc[-1]:.2f}")
print(f" Data range: {data.index[0]} to {data.index[-1]}")
else:
print(" ❌ No historical data returned")
except Exception as e:
print(f" ❌ Error fetching historical data: {e}")
print("\n4. Testing current price...")
try:
price = dp.get_current_price('ETH/USDT')
if price:
print(f" ✅ Current price: ${price:.2f}")
else:
print(" ❌ No current price available")
except Exception as e:
print(f" ❌ Error getting current price: {e}")
print("\n5. Testing real-time streaming setup...")
try:
# Check if streaming can be initialized
print(f" Streaming status: {dp.is_streaming}")
print(" ✅ Real-time streaming setup ready")
except Exception as e:
print(f" ❌ Real-time streaming error: {e}")
except Exception as e:
print(f"❌ Failed to import or create DataProvider: {e}")
import traceback
traceback.print_exc()
def test_dashboard_connection():
"""Test if dashboard can connect to data"""
print("\n" + "="*60)
print("DASHBOARD CONNECTION TEST")
print("="*60)
try:
print("1. Testing dashboard imports...")
from web.scalping_dashboard import ScalpingDashboard
print(" ✅ ScalpingDashboard imported")
print("\n2. Testing data provider connection...")
# Check if the dashboard can create a data provider
dashboard = ScalpingDashboard()
if hasattr(dashboard, 'data_provider'):
print(" ✅ Dashboard has data_provider")
print(f" Data provider symbols: {dashboard.data_provider.symbols}")
else:
print(" ❌ Dashboard missing data_provider")
except Exception as e:
print(f"❌ Dashboard connection error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
test_binance_data()
test_dashboard_connection()

View File

@ -0,0 +1 @@

117
test_mexc_new_keys.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""
Test script for MEXC API with new credentials
"""
import os
import sys
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
def test_api_credentials():
"""Test MEXC API credentials step by step"""
print("="*60)
print("MEXC API CREDENTIALS TEST")
print("="*60)
# Check environment variables
api_key = os.getenv('MEXC_API_KEY')
api_secret = os.getenv('MEXC_SECRET_KEY')
print(f"1. Environment Variables:")
print(f" API Key: {api_key[:5]}...{api_key[-5:] if api_key else 'None'}")
print(f" API Secret: {api_secret[:5]}...{api_secret[-5:] if api_secret else 'None'}")
print(f" API Key Length: {len(api_key) if api_key else 0}")
print(f" API Secret Length: {len(api_secret) if api_secret else 0}")
if not api_key or not api_secret:
print("❌ API credentials not found in environment")
return False
# Test public API first
print(f"\n2. Testing Public API (no authentication):")
try:
from NN.exchanges.mexc_interface import MEXCInterface
api = MEXCInterface('dummy', 'dummy', test_mode=False)
ticker = api.get_ticker('ETHUSDT')
if ticker:
print(f" ✅ Public API works: ETH/USDT = ${ticker.get('last', 'N/A')}")
else:
print(f" ❌ Public API failed")
return False
except Exception as e:
print(f" ❌ Public API error: {e}")
return False
# Test private API with actual credentials
print(f"\n3. Testing Private API (with authentication):")
try:
api_auth = MEXCInterface(api_key, api_secret, test_mode=False)
# Try to get account info
account_info = api_auth.get_account_info()
if account_info:
print(f" ✅ Private API works: Account info retrieved")
print(f" 📊 Account Type: {account_info.get('accountType', 'N/A')}")
# Try to get USDT balance
usdt_balance = api_auth.get_balance('USDT')
print(f" 💰 USDT Balance: {usdt_balance}")
return True
else:
print(f" ❌ Private API failed: Could not get account info")
return False
except Exception as e:
print(f" ❌ Private API error: {e}")
return False
def test_api_permissions():
"""Test specific API permissions"""
print(f"\n4. Testing API Permissions:")
api_key = os.getenv('MEXC_API_KEY')
api_secret = os.getenv('MEXC_SECRET_KEY')
try:
from NN.exchanges.mexc_interface import MEXCInterface
api = MEXCInterface(api_key, api_secret, test_mode=False)
# Test spot trading permissions
print(" Testing spot trading permissions...")
# Try to get open orders (requires spot trading permission)
try:
orders = api.get_open_orders('ETHUSDT')
print(" ✅ Spot trading permission: OK")
except Exception as e:
print(f" ❌ Spot trading permission: {e}")
return False
return True
except Exception as e:
print(f" ❌ Permission test error: {e}")
return False
def main():
"""Main test function"""
success = test_api_credentials()
if success:
test_api_permissions()
print(f"\n✅ MEXC API SETUP COMPLETE")
print("The trading system should now work with live MEXC spot trading")
else:
print(f"\n❌ MEXC API SETUP FAILED")
print("Possible issues:")
print("1. API key or secret incorrect")
print("2. API key not activated yet")
print("3. Insufficient permissions (need spot trading)")
print("4. IP address not whitelisted")
print("5. Account verification incomplete")
if __name__ == "__main__":
main()

View File

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

View File

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