position history with fees
This commit is contained in:
parent
f8681447e3
commit
dd86d21854
@ -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
|
||||||
|
62
closed_trades_history.json
Normal file
62
closed_trades_history.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
149
debug_mexc_auth.py
Normal 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
93
test_binance_data.py
Normal 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()
|
1
test_mexc_data_integration.py
Normal file
1
test_mexc_data_integration.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
117
test_mexc_new_keys.py
Normal file
117
test_mexc_new_keys.py
Normal 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()
|
@ -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")
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user