mex api progress
This commit is contained in:
@ -23,15 +23,15 @@ class MEXCInterface(ExchangeInterface):
|
||||
"""
|
||||
super().__init__(api_key, api_secret, test_mode)
|
||||
self.base_url = "https://api.mexc.com"
|
||||
self.api_version = "v3"
|
||||
self.api_version = "api/v3"
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to MEXC API."""
|
||||
if not self.api_key or not self.api_secret:
|
||||
logger.warning("MEXC API credentials not provided. Running in read-only mode.")
|
||||
try:
|
||||
# Test public API connection by getting ticker data for BTC/USDT
|
||||
self.get_ticker("BTC/USDT")
|
||||
# Test public API connection by getting server time (ping)
|
||||
self.get_server_time()
|
||||
logger.info("Successfully connected to MEXC API in read-only mode")
|
||||
return True
|
||||
except Exception as e:
|
||||
@ -41,7 +41,7 @@ class MEXCInterface(ExchangeInterface):
|
||||
try:
|
||||
# Test connection by getting account info
|
||||
self.get_account_info()
|
||||
logger.info("Successfully connected to MEXC API")
|
||||
logger.info("Successfully connected to MEXC API with authentication")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to MEXC API: {str(e)}")
|
||||
@ -49,7 +49,9 @@ class MEXCInterface(ExchangeInterface):
|
||||
|
||||
def _generate_signature(self, params: Dict[str, Any]) -> str:
|
||||
"""Generate signature for authenticated requests."""
|
||||
query_string = urlencode(params)
|
||||
# Sort parameters by key for consistent signature generation
|
||||
sorted_params = sorted(params.items())
|
||||
query_string = urlencode(sorted_params)
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode('utf-8'),
|
||||
query_string.encode('utf-8'),
|
||||
@ -81,26 +83,32 @@ class MEXCInterface(ExchangeInterface):
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
# Add timestamp
|
||||
# Add timestamp and recvWindow as required by MEXC
|
||||
params['timestamp'] = int(time.time() * 1000)
|
||||
if 'recvWindow' not in params:
|
||||
params['recvWindow'] = 5000
|
||||
|
||||
# Generate signature
|
||||
# Generate signature using the correct MEXC format
|
||||
signature = self._generate_signature(params)
|
||||
params['signature'] = signature
|
||||
|
||||
# Set headers
|
||||
# Set headers as required by MEXC documentation
|
||||
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}"
|
||||
|
||||
try:
|
||||
if method.upper() == 'GET':
|
||||
# For GET requests, send parameters as query string
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
elif method.upper() == 'POST':
|
||||
response = requests.post(url, json=params, headers=headers)
|
||||
# For POST requests, send as form data in request body per MEXC documentation
|
||||
response = requests.post(url, data=params, headers=headers)
|
||||
elif method.upper() == 'DELETE':
|
||||
# For DELETE requests, send parameters as query string
|
||||
response = requests.delete(url, params=params, headers=headers)
|
||||
else:
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
@ -109,11 +117,23 @@ class MEXCInterface(ExchangeInterface):
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in private request to {endpoint}: {str(e)}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
logger.error(f"Response status: {e.response.status_code}")
|
||||
logger.error(f"Response content: {e.response.text}")
|
||||
raise
|
||||
|
||||
def get_server_time(self) -> Dict[str, Any]:
|
||||
"""Get server time (ping test)."""
|
||||
return self._send_public_request('GET', 'time')
|
||||
|
||||
def ping(self) -> Dict[str, Any]:
|
||||
"""Test connectivity to the Rest API."""
|
||||
return self._send_public_request('GET', 'ping')
|
||||
|
||||
def get_account_info(self) -> Dict[str, Any]:
|
||||
"""Get account information."""
|
||||
return self._send_private_request('GET', 'account')
|
||||
params = {'recvWindow': 5000}
|
||||
return self._send_private_request('GET', 'account', params)
|
||||
|
||||
def get_balance(self, asset: str) -> float:
|
||||
"""Get balance of a specific asset.
|
||||
@ -125,7 +145,8 @@ class MEXCInterface(ExchangeInterface):
|
||||
float: Available balance of the asset
|
||||
"""
|
||||
try:
|
||||
account_info = self._send_private_request('GET', 'account')
|
||||
params = {'recvWindow': 5000}
|
||||
account_info = self._send_private_request('GET', 'account', params)
|
||||
balances = account_info.get('balances', [])
|
||||
|
||||
for balance in balances:
|
||||
@ -148,61 +169,86 @@ class MEXCInterface(ExchangeInterface):
|
||||
dict: Ticker data including price information
|
||||
"""
|
||||
mexc_symbol = symbol.replace('/', '')
|
||||
|
||||
# Use official MEXC API endpoints from documentation
|
||||
endpoints_to_try = [
|
||||
('ticker/price', {'symbol': mexc_symbol}),
|
||||
('ticker', {'symbol': mexc_symbol}),
|
||||
('ticker/24hr', {'symbol': mexc_symbol}),
|
||||
('ticker/bookTicker', {'symbol': mexc_symbol}),
|
||||
('market/ticker', {'symbol': mexc_symbol})
|
||||
('ticker/price', {'symbol': mexc_symbol}), # Symbol Price Ticker
|
||||
('ticker/24hr', {'symbol': mexc_symbol}), # 24hr Ticker Price Change Statistics
|
||||
('ticker/bookTicker', {'symbol': mexc_symbol}), # Symbol Order Book Ticker
|
||||
]
|
||||
|
||||
for endpoint, params in endpoints_to_try:
|
||||
try:
|
||||
logger.info(f"Trying to get ticker from endpoint: {endpoint}")
|
||||
logger.debug(f"Trying MEXC endpoint: {endpoint} for {mexc_symbol}")
|
||||
response = self._send_public_request('GET', endpoint, params)
|
||||
|
||||
if not response:
|
||||
continue
|
||||
|
||||
# Handle the response based on structure
|
||||
if isinstance(response, dict):
|
||||
# Single ticker response
|
||||
ticker = response
|
||||
elif isinstance(response, list) and len(response) > 0:
|
||||
# List of tickers, find the one we want
|
||||
# Find the specific symbol in list response
|
||||
ticker = None
|
||||
for t in response:
|
||||
if t.get('symbol') == mexc_symbol:
|
||||
ticker = t
|
||||
break
|
||||
if ticker is None:
|
||||
continue # Try next endpoint if not found
|
||||
continue
|
||||
else:
|
||||
continue # Try next endpoint if unexpected response
|
||||
continue
|
||||
|
||||
# Convert to a standardized format with defaults for missing fields
|
||||
# Convert to standardized format based on MEXC API response
|
||||
current_time = int(time.time() * 1000)
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'bid': float(ticker.get('bidPrice', ticker.get('bid', 0))),
|
||||
'ask': float(ticker.get('askPrice', ticker.get('ask', 0))),
|
||||
'last': float(ticker.get('price', ticker.get('lastPrice', ticker.get('last', 0)))),
|
||||
'volume': float(ticker.get('volume', ticker.get('quoteVolume', 0))),
|
||||
'timestamp': int(ticker.get('time', ticker.get('closeTime', current_time)))
|
||||
}
|
||||
|
||||
# Ensure we have at least a price
|
||||
# Handle different response formats from different endpoints
|
||||
if 'price' in ticker:
|
||||
# ticker/price endpoint
|
||||
price = float(ticker['price'])
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'bid': price, # Use price as fallback
|
||||
'ask': price, # Use price as fallback
|
||||
'last': price,
|
||||
'volume': 0, # Not available in price endpoint
|
||||
'timestamp': current_time
|
||||
}
|
||||
elif 'lastPrice' in ticker:
|
||||
# ticker/24hr endpoint
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'bid': float(ticker.get('bidPrice', ticker.get('lastPrice', 0))),
|
||||
'ask': float(ticker.get('askPrice', ticker.get('lastPrice', 0))),
|
||||
'last': float(ticker.get('lastPrice', 0)),
|
||||
'volume': float(ticker.get('volume', ticker.get('quoteVolume', 0))),
|
||||
'timestamp': int(ticker.get('closeTime', current_time))
|
||||
}
|
||||
elif 'bidPrice' in ticker:
|
||||
# ticker/bookTicker endpoint
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'bid': float(ticker.get('bidPrice', 0)),
|
||||
'ask': float(ticker.get('askPrice', 0)),
|
||||
'last': float(ticker.get('bidPrice', 0)), # Use bid as fallback for last
|
||||
'volume': 0, # Not available in book ticker
|
||||
'timestamp': current_time
|
||||
}
|
||||
else:
|
||||
continue
|
||||
|
||||
# Validate we have a valid price
|
||||
if result['last'] > 0:
|
||||
logger.info(f"Successfully got ticker from {endpoint} for {symbol}: {result['last']}")
|
||||
logger.info(f"✅ MEXC: Got ticker from {endpoint} for {symbol}: ${result['last']:.2f}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ CRITICAL: Failed to get ticker for {symbol}: {e}")
|
||||
logger.error("❌ NO DUMMY DATA FALLBACK - Real market data required")
|
||||
# Return None instead of dummy data - let calling code handle the failure
|
||||
return None
|
||||
logger.warning(f"MEXC endpoint {endpoint} failed for {symbol}: {e}")
|
||||
continue
|
||||
|
||||
# If we get here, all endpoints failed
|
||||
logger.error(f"All ticker endpoints failed for {symbol}")
|
||||
|
||||
# Return None instead of dummy data - let calling code handle the failure
|
||||
# All endpoints failed
|
||||
logger.error(f"❌ MEXC: All ticker endpoints failed for {symbol}")
|
||||
return None
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
@ -211,8 +257,8 @@ class MEXCInterface(ExchangeInterface):
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
side: Order side ('buy' or 'sell')
|
||||
order_type: Order type ('market', 'limit', etc.)
|
||||
side: Order side ('BUY' or 'SELL')
|
||||
order_type: Order type ('MARKET', 'LIMIT', etc.)
|
||||
quantity: Order quantity
|
||||
price: Order price (for limit orders)
|
||||
|
||||
@ -220,22 +266,30 @@ class MEXCInterface(ExchangeInterface):
|
||||
dict: Order information including order ID
|
||||
"""
|
||||
mexc_symbol = symbol.replace('/', '')
|
||||
|
||||
# Prepare order parameters according to MEXC API
|
||||
params = {
|
||||
'symbol': mexc_symbol,
|
||||
'side': side.upper(),
|
||||
'type': order_type.upper(),
|
||||
'quantity': quantity,
|
||||
'quantity': str(quantity), # MEXC expects string format
|
||||
'recvWindow': 5000
|
||||
}
|
||||
|
||||
if order_type.lower() == 'limit' and price is not None:
|
||||
params['price'] = price
|
||||
# Add price and timeInForce for limit orders
|
||||
if order_type.upper() == 'LIMIT':
|
||||
if price is None:
|
||||
raise ValueError("Price is required for LIMIT orders")
|
||||
params['price'] = str(price)
|
||||
params['timeInForce'] = 'GTC' # Good Till Cancelled
|
||||
|
||||
try:
|
||||
logger.info(f"MEXC: Placing {side} {order_type} order for {symbol}: {quantity} @ {price}")
|
||||
order_result = self._send_private_request('POST', 'order', params)
|
||||
logger.info(f"MEXC: Order placed successfully: {order_result.get('orderId', 'N/A')}")
|
||||
return order_result
|
||||
except Exception as e:
|
||||
logger.error(f"Error placing {side} {order_type} order for {symbol}: {str(e)}")
|
||||
logger.error(f"MEXC: Error placing {side} {order_type} order for {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> bool:
|
||||
|
Reference in New Issue
Block a user