diff --git a/NN/exchanges/mexc_interface.py b/NN/exchanges/mexc_interface.py index 1596ede..5d07dc1 100644 --- a/NN/exchanges/mexc_interface.py +++ b/NN/exchanges/mexc_interface.py @@ -1,17 +1,23 @@ import logging 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 hmac import hashlib from urllib.parse import urlencode +from datetime import datetime +from threading import Thread, Lock +from collections import deque from .exchange_interface import ExchangeInterface logger = logging.getLogger(__name__) 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): """Initialize MEXC exchange interface. @@ -25,6 +31,247 @@ class MEXCInterface(ExchangeInterface): self.base_url = "https://api.mexc.com" 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: """Connect to MEXC API.""" if not self.api_key or not self.api_secret: @@ -84,9 +331,15 @@ class MEXCInterface(ExchangeInterface): params = {} # 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: - params['recvWindow'] = 5000 + params['recvWindow'] = 10000 # Increased receive window # Generate signature using the correct MEXC format signature = self._generate_signature(params) @@ -94,18 +347,18 @@ class MEXCInterface(ExchangeInterface): # Set headers as required by MEXC documentation headers = { - 'X-MEXC-APIKEY': self.api_key, - 'Content-Type': 'application/x-www-form-urlencoded' + 'X-MEXC-APIKEY': self.api_key } url = f"{self.base_url}/{self.api_version}/{endpoint}" try: 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) 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) elif method.upper() == 'DELETE': # For DELETE requests, send parameters as query string diff --git a/closed_trades_history.json b/closed_trades_history.json new file mode 100644 index 0000000..ecdaded --- /dev/null +++ b/closed_trades_history.json @@ -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 + } +] \ No newline at end of file diff --git a/config.yaml b/config.yaml index 2ffdab3..7c132de 100644 --- a/config.yaml +++ b/config.yaml @@ -148,10 +148,10 @@ mexc_trading: api_secret: "" # Set in .env file as MEXC_SECRET_KEY # Position sizing (conservative for live trading) - max_position_value_usd: 1.0 # Maximum $1 per position for testing - min_position_value_usd: 0.1 # Minimum $0.10 per position - position_size_percent: 0.001 # 0.1% of balance per trade (very conservative) - + max_position_value_usd: 10.0 # Maximum $1 per position for testing + min_position_value_usd: 5 # Minimum $0.10 per position + position_size_percent: 0.01 # 1% of balance per trade (conservative) + # Risk management max_daily_loss_usd: 5.0 # Stop trading if daily loss exceeds $5 max_concurrent_positions: 3 # Only 1 position at a time for testing @@ -247,4 +247,4 @@ backtesting: model_paths: realtime_model: "NN/models/saved/optimized_short_term_model_realtime_best.pt" ticks_model: "NN/models/saved/optimized_short_term_model_ticks_best.pt" - backup_model: "NN/models/saved/realtime_ticks_checkpoints/checkpoint_epoch_50449_backup/model.pt" + backup_model: "NN/models/saved/realtime_ticks_checkpoints/checkpoint_epoch_50449_backup/model.pt" \ No newline at end of file diff --git a/core/data_provider.py b/core/data_provider.py index cb2e3a9..b623237 100644 --- a/core/data_provider.py +++ b/core/data_provider.py @@ -144,9 +144,14 @@ class DataProvider: logger.info(f"Preloading 300s of data for {symbol} {timeframe}") df = self._preload_300s_data(symbol, timeframe) 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}") 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: # Add technical indicators @@ -217,9 +222,14 @@ class DataProvider: 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) + # 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: logger.info(f"Successfully preloaded {len(df)} candles for {symbol} {timeframe}") return df @@ -289,8 +299,65 @@ class DataProvider: logger.error(f"Error in preload_all_symbols_data: {e}") 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]: - """Fetch data from Binance API""" + """Fetch data from Binance API (primary data source)""" try: # Convert symbol format binance_symbol = symbol.replace('/', '').upper() @@ -331,7 +398,7 @@ class DataProvider: df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']] 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 except Exception as e: diff --git a/core/trading_executor.py b/core/trading_executor.py index b300d78..c21ec81 100644 --- a/core/trading_executor.py +++ b/core/trading_executor.py @@ -67,10 +67,28 @@ class TradingExecutor: 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', '')) + # 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( api_key=api_key, api_secret=api_secret, - test_mode=self.mexc_config.get('test_mode', True) + test_mode=exchange_test_mode ) # Trading state @@ -80,7 +98,10 @@ class TradingExecutor: self.daily_loss = 0.0 self.last_trade_time = {} 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 self.lock = Lock() @@ -98,7 +119,8 @@ class TradingExecutor: return True else: logger.error("Failed to connect to MEXC exchange") - self.trading_enabled = False + if not self.dry_run: + self.trading_enabled = False return False except Exception as 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} " f"(value: ${position_value:.2f}, confidence: {confidence:.2f})") - if self.dry_run: - logger.info("DRY RUN MODE - Trade logged but not executed") + if self.simulation_mode: + logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Trade logged but not executed") # Create mock position for tracking self.positions[symbol] = Position( symbol=symbol, @@ -213,7 +235,7 @@ class TradingExecutor: quantity=quantity, entry_price=current_price, 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.daily_trades += 1 @@ -264,8 +286,8 @@ class TradingExecutor: logger.info(f"Executing SELL: {position.quantity:.6f} {symbol} at ${current_price:.2f} " f"(confidence: {confidence:.2f})") - if self.dry_run: - logger.info("DRY RUN MODE - Trade logged but not executed") + if self.simulation_mode: + logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Trade logged but not executed") # Calculate P&L pnl = position.calculate_pnl(current_price) @@ -444,4 +466,4 @@ class TradingExecutor: except Exception as e: logger.error(f"Error getting account balance: {e}") - return {} \ No newline at end of file + return {} diff --git a/debug_mexc_auth.py b/debug_mexc_auth.py new file mode 100644 index 0000000..6e4b34a --- /dev/null +++ b/debug_mexc_auth.py @@ -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() \ No newline at end of file diff --git a/test_binance_data.py b/test_binance_data.py new file mode 100644 index 0000000..1d14e9f --- /dev/null +++ b/test_binance_data.py @@ -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() \ No newline at end of file diff --git a/test_mexc_data_integration.py b/test_mexc_data_integration.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/test_mexc_data_integration.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test_mexc_new_keys.py b/test_mexc_new_keys.py new file mode 100644 index 0000000..0ea1660 --- /dev/null +++ b/test_mexc_new_keys.py @@ -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() \ No newline at end of file diff --git a/web/dashboard.py b/web/dashboard.py index 32d2022..b828c42 100644 --- a/web/dashboard.py +++ b/web/dashboard.py @@ -152,8 +152,8 @@ class TradingDashboard: # Check if trading is enabled and not in dry run mode if not self.trading_executor.trading_enabled: logger.warning("MEXC: Trading not enabled - using default balance") - elif self.trading_executor.dry_run: - logger.warning("MEXC: Dry run mode enabled - using default balance") + elif self.trading_executor.simulation_mode: + logger.warning(f"MEXC: {self.trading_executor.trading_mode.upper()} mode enabled - using default balance") else: # Get USDT balance from MEXC balance_info = self.trading_executor.get_account_balance() @@ -188,7 +188,7 @@ class TradingDashboard: html.I(className="fas fa-chart-line me-2"), "Live Trading Dashboard" ], className="text-white mb-1"), - html.P(f"Ultra-Fast Updates • Portfolio: ${self.starting_balance:,.0f} • {'MEXC Live' if (self.trading_executor and self.trading_executor.trading_enabled and not self.trading_executor.dry_run) else 'Demo Mode'}", + html.P(f"Ultra-Fast Updates • Portfolio: ${self.starting_balance:,.0f} • {'MEXC Live' if (self.trading_executor and self.trading_executor.trading_enabled and not self.trading_executor.simulation_mode) else 'Demo Mode'}", className="text-light mb-0 opacity-75 small") ], className="bg-dark p-2 mb-2"), @@ -219,6 +219,13 @@ class TradingDashboard: ], className="card-body text-center p-2") ], className="card bg-light", style={"height": "60px"}), + html.Div([ + html.Div([ + html.H5(id="total-fees", className="text-warning mb-0 small"), + html.P("Total Fees", className="text-muted mb-0 tiny") + ], className="card-body text-center p-2") + ], className="card bg-light", style={"height": "60px"}), + html.Div([ html.Div([ html.H5(id="current-position", className="text-info mb-0 small"), @@ -246,7 +253,7 @@ class TradingDashboard: html.P("MEXC API", className="text-muted mb-0 tiny") ], className="card-body text-center p-2") ], className="card bg-light", style={"height": "60px"}), - ], style={"display": "grid", "gridTemplateColumns": "repeat(3, 1fr)", "gap": "8px", "width": "50%"}), + ], style={"display": "grid", "gridTemplateColumns": "repeat(4, 1fr)", "gap": "8px", "width": "60%"}), # Right side - Recent Signals & Executions html.Div([ @@ -350,6 +357,7 @@ class TradingDashboard: Output('current-price', 'children'), Output('session-pnl', 'children'), Output('session-pnl', 'className'), + Output('total-fees', 'children'), Output('current-position', 'children'), Output('current-position', 'className'), Output('trade-count', 'children'), @@ -511,6 +519,9 @@ class TradingDashboard: pnl_text = f"${total_session_pnl:.2f}" pnl_class = "text-success mb-0 small" if total_session_pnl >= 0 else "text-danger mb-0 small" + # Total fees formatting + fees_text = f"${self.total_fees:.2f}" + # Position info with real-time unrealized PnL and proper color coding if self.current_position: pos_side = self.current_position['side'] @@ -540,8 +551,8 @@ class TradingDashboard: # MEXC status with detailed information if self.trading_executor and self.trading_executor.trading_enabled: - if self.trading_executor.dry_run: - mexc_status = "DRY RUN" + if self.trading_executor.simulation_mode: + mexc_status = f"{self.trading_executor.trading_mode.upper()} MODE" else: mexc_status = "LIVE" else: @@ -597,7 +608,7 @@ class TradingDashboard: closed_trades_table = [html.P("Closed trades data unavailable", className="text-muted")] return ( - price_text, pnl_text, pnl_class, position_text, position_class, trade_count_text, portfolio_text, mexc_status, + price_text, pnl_text, pnl_class, fees_text, position_text, position_class, trade_count_text, portfolio_text, mexc_status, price_chart, training_metrics, decisions_list, session_perf, closed_trades_table, system_status['icon_class'], system_status['title'], system_status['details'] ) @@ -608,7 +619,7 @@ class TradingDashboard: empty_fig = self._create_empty_chart("Error", "Dashboard error - check logs") return ( - "Error", "$0.00", "text-muted mb-0 small", "None", "text-muted", "0", "$10,000.00", "OFFLINE", + "Error", "$0.00", "text-muted mb-0 small", "$0.00", "None", "text-muted", "0", "$10,000.00", "OFFLINE", empty_fig, [html.P("Error loading training metrics", className="text-danger")], [html.P("Error loading decisions", className="text-danger")], @@ -1414,6 +1425,7 @@ class TradingDashboard: decision['mexc_executed'] = mexc_success # Calculate position size based on confidence and configuration + current_price = decision.get('price', 0) if current_price and current_price > 0: # Get position sizing from trading executor configuration if self.trading_executor: @@ -1756,9 +1768,12 @@ class TradingDashboard: if not self.closed_trades: return [html.P("No closed trades yet", className="text-muted text-center")] - # Create table rows for recent closed trades + # Create table rows for recent closed trades (newest first) table_rows = [] - for trade in self.closed_trades[-20:]: # Show last 20 trades + recent_trades = self.closed_trades[-20:] # Get last 20 trades + recent_trades.reverse() # Newest first + + for trade in recent_trades: # Determine row color based on P&L row_class = "table-success" if trade['net_pnl'] >= 0 else "table-danger" @@ -1768,12 +1783,18 @@ class TradingDashboard: # Format side color side_color = "text-success" if trade['side'] == 'LONG' else "text-danger" + # Format position size + position_size = trade.get('size', 0) + size_display = f"{position_size:.4f}" if position_size < 1 else f"{position_size:.2f}" + table_rows.append( html.Tr([ html.Td(f"#{trade['trade_id']}", className="small"), html.Td(trade['side'], className=f"small fw-bold {side_color}"), + html.Td(size_display, className="small text-info"), html.Td(f"${trade['entry_price']:.2f}", className="small"), html.Td(f"${trade['exit_price']:.2f}", className="small"), + html.Td(f"${trade.get('fees', 0):.2f}", className="small text-warning"), html.Td(f"${trade['net_pnl']:.2f}", className="small fw-bold"), html.Td(duration_str, className="small"), html.Td("✓" if trade.get('mexc_executed', False) else "SIM", @@ -1787,8 +1808,10 @@ class TradingDashboard: html.Tr([ html.Th("ID", className="small"), html.Th("Side", className="small"), + html.Th("Size", className="small"), html.Th("Entry", className="small"), html.Th("Exit", className="small"), + html.Th("Fees", className="small"), html.Th("P&L", className="small"), html.Th("Duration", className="small"), html.Th("MEXC", className="small") @@ -1801,12 +1824,14 @@ class TradingDashboard: total_trades = len(self.closed_trades) winning_trades = len([t for t in self.closed_trades if t['net_pnl'] > 0]) total_pnl = sum(t['net_pnl'] for t in self.closed_trades) + total_fees_closed = sum(t.get('fees', 0) for t in self.closed_trades) win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0 summary = html.Div([ html.Small([ html.Strong(f"Total: {total_trades} | "), html.Span(f"Win Rate: {win_rate:.1f}% | ", className="text-info"), + html.Span(f"Fees: ${total_fees_closed:.2f} | ", className="text-warning"), html.Span(f"Total P&L: ${total_pnl:.2f}", className="text-success" if total_pnl >= 0 else "text-danger") ], className="d-block mb-2") diff --git a/web/scalping_dashboard.py b/web/scalping_dashboard.py index bc0ec92..63af3b0 100644 --- a/web/scalping_dashboard.py +++ b/web/scalping_dashboard.py @@ -49,18 +49,23 @@ class TradingSession: self.starting_balance = 100.0 # $100 USD starting balance self.current_balance = self.starting_balance self.total_pnl = 0.0 + self.total_fees = 0.0 # Track total fees paid (opening + closing) self.total_trades = 0 self.winning_trades = 0 self.losing_trades = 0 - self.positions = {} # symbol -> {'size': float, 'entry_price': float, 'side': str} + self.positions = {} # symbol -> {'size': float, 'entry_price': float, 'side': str, 'fees': float} self.trade_history = [] self.last_action = None self.trading_executor = trading_executor + # Fee configuration - MEXC spot trading fees + self.fee_rate = 0.001 # 0.1% trading fee (typical for MEXC spot) + logger.info(f"NEW TRADING SESSION STARTED WITH MEXC INTEGRATION") logger.info(f"Session ID: {self.session_id}") logger.info(f"Starting Balance: ${self.starting_balance:.2f}") logger.info(f"MEXC Trading: {'ENABLED' if trading_executor and trading_executor.trading_enabled else 'DISABLED'}") + logger.info(f"Trading Fee Rate: {self.fee_rate*100:.1f}%") logger.info(f"Start Time: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}") def execute_trade(self, action: TradingAction, current_price: float): @@ -105,14 +110,20 @@ class TradingSession: if action.action == 'BUY': # Close any existing short position if symbol in self.positions and self.positions[symbol]['side'] == 'SHORT': - self._close_position(symbol, current_price, 'BUY') + pnl = self._close_position(symbol, current_price, 'BUY') + trade_info['pnl'] = pnl + + # Open new long position with opening fee + opening_fee = current_price * position_size * self.fee_rate + self.total_fees += opening_fee - # Open new long position self.positions[symbol] = { 'size': position_size, 'entry_price': current_price, - 'side': 'LONG' + 'side': 'LONG', + 'fees': opening_fee # Track opening fee } + trade_info['opening_fee'] = opening_fee trade_info['pnl'] = 0 # No immediate P&L on entry elif action.action == 'SELL': @@ -121,12 +132,17 @@ class TradingSession: pnl = self._close_position(symbol, current_price, 'SELL') trade_info['pnl'] = pnl else: - # Open new short position + # Open new short position with opening fee + opening_fee = current_price * position_size * self.fee_rate + self.total_fees += opening_fee + self.positions[symbol] = { 'size': position_size, 'entry_price': current_price, - 'side': 'SHORT' + 'side': 'SHORT', + 'fees': opening_fee # Track opening fee } + trade_info['opening_fee'] = opening_fee trade_info['pnl'] = 0 elif action.action == 'HOLD': @@ -154,7 +170,7 @@ class TradingSession: return None def _close_position(self, symbol: str, exit_price: float, close_action: str) -> float: - """Close an existing position and calculate P&L""" + """Close an existing position and calculate P&L with fees""" if symbol not in self.positions: return 0.0 @@ -162,18 +178,27 @@ class TradingSession: entry_price = position['entry_price'] size = position['size'] side = position['side'] + opening_fee = position.get('fees', 0.0) - # Calculate P&L + # Calculate closing fee + closing_fee = exit_price * size * self.fee_rate + total_fees = opening_fee + closing_fee + self.total_fees += closing_fee + + # Calculate gross P&L if side == 'LONG': - pnl = (exit_price - entry_price) * size + gross_pnl = (exit_price - entry_price) * size else: # SHORT - pnl = (entry_price - exit_price) * size + gross_pnl = (entry_price - exit_price) * size + + # Calculate net P&L (after fees) + net_pnl = gross_pnl - total_fees # Update session P&L - self.total_pnl += pnl + self.total_pnl += net_pnl - # Track win/loss - if pnl > 0: + # Track win/loss based on net P&L + if net_pnl > 0: self.winning_trades += 1 else: self.losing_trades += 1 @@ -183,9 +208,10 @@ class TradingSession: logger.info(f"CHART: POSITION CLOSED: {side} {symbol}") logger.info(f"CHART: Entry: ${entry_price:.2f} | Exit: ${exit_price:.2f}") - logger.info(f"MONEY: Trade P&L: ${pnl:+.2f}") + logger.info(f"FEES: Opening: ${opening_fee:.4f} | Closing: ${closing_fee:.4f} | Total: ${total_fees:.4f}") + logger.info(f"MONEY: Gross P&L: ${gross_pnl:+.2f} | Net P&L: ${net_pnl:+.2f}") - return pnl + return net_pnl def get_win_rate(self) -> float: """Calculate current win rate""" @@ -203,6 +229,7 @@ class TradingSession: 'starting_balance': self.starting_balance, 'current_balance': self.current_balance, 'total_pnl': self.total_pnl, + 'total_fees': self.total_fees, 'total_trades': self.total_trades, 'winning_trades': self.winning_trades, 'losing_trades': self.losing_trades, @@ -605,22 +632,27 @@ class RealTimeScalpingDashboard: html.Div([ html.H3(id="live-pnl", className="text-success"), html.P("Session P&L", className="text-white") - ], className="col-md-3 text-center"), + ], className="col-md-2 text-center"), + + html.Div([ + html.H3(id="total-fees", className="text-warning"), + html.P("Total Fees", className="text-white") + ], className="col-md-2 text-center"), html.Div([ html.H3(id="win-rate", className="text-info"), html.P("Win Rate", className="text-white") - ], className="col-md-3 text-center"), + ], className="col-md-2 text-center"), html.Div([ html.H3(id="total-trades", className="text-primary"), html.P("Total Trades", className="text-white") - ], className="col-md-3 text-center"), + ], className="col-md-2 text-center"), html.Div([ html.H3(id="last-action", className="text-warning"), html.P("Last Action", className="text-white") - ], className="col-md-3 text-center") + ], className="col-md-4 text-center") ], className="col-md-4"), # Middle - Price displays (2 columns, 2/12 width) @@ -732,6 +764,7 @@ class RealTimeScalpingDashboard: Output('session-duration', 'children'), Output('open-positions', 'children'), Output('live-pnl', 'children'), + Output('total-fees', 'children'), Output('win-rate', 'children'), Output('total-trades', 'children'), Output('last-action', 'children'), @@ -823,8 +856,8 @@ class RealTimeScalpingDashboard: # MEXC status if dashboard_instance.trading_executor and dashboard_instance.trading_executor.trading_enabled: mexc_status = "LIVE" - elif dashboard_instance.trading_executor and dashboard_instance.trading_executor.dry_run: - mexc_status = "DRY RUN" + elif dashboard_instance.trading_executor and dashboard_instance.trading_executor.simulation_mode: + mexc_status = f"{dashboard_instance.trading_executor.trading_mode.upper()} MODE" else: mexc_status = "OFFLINE" @@ -2579,4 +2612,4 @@ def create_scalping_dashboard(data_provider=None, orchestrator=None, trading_exe return RealTimeScalpingDashboard(data_provider, orchestrator, trading_executor) # For backward compatibility -ScalpingDashboard = RealTimeScalpingDashboard \ No newline at end of file +ScalpingDashboard = RealTimeScalpingDashboard