code structure
This commit is contained in:
574
core/exchanges/mexc_interface.py
Normal file
574
core/exchanges/mexc_interface.py
Normal file
@ -0,0 +1,574 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional
|
||||
import requests
|
||||
import hmac
|
||||
import hashlib
|
||||
from urllib.parse import urlencode, quote_plus
|
||||
|
||||
from .exchange_interface import ExchangeInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# https://github.com/mexcdevelop/mexc-api-postman/blob/main/MEXC%20V3.postman_collection.json
|
||||
# MEXC V3.postman_collection.json
|
||||
|
||||
class MEXCInterface(ExchangeInterface):
|
||||
"""MEXC Exchange API Interface"""
|
||||
|
||||
def __init__(self, api_key: str = "", api_secret: str = "", test_mode: bool = True, trading_mode: str = 'simulation'):
|
||||
"""Initialize MEXC exchange interface.
|
||||
|
||||
Args:
|
||||
api_key: MEXC API key
|
||||
api_secret: MEXC API secret
|
||||
test_mode: If True, use test/sandbox environment (Note: MEXC doesn't have a true sandbox)
|
||||
trading_mode: 'simulation', 'testnet', or 'live'. Determines API endpoints used.
|
||||
"""
|
||||
super().__init__(api_key, api_secret, test_mode)
|
||||
|
||||
self.trading_mode = trading_mode # Store the trading mode
|
||||
|
||||
# MEXC API Base URLs
|
||||
self.base_url = "https://api.mexc.com" # Live API URL
|
||||
if self.trading_mode == 'testnet':
|
||||
# Note: MEXC does not have a separate testnet for spot trading.
|
||||
# We use the live API for 'testnet' mode and rely on 'simulation' for true dry-runs.
|
||||
logger.warning("MEXC does not have a separate testnet for spot trading. Using live API for 'testnet' mode.")
|
||||
|
||||
self.api_version = "api/v3"
|
||||
self.recv_window = 5000 # 5 seconds window for request validity
|
||||
|
||||
# Session for HTTP requests
|
||||
self.session = requests.Session()
|
||||
|
||||
logger.info(f"MEXCInterface initialized in {self.trading_mode} mode. Ensure correct API endpoints are being used.")
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Test connection to MEXC API by fetching account info."""
|
||||
if not self.api_key or not self.api_secret:
|
||||
logger.error("MEXC API key or secret not set. Cannot connect.")
|
||||
return False
|
||||
|
||||
# Test connection by making a small, authenticated request
|
||||
try:
|
||||
account_info = self.get_account_info()
|
||||
if account_info:
|
||||
logger.info("Successfully connected to MEXC API and retrieved account info.")
|
||||
return True
|
||||
else:
|
||||
logger.error("Failed to connect to MEXC API: Could not retrieve account info.")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Exception during MEXC API connection test: {e}")
|
||||
return False
|
||||
|
||||
def _format_spot_symbol(self, symbol: str) -> str:
|
||||
"""Formats a symbol to MEXC spot API standard and converts USDT to USDC for execution."""
|
||||
if '/' in symbol:
|
||||
base, quote = symbol.split('/')
|
||||
# Convert USDT to USDC for MEXC execution (MEXC API only supports USDC pairs)
|
||||
if quote.upper() == 'USDT':
|
||||
quote = 'USDC'
|
||||
return f"{base.upper()}{quote.upper()}"
|
||||
else:
|
||||
# Convert USDT to USDC for symbols like ETHUSDT -> ETHUSDC
|
||||
if symbol.upper().endswith('USDT'):
|
||||
symbol = symbol.upper().replace('USDT', 'USDC')
|
||||
return symbol.upper()
|
||||
|
||||
def _format_futures_symbol(self, symbol: str) -> str:
|
||||
"""Formats a symbol to MEXC futures API standard (e.g., 'ETH/USDT' -> 'ETH_USDT')."""
|
||||
# This method is included for completeness but should not be used for spot trading
|
||||
return symbol.replace('/', '_').upper()
|
||||
|
||||
def _generate_signature(self, params: Dict[str, Any]) -> str:
|
||||
"""Generate signature for private API calls using MEXC's parameter ordering"""
|
||||
# MEXC uses specific parameter ordering for signature generation
|
||||
# Based on working Postman collection: symbol, side, type, quantity, price, timestamp, recvWindow, then others
|
||||
|
||||
# Remove signature if present
|
||||
clean_params = {k: v for k, v in params.items() if k != 'signature'}
|
||||
|
||||
# MEXC parameter order (from working Postman collection)
|
||||
mexc_order = ['symbol', 'side', 'type', 'quantity', 'price', 'timestamp', 'recvWindow']
|
||||
|
||||
ordered_params = []
|
||||
|
||||
# Add parameters in MEXC's expected order
|
||||
for param_name in mexc_order:
|
||||
if param_name in clean_params:
|
||||
ordered_params.append(f"{param_name}={clean_params[param_name]}")
|
||||
del clean_params[param_name]
|
||||
|
||||
# Add any remaining parameters in alphabetical order
|
||||
for key in sorted(clean_params.keys()):
|
||||
ordered_params.append(f"{key}={clean_params[key]}")
|
||||
|
||||
# Create query string
|
||||
query_string = '&'.join(ordered_params)
|
||||
|
||||
logger.debug(f"MEXC signature query string: {query_string}")
|
||||
|
||||
# Generate HMAC SHA256 signature
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode('utf-8'),
|
||||
query_string.encode('utf-8'),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
logger.debug(f"MEXC signature: {signature}")
|
||||
return signature
|
||||
|
||||
def _send_public_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
||||
"""Send a public API request to MEXC."""
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
url = f"{self.base_url}/{self.api_version}/{endpoint}"
|
||||
|
||||
headers = {'Accept': 'application/json'}
|
||||
|
||||
try:
|
||||
response = requests.request(method, url, params=params, headers=headers, timeout=10)
|
||||
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
logger.error(f"HTTP error in public request to {endpoint}: {response.status_code} {response.reason}")
|
||||
logger.error(f"Response content: {response.text}")
|
||||
return {}
|
||||
except requests.exceptions.ConnectionError as conn_err:
|
||||
logger.error(f"Connection error in public request to {endpoint}: {conn_err}")
|
||||
return {}
|
||||
except requests.exceptions.Timeout as timeout_err:
|
||||
logger.error(f"Timeout error in public request to {endpoint}: {timeout_err}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error in public request to {endpoint}: {e}")
|
||||
return {}
|
||||
|
||||
def _send_private_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||||
"""Send a private request to the exchange with proper signature and MEXC error handling"""
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
|
||||
# Add timestamp and recvWindow to params for signature and request
|
||||
params['timestamp'] = timestamp
|
||||
params['recvWindow'] = str(self.recv_window)
|
||||
|
||||
# Generate signature with all parameters
|
||||
signature = self._generate_signature(params)
|
||||
params['signature'] = signature
|
||||
|
||||
headers = {
|
||||
"X-MEXC-APIKEY": self.api_key
|
||||
}
|
||||
|
||||
# For spot API, use the correct endpoint format
|
||||
if not endpoint.startswith('api/v3/'):
|
||||
endpoint = f"api/v3/{endpoint}"
|
||||
url = f"{self.base_url}/{endpoint}"
|
||||
|
||||
try:
|
||||
if method.upper() == "GET":
|
||||
response = self.session.get(url, headers=headers, params=params, timeout=10)
|
||||
elif method.upper() == "POST":
|
||||
# For POST requests, MEXC expects parameters as query parameters, not form data
|
||||
# Based on Postman collection: Content-Type header is disabled
|
||||
response = self.session.post(url, headers=headers, params=params, timeout=10)
|
||||
elif method.upper() == "DELETE":
|
||||
response = self.session.delete(url, headers=headers, params=params, timeout=10)
|
||||
else:
|
||||
logger.error(f"Unsupported method: {method}")
|
||||
return None
|
||||
|
||||
logger.debug(f"Request URL: {response.url}")
|
||||
logger.debug(f"Response status: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
# Parse error response for specific error codes
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_code = error_data.get('code')
|
||||
error_msg = error_data.get('msg', 'Unknown error')
|
||||
|
||||
# Handle specific MEXC error codes
|
||||
if error_code == 30005: # Oversold
|
||||
logger.warning(f"MEXC Oversold detected (Code 30005) for {endpoint}. This indicates risk control measures are active.")
|
||||
logger.warning(f"Possible causes: Market manipulation detection, abnormal trading patterns, or position limits.")
|
||||
logger.warning(f"Action: Waiting before retry and reducing position size if needed.")
|
||||
|
||||
# For oversold errors, we should not retry immediately
|
||||
# Return a special error structure that the trading executor can handle
|
||||
return {
|
||||
'error': 'oversold',
|
||||
'code': 30005,
|
||||
'message': error_msg,
|
||||
'retry_after': 60 # Suggest waiting 60 seconds
|
||||
}
|
||||
elif error_code == 30001: # Transaction direction not allowed
|
||||
logger.error(f"MEXC: Transaction direction not allowed for {endpoint}")
|
||||
return {
|
||||
'error': 'direction_not_allowed',
|
||||
'code': 30001,
|
||||
'message': error_msg
|
||||
}
|
||||
elif error_code == 30004: # Insufficient position
|
||||
logger.error(f"MEXC: Insufficient position for {endpoint}")
|
||||
return {
|
||||
'error': 'insufficient_position',
|
||||
'code': 30004,
|
||||
'message': error_msg
|
||||
}
|
||||
else:
|
||||
logger.error(f"MEXC API error: Code: {error_code}, Message: {error_msg}")
|
||||
return {
|
||||
'error': 'api_error',
|
||||
'code': error_code,
|
||||
'message': error_msg
|
||||
}
|
||||
except:
|
||||
# Fallback if response is not JSON
|
||||
logger.error(f"API error: Status Code: {response.status_code}, Response: {response.text}")
|
||||
return None
|
||||
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
logger.error(f"HTTP error for {endpoint}: Status Code: {response.status_code}, Response: {response.text}")
|
||||
logger.error(f"HTTP error details: {http_err}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Request error for {endpoint}: {e}")
|
||||
return None
|
||||
|
||||
def get_account_info(self) -> Dict[str, Any]:
|
||||
"""Get account information"""
|
||||
endpoint = "account"
|
||||
result = self._send_private_request("GET", endpoint, {})
|
||||
return result if result is not None else {}
|
||||
|
||||
def get_balance(self, asset: str) -> float:
|
||||
"""Get available balance for a specific asset."""
|
||||
account_info = self.get_account_info()
|
||||
if account_info and 'balances' in account_info:
|
||||
for balance in account_info['balances']:
|
||||
if balance.get('asset') == asset.upper():
|
||||
return float(balance.get('free', 0.0))
|
||||
logger.warning(f"Could not retrieve free balance for {asset}")
|
||||
return 0.0
|
||||
|
||||
def get_ticker(self, symbol: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get ticker information for a symbol."""
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
endpoint = "ticker/24hr"
|
||||
params = {'symbol': formatted_symbol}
|
||||
|
||||
response = self._send_public_request('GET', endpoint, params)
|
||||
|
||||
if response:
|
||||
# MEXC ticker returns a dictionary if single symbol, list if all symbols
|
||||
if isinstance(response, dict):
|
||||
ticker_data = response
|
||||
elif isinstance(response, list) and len(response) > 0:
|
||||
# If the response is a list, try to find the specific symbol
|
||||
found_ticker = None
|
||||
for item in response:
|
||||
if isinstance(item, dict) and item.get('symbol') == formatted_symbol:
|
||||
found_ticker = item
|
||||
break
|
||||
if found_ticker:
|
||||
ticker_data = found_ticker
|
||||
else:
|
||||
logger.error(f"Ticker data for {formatted_symbol} not found in response list.")
|
||||
return None
|
||||
else:
|
||||
logger.error(f"Unexpected ticker response format: {response}")
|
||||
return None
|
||||
|
||||
# Extract relevant info and format for universal use
|
||||
last_price = float(ticker_data.get('lastPrice', 0))
|
||||
bid_price = float(ticker_data.get('bidPrice', 0))
|
||||
ask_price = float(ticker_data.get('askPrice', 0))
|
||||
volume = float(ticker_data.get('volume', 0)) # Base asset volume
|
||||
|
||||
# Determine price change and percent change
|
||||
price_change = float(ticker_data.get('priceChange', 0))
|
||||
price_change_percent = float(ticker_data.get('priceChangePercent', 0))
|
||||
|
||||
logger.info(f"MEXC: Got ticker from {endpoint} for {symbol}: ${last_price:.2f}")
|
||||
|
||||
return {
|
||||
'symbol': formatted_symbol,
|
||||
'last': last_price,
|
||||
'bid': bid_price,
|
||||
'ask': ask_price,
|
||||
'volume': volume,
|
||||
'high': float(ticker_data.get('highPrice', 0)),
|
||||
'low': float(ticker_data.get('lowPrice', 0)),
|
||||
'change': price_change_percent, # This is usually priceChangePercent
|
||||
'exchange': 'MEXC',
|
||||
'raw_data': ticker_data
|
||||
}
|
||||
logger.error(f"Failed to get ticker for {symbol}")
|
||||
return None
|
||||
|
||||
def get_api_symbols(self) -> List[str]:
|
||||
"""Get list of symbols supported for API trading"""
|
||||
try:
|
||||
endpoint = "selfSymbols"
|
||||
result = self._send_private_request("GET", endpoint, {})
|
||||
if result and 'data' in result:
|
||||
return result['data']
|
||||
elif isinstance(result, list):
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"Unexpected response format for API symbols: {result}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting API symbols: {e}")
|
||||
return []
|
||||
|
||||
def is_symbol_supported(self, symbol: str) -> bool:
|
||||
"""Check if a symbol is supported for API trading"""
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
supported_symbols = self.get_api_symbols()
|
||||
return formatted_symbol in supported_symbols
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str, quantity: float, price: Optional[float] = None) -> Dict[str, Any]:
|
||||
"""Place a new order on MEXC."""
|
||||
try:
|
||||
logger.info(f"MEXC: place_order called with symbol={symbol}, side={side}, order_type={order_type}, quantity={quantity}, price={price}")
|
||||
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
logger.info(f"MEXC: Formatted symbol: {symbol} -> {formatted_symbol}")
|
||||
|
||||
# Check if symbol is supported for API trading
|
||||
if not self.is_symbol_supported(symbol):
|
||||
supported_symbols = self.get_api_symbols()
|
||||
logger.error(f"Symbol {formatted_symbol} is not supported for API trading")
|
||||
logger.info(f"Supported symbols include: {supported_symbols[:10]}...") # Show first 10
|
||||
return {}
|
||||
|
||||
# Round quantity to MEXC precision requirements and ensure minimum order value
|
||||
# MEXC ETHUSDC requires precision based on baseAssetPrecision (5 decimals for ETH)
|
||||
original_quantity = quantity
|
||||
if 'ETH' in formatted_symbol:
|
||||
quantity = round(quantity, 5) # MEXC ETHUSDC precision: 5 decimals
|
||||
# Ensure minimum order value (typically $10+ for MEXC)
|
||||
if price and quantity * price < 10.0:
|
||||
quantity = round(10.0 / price, 5) # Adjust to minimum $10 order
|
||||
elif 'BTC' in formatted_symbol:
|
||||
quantity = round(quantity, 6) # MEXC BTCUSDC precision: 6 decimals
|
||||
if price and quantity * price < 10.0:
|
||||
quantity = round(10.0 / price, 6) # Adjust to minimum $10 order
|
||||
else:
|
||||
quantity = round(quantity, 5) # Default precision for MEXC
|
||||
if price and quantity * price < 10.0:
|
||||
quantity = round(10.0 / price, 5) # Adjust to minimum $10 order
|
||||
|
||||
if quantity != original_quantity:
|
||||
logger.info(f"MEXC: Adjusted quantity: {original_quantity} -> {quantity}")
|
||||
|
||||
# MEXC doesn't support MARKET orders for many pairs - use LIMIT orders instead
|
||||
if order_type.upper() == 'MARKET':
|
||||
# Convert market order to limit order with aggressive pricing for immediate execution
|
||||
if price is None:
|
||||
ticker = self.get_ticker(symbol)
|
||||
if ticker and 'last' in ticker:
|
||||
current_price = float(ticker['last'])
|
||||
# For buy orders, use slightly above market to ensure immediate execution
|
||||
# For sell orders, use slightly below market to ensure immediate execution
|
||||
if side.upper() == 'BUY':
|
||||
price = current_price * 1.002 # 0.2% premium for immediate buy execution
|
||||
else:
|
||||
price = current_price * 0.998 # 0.2% discount for immediate sell execution
|
||||
else:
|
||||
logger.error("Cannot get current price for market order conversion")
|
||||
return {}
|
||||
|
||||
# Convert to limit order with immediate execution pricing
|
||||
order_type = 'LIMIT'
|
||||
logger.info(f"MEXC: Converting MARKET to aggressive LIMIT order at ${price:.2f} for immediate execution")
|
||||
|
||||
# Prepare order parameters
|
||||
params = {
|
||||
'symbol': formatted_symbol,
|
||||
'side': side.upper(),
|
||||
'type': order_type.upper(),
|
||||
'quantity': str(quantity) # Quantity must be a string
|
||||
}
|
||||
|
||||
if price is not None:
|
||||
# Format price to remove unnecessary decimal places (e.g., 2900.0 -> 2900)
|
||||
params['price'] = str(int(price)) if price == int(price) else str(price)
|
||||
|
||||
logger.info(f"MEXC: Placing {side.upper()} {order_type.upper()} order for {quantity} {formatted_symbol} at price {price}")
|
||||
logger.info(f"MEXC: Order parameters: {params}")
|
||||
|
||||
# Use the standard private request method which handles timestamp and signature
|
||||
endpoint = "order"
|
||||
result = self._send_private_request("POST", endpoint, params)
|
||||
|
||||
if result:
|
||||
# Check if result contains error information
|
||||
if isinstance(result, dict) and 'error' in result:
|
||||
error_type = result.get('error')
|
||||
error_code = result.get('code')
|
||||
error_msg = result.get('message', 'Unknown error')
|
||||
logger.error(f"MEXC: Order failed with error {error_code}: {error_msg}")
|
||||
return result # Return error result for handling by trading executor
|
||||
else:
|
||||
logger.info(f"MEXC: Order placed successfully: {result}")
|
||||
return result
|
||||
else:
|
||||
logger.error(f"MEXC: Failed to place order - _send_private_request returned None/empty result")
|
||||
logger.error(f"MEXC: Failed order details - symbol: {formatted_symbol}, side: {side}, type: {order_type}, quantity: {quantity}, price: {price}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"MEXC: Exception in place_order: {e}")
|
||||
logger.error(f"MEXC: Exception details - symbol: {symbol}, side: {side}, type: {order_type}, quantity: {quantity}, price: {price}")
|
||||
import traceback
|
||||
logger.error(f"MEXC: Full traceback: {traceback.format_exc()}")
|
||||
return {}
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Cancel an existing order on MEXC."""
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
endpoint = "order"
|
||||
params = {
|
||||
'symbol': formatted_symbol,
|
||||
'orderId': order_id
|
||||
}
|
||||
logger.info(f"MEXC: Cancelling order {order_id} for {formatted_symbol}")
|
||||
try:
|
||||
# MEXC API endpoint for cancelling orders is /api/v3/order (DELETE)
|
||||
cancel_result = self._send_private_request('DELETE', endpoint, params)
|
||||
if cancel_result:
|
||||
logger.info(f"MEXC: Order cancelled successfully: {cancel_result}")
|
||||
return cancel_result
|
||||
else:
|
||||
logger.error(f"MEXC: Error cancelling order: {cancel_result}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"MEXC: Exception cancelling order: {e}")
|
||||
return {}
|
||||
|
||||
def get_order_status(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Get the status of an order on MEXC."""
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
endpoint = "order"
|
||||
params = {
|
||||
'symbol': formatted_symbol,
|
||||
'orderId': order_id
|
||||
}
|
||||
logger.info(f"MEXC: Getting status for order {order_id} for {formatted_symbol}")
|
||||
try:
|
||||
# MEXC API endpoint for order status is /api/v3/order (GET)
|
||||
status_result = self._send_private_request('GET', endpoint, params)
|
||||
if status_result:
|
||||
logger.info(f"MEXC: Order status retrieved: {status_result}")
|
||||
return status_result
|
||||
else:
|
||||
logger.error(f"MEXC: Error getting order status: {status_result}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"MEXC: Exception getting order status: {e}")
|
||||
return {}
|
||||
|
||||
def get_open_orders(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
"""Get all open orders on MEXC for a symbol or all symbols."""
|
||||
endpoint = "openOrders"
|
||||
params = {}
|
||||
if symbol:
|
||||
params['symbol'] = self._format_spot_symbol(symbol)
|
||||
|
||||
logger.info(f"MEXC: Getting open orders for {symbol if symbol else 'all symbols'}")
|
||||
try:
|
||||
# MEXC API endpoint for open orders is /api/v3/openOrders (GET)
|
||||
open_orders = self._send_private_request('GET', endpoint, params)
|
||||
if open_orders and isinstance(open_orders, list):
|
||||
logger.info(f"MEXC: Retrieved {len(open_orders)} open orders.")
|
||||
return open_orders
|
||||
else:
|
||||
logger.error(f"MEXC: Error getting open orders: {open_orders}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"MEXC: Exception getting open orders: {e}")
|
||||
return []
|
||||
|
||||
def get_my_trades(self, symbol: str, limit: int = 100) -> List[Dict[str, Any]]:
|
||||
"""Get trade history for a specific symbol."""
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
endpoint = "myTrades"
|
||||
params = {'symbol': formatted_symbol, 'limit': limit}
|
||||
|
||||
logger.info(f"MEXC: Getting trade history for {formatted_symbol} (limit: {limit})")
|
||||
try:
|
||||
# MEXC API endpoint for trade history is /api/v3/myTrades (GET)
|
||||
trade_history = self._send_private_request('GET', endpoint, params)
|
||||
if trade_history and isinstance(trade_history, list):
|
||||
logger.info(f"MEXC: Retrieved {len(trade_history)} trade records.")
|
||||
return trade_history
|
||||
else:
|
||||
logger.error(f"MEXC: Error getting trade history: {trade_history}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"MEXC: Exception getting trade history: {e}")
|
||||
return []
|
||||
|
||||
def get_server_time(self) -> int:
|
||||
"""Get current MEXC server time in milliseconds."""
|
||||
endpoint = "time"
|
||||
response = self._send_public_request('GET', endpoint)
|
||||
if response and 'serverTime' in response:
|
||||
return int(response['serverTime'])
|
||||
logger.error("Failed to get MEXC server time.")
|
||||
return int(time.time() * 1000) # Fallback to local time
|
||||
|
||||
def get_all_balances(self) -> Dict[str, Dict[str, float]]:
|
||||
"""Get all asset balances from MEXC account."""
|
||||
account_info = self.get_account_info()
|
||||
balances = {}
|
||||
if account_info and 'balances' in account_info:
|
||||
for balance in account_info['balances']:
|
||||
asset = balance.get('asset')
|
||||
free = float(balance.get('free'))
|
||||
locked = float(balance.get('locked'))
|
||||
if asset:
|
||||
balances[asset.upper()] = {'free': free, 'locked': locked, 'total': free + locked}
|
||||
return balances
|
||||
|
||||
def get_trading_fees(self) -> Dict[str, Any]:
|
||||
"""Get current trading fee rates from MEXC API"""
|
||||
endpoint = "account/commission"
|
||||
response = self._send_private_request('GET', endpoint)
|
||||
if response and 'data' in response:
|
||||
fees_data = response['data']
|
||||
return {
|
||||
'maker': float(fees_data.get('makerCommission', 0.0)),
|
||||
'taker': float(fees_data.get('takerCommission', 0.0)),
|
||||
'default': float(fees_data.get('defaultCommission', 0.0))
|
||||
}
|
||||
logger.error("Failed to get trading fees from MEXC API.")
|
||||
return {}
|
||||
|
||||
def get_symbol_trading_fees(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get trading fee rates for a specific symbol from MEXC API"""
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
endpoint = "account/commission"
|
||||
params = {'symbol': formatted_symbol}
|
||||
response = self._send_private_request('GET', endpoint, params)
|
||||
if response and 'data' in response:
|
||||
fees_data = response['data']
|
||||
return {
|
||||
'maker': float(fees_data.get('makerCommission', 0.0)),
|
||||
'taker': float(fees_data.get('takerCommission', 0.0)),
|
||||
'default': float(fees_data.get('defaultCommission', 0.0))
|
||||
}
|
||||
logger.error(f"Failed to get trading fees for {symbol} from MEXC API.")
|
||||
return {}
|
Reference in New Issue
Block a user