mexc interface integrations REST API fixes
This commit is contained in:
3184
NN/exchanges/mexc/mexc_postman_dump.json
Normal file
3184
NN/exchanges/mexc/mexc_postman_dump.json
Normal file
File diff suppressed because it is too large
Load Diff
170
NN/exchanges/mexc/test_live_trading.py
Normal file
170
NN/exchanges/mexc/test_live_trading.py
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test Live Trading - Verify MEXC Connection and Trading
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from core.trading_executor import TradingExecutor
|
||||||
|
from core.config import get_config
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def test_live_trading():
|
||||||
|
"""Test live trading functionality"""
|
||||||
|
try:
|
||||||
|
logger.info("=== LIVE TRADING TEST ===")
|
||||||
|
logger.info("Testing MEXC connection and account balance reading")
|
||||||
|
|
||||||
|
# Initialize trading executor
|
||||||
|
logger.info("Initializing Trading Executor...")
|
||||||
|
executor = TradingExecutor("config.yaml")
|
||||||
|
|
||||||
|
# Check trading mode
|
||||||
|
logger.info(f"Trading Mode: {executor.trading_mode}")
|
||||||
|
logger.info(f"Simulation Mode: {executor.simulation_mode}")
|
||||||
|
logger.info(f"Trading Enabled: {executor.trading_enabled}")
|
||||||
|
|
||||||
|
if executor.simulation_mode:
|
||||||
|
logger.warning("WARNING: Still in simulation mode. Check config.yaml")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 1: Get account balance
|
||||||
|
logger.info("\n=== TEST 1: ACCOUNT BALANCE ===")
|
||||||
|
try:
|
||||||
|
balances = executor.get_account_balance()
|
||||||
|
logger.info("Account Balances:")
|
||||||
|
|
||||||
|
total_value = 0.0
|
||||||
|
for asset, balance_info in balances.items():
|
||||||
|
if balance_info['total'] > 0:
|
||||||
|
logger.info(f" {asset}: {balance_info['total']:.6f} ({balance_info['type']})")
|
||||||
|
if asset in ['USDT', 'USDC', 'USD']:
|
||||||
|
total_value += balance_info['total']
|
||||||
|
|
||||||
|
logger.info(f"Total USD Value: ${total_value:.2f}")
|
||||||
|
|
||||||
|
if total_value < 25:
|
||||||
|
logger.warning(f"Account balance ${total_value:.2f} may be insufficient for testing")
|
||||||
|
else:
|
||||||
|
logger.info(f"Account balance ${total_value:.2f} looks good for testing")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting account balance: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 2: Get current ETH price
|
||||||
|
logger.info("\n=== TEST 2: MARKET DATA ===")
|
||||||
|
try:
|
||||||
|
# Test getting current price for ETH/USDT
|
||||||
|
if executor.exchange:
|
||||||
|
ticker = executor.exchange.get_ticker("ETH/USDT")
|
||||||
|
if ticker and 'last' in ticker:
|
||||||
|
current_price = ticker['last']
|
||||||
|
logger.info(f"Current ETH/USDT Price: ${current_price:.2f}")
|
||||||
|
else:
|
||||||
|
logger.error("Failed to get ETH/USDT ticker data")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
logger.error("Exchange interface not available")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting market data: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 3: Calculate position sizing
|
||||||
|
logger.info("\n=== TEST 3: POSITION SIZING ===")
|
||||||
|
try:
|
||||||
|
# Test position size calculation with different confidence levels
|
||||||
|
test_confidences = [0.3, 0.5, 0.7, 0.9]
|
||||||
|
|
||||||
|
for confidence in test_confidences:
|
||||||
|
position_size = executor._calculate_position_size(confidence, current_price)
|
||||||
|
quantity = position_size / current_price
|
||||||
|
logger.info(f"Confidence {confidence:.1f}: ${position_size:.2f} = {quantity:.6f} ETH")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating position sizes: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 4: Small test trade (optional - requires confirmation)
|
||||||
|
logger.info("\n=== TEST 4: TEST TRADE (OPTIONAL) ===")
|
||||||
|
|
||||||
|
user_input = input("Do you want to execute a SMALL test trade? (type 'YES' to confirm): ")
|
||||||
|
if user_input.upper() == 'YES':
|
||||||
|
try:
|
||||||
|
logger.info("Executing SMALL test BUY order...")
|
||||||
|
|
||||||
|
# Execute a very small buy order with low confidence (minimum position size)
|
||||||
|
success = executor.execute_signal(
|
||||||
|
symbol="ETH/USDT",
|
||||||
|
action="BUY",
|
||||||
|
confidence=0.3, # Low confidence = minimum position size
|
||||||
|
current_price=current_price
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info("✅ Test BUY order executed successfully!")
|
||||||
|
|
||||||
|
# Wait a moment, then try to sell
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
logger.info("Executing corresponding SELL order...")
|
||||||
|
success = executor.execute_signal(
|
||||||
|
symbol="ETH/USDT",
|
||||||
|
action="SELL",
|
||||||
|
confidence=0.9, # High confidence to ensure execution
|
||||||
|
current_price=current_price
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info("✅ Test SELL order executed successfully!")
|
||||||
|
logger.info("✅ Full test trade cycle completed!")
|
||||||
|
else:
|
||||||
|
logger.warning("❌ Test SELL order failed")
|
||||||
|
else:
|
||||||
|
logger.warning("❌ Test BUY order failed")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing test trade: {e}")
|
||||||
|
else:
|
||||||
|
logger.info("Test trade skipped")
|
||||||
|
|
||||||
|
# Test 5: Position and trade history
|
||||||
|
logger.info("\n=== TEST 5: POSITIONS AND HISTORY ===")
|
||||||
|
try:
|
||||||
|
positions = executor.get_positions()
|
||||||
|
trade_history = executor.get_trade_history()
|
||||||
|
|
||||||
|
logger.info(f"Current Positions: {len(positions)}")
|
||||||
|
for symbol, position in positions.items():
|
||||||
|
logger.info(f" {symbol}: {position.side} {position.quantity:.6f} @ ${position.entry_price:.2f}")
|
||||||
|
|
||||||
|
logger.info(f"Trade History: {len(trade_history)} trades")
|
||||||
|
for trade in trade_history[-5:]: # Last 5 trades
|
||||||
|
pnl_str = f"${trade.pnl:+.2f}" if trade.pnl else "$0.00"
|
||||||
|
logger.info(f" {trade.symbol} {trade.side}: {pnl_str}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting positions/history: {e}")
|
||||||
|
|
||||||
|
logger.info("\n=== LIVE TRADING TEST COMPLETED ===")
|
||||||
|
logger.info("If all tests passed, live trading is ready!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in live trading test: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(test_live_trading())
|
@ -65,45 +65,48 @@ class MEXCInterface(ExchangeInterface):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _format_spot_symbol(self, symbol: str) -> str:
|
def _format_spot_symbol(self, symbol: str) -> str:
|
||||||
"""Formats a symbol to MEXC spot API standard (e.g., 'ETH/USDT' -> 'ETHUSDC')."""
|
"""Formats a symbol to MEXC spot API standard and converts USDT to USDC for execution."""
|
||||||
if '/' in symbol:
|
if '/' in symbol:
|
||||||
base, quote = symbol.split('/')
|
base, quote = symbol.split('/')
|
||||||
# Convert USDT to USDC for MEXC spot trading
|
# Convert USDT to USDC for MEXC execution (MEXC API only supports USDC pairs)
|
||||||
if quote.upper() == 'USDT':
|
if quote.upper() == 'USDT':
|
||||||
quote = 'USDC'
|
quote = 'USDC'
|
||||||
return f"{base.upper()}{quote.upper()}"
|
return f"{base.upper()}{quote.upper()}"
|
||||||
else:
|
else:
|
||||||
# Convert USDT to USDC for symbols like ETHUSDT
|
# Convert USDT to USDC for symbols like ETHUSDT -> ETHUSDC
|
||||||
symbol = symbol.upper()
|
if symbol.upper().endswith('USDT'):
|
||||||
if symbol.endswith('USDT'):
|
symbol = symbol.upper().replace('USDT', 'USDC')
|
||||||
symbol = symbol.replace('USDT', 'USDC')
|
return symbol.upper()
|
||||||
return symbol
|
|
||||||
|
|
||||||
def _format_futures_symbol(self, symbol: str) -> str:
|
def _format_futures_symbol(self, symbol: str) -> str:
|
||||||
"""Formats a symbol to MEXC futures API standard (e.g., 'ETH/USDT' -> 'ETH_USDT')."""
|
"""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
|
# This method is included for completeness but should not be used for spot trading
|
||||||
return symbol.replace('/', '_').upper()
|
return symbol.replace('/', '_').upper()
|
||||||
|
|
||||||
def _generate_signature(self, timestamp: str, method: str, endpoint: str, params: Dict[str, Any]) -> str:
|
def _generate_signature(self, params: Dict[str, Any]) -> str:
|
||||||
"""Generate signature for private API calls using MEXC's expected parameter order"""
|
"""Generate signature for private API calls using MEXC's parameter ordering"""
|
||||||
# MEXC requires specific parameter ordering, not alphabetical
|
# MEXC uses specific parameter ordering for signature generation
|
||||||
# Based on successful test: symbol, side, type, quantity, timestamp, then other params
|
# Based on working Postman collection: symbol, side, type, quantity, price, timestamp, recvWindow, then others
|
||||||
mexc_param_order = ['symbol', 'side', 'type', 'quantity', 'timestamp', 'recvWindow']
|
|
||||||
|
# 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']
|
||||||
|
|
||||||
# Build ordered parameter list
|
|
||||||
ordered_params = []
|
ordered_params = []
|
||||||
|
|
||||||
# Add parameters in MEXC's expected order
|
# Add parameters in MEXC's expected order
|
||||||
for param_name in mexc_param_order:
|
for param_name in mexc_order:
|
||||||
if param_name in params and param_name != 'signature':
|
if param_name in clean_params:
|
||||||
ordered_params.append(f"{param_name}={params[param_name]}")
|
ordered_params.append(f"{param_name}={clean_params[param_name]}")
|
||||||
|
del clean_params[param_name]
|
||||||
|
|
||||||
# Add any remaining parameters not in the standard order (alphabetically)
|
# Add any remaining parameters in alphabetical order
|
||||||
remaining_params = {k: v for k, v in params.items() if k not in mexc_param_order and k != 'signature'}
|
for key in sorted(clean_params.keys()):
|
||||||
for key in sorted(remaining_params.keys()):
|
ordered_params.append(f"{key}={clean_params[key]}")
|
||||||
ordered_params.append(f"{key}={remaining_params[key]}")
|
|
||||||
|
|
||||||
# Create query string (MEXC doesn't use the api_key + timestamp prefix)
|
# Create query string
|
||||||
query_string = '&'.join(ordered_params)
|
query_string = '&'.join(ordered_params)
|
||||||
|
|
||||||
logger.debug(f"MEXC signature query string: {query_string}")
|
logger.debug(f"MEXC signature query string: {query_string}")
|
||||||
@ -118,7 +121,7 @@ class MEXCInterface(ExchangeInterface):
|
|||||||
logger.debug(f"MEXC signature: {signature}")
|
logger.debug(f"MEXC signature: {signature}")
|
||||||
return signature
|
return signature
|
||||||
|
|
||||||
def _send_public_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
def _send_public_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
||||||
"""Send a public API request to MEXC."""
|
"""Send a public API request to MEXC."""
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
@ -145,46 +148,52 @@ class MEXCInterface(ExchangeInterface):
|
|||||||
logger.error(f"Error in public request to {endpoint}: {e}")
|
logger.error(f"Error in public request to {endpoint}: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _send_private_request(self, method: str, endpoint: str, params: Dict[str, Any] = None) -> Optional[Dict[str, Any]]:
|
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"""
|
"""Send a private request to the exchange with proper signature"""
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
timestamp = str(int(time.time() * 1000))
|
timestamp = str(int(time.time() * 1000))
|
||||||
|
|
||||||
# Add timestamp and recvWindow to params for signature and request
|
# Add timestamp and recvWindow to params for signature and request
|
||||||
params['timestamp'] = timestamp
|
params['timestamp'] = timestamp
|
||||||
params['recvWindow'] = self.recv_window
|
params['recvWindow'] = str(self.recv_window)
|
||||||
signature = self._generate_signature(timestamp, method, endpoint, params)
|
|
||||||
|
# Generate signature with all parameters
|
||||||
|
signature = self._generate_signature(params)
|
||||||
params['signature'] = signature
|
params['signature'] = signature
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"X-MEXC-APIKEY": self.api_key,
|
"X-MEXC-APIKEY": self.api_key
|
||||||
"Request-Time": timestamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# For spot API, use the correct endpoint format
|
# For spot API, use the correct endpoint format
|
||||||
if not endpoint.startswith('api/v3/'):
|
if not endpoint.startswith('api/v3/'):
|
||||||
endpoint = f"api/v3/{endpoint}"
|
endpoint = f"api/v3/{endpoint}"
|
||||||
url = f"{self.base_url}/{endpoint}"
|
url = f"{self.base_url}/{endpoint}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if method.upper() == "GET":
|
if method.upper() == "GET":
|
||||||
response = self.session.get(url, headers=headers, params=params, timeout=10)
|
response = self.session.get(url, headers=headers, params=params, timeout=10)
|
||||||
elif method.upper() == "POST":
|
elif method.upper() == "POST":
|
||||||
# MEXC expects POST parameters as query string, not in body
|
# 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)
|
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:
|
else:
|
||||||
logger.error(f"Unsupported method: {method}")
|
logger.error(f"Unsupported method: {method}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
response.raise_for_status()
|
logger.debug(f"Request URL: {response.url}")
|
||||||
data = response.json()
|
logger.debug(f"Response status: {response.status_code}")
|
||||||
# For successful responses, return the data directly
|
|
||||||
# MEXC doesn't always use 'success' field for successful operations
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return data
|
return response.json()
|
||||||
else:
|
else:
|
||||||
logger.error(f"API error: Status Code: {response.status_code}, Response: {response.text}")
|
logger.error(f"API error: Status Code: {response.status_code}, Response: {response.text}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as http_err:
|
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 for {endpoint}: Status Code: {response.status_code}, Response: {response.text}")
|
||||||
logger.error(f"HTTP error details: {http_err}")
|
logger.error(f"HTTP error details: {http_err}")
|
||||||
@ -223,7 +232,11 @@ class MEXCInterface(ExchangeInterface):
|
|||||||
ticker_data = response
|
ticker_data = response
|
||||||
elif isinstance(response, list) and len(response) > 0:
|
elif isinstance(response, list) and len(response) > 0:
|
||||||
# If the response is a list, try to find the specific symbol
|
# If the response is a list, try to find the specific symbol
|
||||||
found_ticker = next((item for item in response if item.get('symbol') == formatted_symbol), None)
|
found_ticker = None
|
||||||
|
for item in response:
|
||||||
|
if isinstance(item, dict) and item.get('symbol') == formatted_symbol:
|
||||||
|
found_ticker = item
|
||||||
|
break
|
||||||
if found_ticker:
|
if found_ticker:
|
||||||
ticker_data = found_ticker
|
ticker_data = found_ticker
|
||||||
else:
|
else:
|
||||||
@ -293,38 +306,66 @@ class MEXCInterface(ExchangeInterface):
|
|||||||
logger.info(f"Supported symbols include: {supported_symbols[:10]}...") # Show first 10
|
logger.info(f"Supported symbols include: {supported_symbols[:10]}...") # Show first 10
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
endpoint = "order"
|
# Round quantity to MEXC precision requirements and ensure minimum order value
|
||||||
|
# MEXC ETHUSDC requires precision based on baseAssetPrecision (5 decimals for ETH)
|
||||||
|
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
|
||||||
|
|
||||||
params: Dict[str, Any] = {
|
# 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,
|
'symbol': formatted_symbol,
|
||||||
'side': side.upper(),
|
'side': side.upper(),
|
||||||
'type': order_type.upper(),
|
'type': order_type.upper(),
|
||||||
'quantity': str(quantity) # Quantity must be a string
|
'quantity': str(quantity) # Quantity must be a string
|
||||||
}
|
}
|
||||||
|
|
||||||
if price is not None:
|
if price is not None:
|
||||||
params['price'] = str(price) # Price must be a string for limit orders
|
# 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: Placing {side.upper()} {order_type.upper()} order for {quantity} {formatted_symbol} at price {price}")
|
||||||
|
|
||||||
# For market orders, some parameters might be optional or handled differently.
|
# Use the standard private request method which handles timestamp and signature
|
||||||
# Check MEXC API docs for market order specifics (e.g., quoteOrderQty for buy market orders)
|
endpoint = "order"
|
||||||
if order_type.upper() == 'MARKET' and side.upper() == 'BUY':
|
result = self._send_private_request("POST", endpoint, params)
|
||||||
# If it's a market buy order, MEXC often expects quoteOrderQty instead of quantity
|
|
||||||
# Assuming quantity here refers to the base asset, if quoteOrderQty is needed, adjust.
|
|
||||||
# For now, we will stick to quantity and let MEXC handle the conversion if possible
|
|
||||||
pass # No specific change needed based on the current params structure
|
|
||||||
|
|
||||||
try:
|
if result:
|
||||||
# MEXC API endpoint for placing orders is /api/v3/order (POST)
|
logger.info(f"MEXC: Order placed successfully: {result}")
|
||||||
order_result = self._send_private_request('POST', endpoint, params)
|
return result
|
||||||
if order_result:
|
|
||||||
logger.info(f"MEXC: Order placed successfully: {order_result}")
|
|
||||||
return order_result
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"MEXC: Error placing order: {order_result}")
|
logger.error(f"MEXC: Failed to place order")
|
||||||
return {}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"MEXC: Exception placing order: {e}")
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def cancel_order(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
def cancel_order(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||||
|
@ -1,86 +1,76 @@
|
|||||||
import requests
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Check ETHUSDC Trading Rules and Precision
|
||||||
|
"""
|
||||||
|
|
||||||
# Check ETHUSDC precision requirements on MEXC
|
import os
|
||||||
try:
|
import sys
|
||||||
# Get symbol information from MEXC
|
from pathlib import Path
|
||||||
resp = requests.get('https://api.mexc.com/api/v3/exchangeInfo')
|
|
||||||
data = resp.json()
|
|
||||||
|
|
||||||
print('=== ETHUSDC SYMBOL INFORMATION ===')
|
# Add project root to path
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
# Find ETHUSDC symbol
|
from NN.exchanges.mexc_interface import MEXCInterface
|
||||||
ethusdc_info = None
|
|
||||||
for symbol_info in data.get('symbols', []):
|
|
||||||
if symbol_info['symbol'] == 'ETHUSDC':
|
|
||||||
ethusdc_info = symbol_info
|
|
||||||
break
|
|
||||||
|
|
||||||
if ethusdc_info:
|
def check_ethusdc_precision():
|
||||||
print(f'Symbol: {ethusdc_info["symbol"]}')
|
"""Check ETHUSDC trading rules"""
|
||||||
print(f'Status: {ethusdc_info["status"]}')
|
print("Checking ETHUSDC Trading Rules...")
|
||||||
print(f'Base Asset: {ethusdc_info["baseAsset"]}')
|
print("=" * 50)
|
||||||
print(f'Quote Asset: {ethusdc_info["quoteAsset"]}')
|
|
||||||
print(f'Base Asset Precision: {ethusdc_info["baseAssetPrecision"]}')
|
|
||||||
print(f'Quote Asset Precision: {ethusdc_info["quoteAssetPrecision"]}')
|
|
||||||
|
|
||||||
# Check order types
|
# Get API credentials
|
||||||
order_types = ethusdc_info.get('orderTypes', [])
|
api_key = os.getenv('MEXC_API_KEY', '')
|
||||||
print(f'Allowed Order Types: {order_types}')
|
api_secret = os.getenv('MEXC_SECRET_KEY', '')
|
||||||
|
|
||||||
# Check filters for quantity and price precision
|
if not api_key or not api_secret:
|
||||||
print('\nFilters:')
|
print("❌ No MEXC API credentials found")
|
||||||
for filter_info in ethusdc_info.get('filters', []):
|
return
|
||||||
filter_type = filter_info['filterType']
|
|
||||||
print(f' {filter_type}:')
|
|
||||||
for key, value in filter_info.items():
|
|
||||||
if key != 'filterType':
|
|
||||||
print(f' {key}: {value}')
|
|
||||||
|
|
||||||
# Calculate proper quantity precision
|
# Create MEXC interface
|
||||||
print('\n=== QUANTITY FORMATTING RECOMMENDATIONS ===')
|
mexc = MEXCInterface(api_key=api_key, api_secret=api_secret, test_mode=False)
|
||||||
|
|
||||||
# Find LOT_SIZE filter for minimum order size
|
if not mexc.connect():
|
||||||
lot_size_filter = None
|
print("❌ Failed to connect to MEXC API")
|
||||||
min_notional_filter = None
|
return
|
||||||
for filter_info in ethusdc_info.get('filters', []):
|
|
||||||
if filter_info['filterType'] == 'LOT_SIZE':
|
|
||||||
lot_size_filter = filter_info
|
|
||||||
elif filter_info['filterType'] == 'MIN_NOTIONAL':
|
|
||||||
min_notional_filter = filter_info
|
|
||||||
|
|
||||||
if lot_size_filter:
|
print("✅ Connected to MEXC API")
|
||||||
step_size = lot_size_filter['stepSize']
|
|
||||||
min_qty = lot_size_filter['minQty']
|
|
||||||
max_qty = lot_size_filter['maxQty']
|
|
||||||
print(f'Min Quantity: {min_qty}')
|
|
||||||
print(f'Max Quantity: {max_qty}')
|
|
||||||
print(f'Step Size: {step_size}')
|
|
||||||
|
|
||||||
# Count decimal places in step size to determine precision
|
# Check if ETHUSDC is supported
|
||||||
decimal_places = len(step_size.split('.')[-1].rstrip('0')) if '.' in step_size else 0
|
if mexc.is_symbol_supported("ETH/USDT"): # Will be converted to ETHUSDC
|
||||||
print(f'Required decimal places: {decimal_places}')
|
print("✅ ETHUSDC is supported for trading")
|
||||||
|
|
||||||
# Test formatting our problematic quantity
|
|
||||||
test_quantity = 0.0028169119884018344
|
|
||||||
formatted_quantity = round(test_quantity, decimal_places)
|
|
||||||
print(f'Original quantity: {test_quantity}')
|
|
||||||
print(f'Formatted quantity: {formatted_quantity}')
|
|
||||||
print(f'String format: {formatted_quantity:.{decimal_places}f}')
|
|
||||||
|
|
||||||
# Check if our quantity meets minimum
|
|
||||||
if formatted_quantity < float(min_qty):
|
|
||||||
print(f'❌ Quantity {formatted_quantity} is below minimum {min_qty}')
|
|
||||||
min_value_needed = float(min_qty) * 2665 # Approximate ETH price
|
|
||||||
print(f'💡 Need at least ${min_value_needed:.2f} to place minimum order')
|
|
||||||
else:
|
else:
|
||||||
print(f'✅ Quantity {formatted_quantity} meets minimum requirement')
|
print("❌ ETHUSDC is not supported for trading")
|
||||||
|
return
|
||||||
|
|
||||||
if min_notional_filter:
|
# Get current ticker to see price
|
||||||
min_notional = min_notional_filter['minNotional']
|
ticker = mexc.get_ticker("ETH/USDT") # Will query ETHUSDC
|
||||||
print(f'Minimum Notional Value: ${min_notional}')
|
if ticker:
|
||||||
|
price = ticker.get('last', 0)
|
||||||
|
print(f"Current ETHUSDC Price: ${price:.2f}")
|
||||||
|
|
||||||
else:
|
# Test different quantities to find minimum
|
||||||
print('❌ ETHUSDC symbol not found in exchange info')
|
test_quantities = [
|
||||||
|
0.001, # $3 worth
|
||||||
|
0.01, # $30 worth
|
||||||
|
0.1, # $300 worth
|
||||||
|
0.009871, # Our calculated quantity
|
||||||
|
]
|
||||||
|
|
||||||
except Exception as e:
|
print("\nTesting different quantities:")
|
||||||
print(f'Error: {e}')
|
print("-" * 30)
|
||||||
|
|
||||||
|
for qty in test_quantities:
|
||||||
|
rounded_qty = round(qty, 6)
|
||||||
|
value_usd = rounded_qty * price if ticker else 0
|
||||||
|
print(f"Quantity: {rounded_qty:8.6f} ETH (~${value_usd:.2f})")
|
||||||
|
|
||||||
|
print(f"\nOur test quantity: 0.009871 ETH")
|
||||||
|
print(f"Rounded to 6 decimals: {round(0.009871, 6):.6f} ETH")
|
||||||
|
print(f"Value: ~${round(0.009871, 6) * price:.2f}")
|
||||||
|
|
||||||
|
print("\nNext steps:")
|
||||||
|
print("1. Check if minimum order value is $10+ USD")
|
||||||
|
print("2. Try with a larger quantity (0.01 ETH = ~$30)")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
check_ethusdc_precision()
|
77
check_mexc_symbols.py
Normal file
77
check_mexc_symbols.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Check MEXC Available Trading Symbols
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from core.trading_executor import TradingExecutor
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def check_mexc_symbols():
|
||||||
|
"""Check available trading symbols on MEXC"""
|
||||||
|
try:
|
||||||
|
logger.info("=== MEXC SYMBOL AVAILABILITY CHECK ===")
|
||||||
|
|
||||||
|
# Initialize trading executor
|
||||||
|
executor = TradingExecutor("config.yaml")
|
||||||
|
|
||||||
|
if not executor.exchange:
|
||||||
|
logger.error("Failed to initialize exchange")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get all supported symbols
|
||||||
|
logger.info("Fetching all supported symbols from MEXC...")
|
||||||
|
supported_symbols = executor.exchange.get_api_symbols()
|
||||||
|
|
||||||
|
logger.info(f"Total supported symbols: {len(supported_symbols)}")
|
||||||
|
|
||||||
|
# Filter ETH-related symbols
|
||||||
|
eth_symbols = [s for s in supported_symbols if 'ETH' in s]
|
||||||
|
logger.info(f"ETH-related symbols ({len(eth_symbols)}):")
|
||||||
|
for symbol in sorted(eth_symbols):
|
||||||
|
logger.info(f" {symbol}")
|
||||||
|
|
||||||
|
# Filter USDT pairs
|
||||||
|
usdt_symbols = [s for s in supported_symbols if s.endswith('USDT')]
|
||||||
|
logger.info(f"USDT pairs ({len(usdt_symbols)}):")
|
||||||
|
for symbol in sorted(usdt_symbols)[:20]: # Show first 20
|
||||||
|
logger.info(f" {symbol}")
|
||||||
|
if len(usdt_symbols) > 20:
|
||||||
|
logger.info(f" ... and {len(usdt_symbols) - 20} more")
|
||||||
|
|
||||||
|
# Filter USDC pairs
|
||||||
|
usdc_symbols = [s for s in supported_symbols if s.endswith('USDC')]
|
||||||
|
logger.info(f"USDC pairs ({len(usdc_symbols)}):")
|
||||||
|
for symbol in sorted(usdc_symbols):
|
||||||
|
logger.info(f" {symbol}")
|
||||||
|
|
||||||
|
# Check specific symbols we're interested in
|
||||||
|
test_symbols = ['ETHUSDT', 'ETHUSDC', 'BTCUSDT', 'BTCUSDC']
|
||||||
|
logger.info("Checking specific symbols:")
|
||||||
|
for symbol in test_symbols:
|
||||||
|
if symbol in supported_symbols:
|
||||||
|
logger.info(f" ✅ {symbol} - SUPPORTED")
|
||||||
|
else:
|
||||||
|
logger.info(f" ❌ {symbol} - NOT SUPPORTED")
|
||||||
|
|
||||||
|
# Show a sample of all available symbols
|
||||||
|
logger.info("Sample of all available symbols:")
|
||||||
|
for symbol in sorted(supported_symbols)[:30]:
|
||||||
|
logger.info(f" {symbol}")
|
||||||
|
if len(supported_symbols) > 30:
|
||||||
|
logger.info(f" ... and {len(supported_symbols) - 30} more")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking MEXC symbols: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
check_mexc_symbols()
|
@ -159,7 +159,7 @@ trading:
|
|||||||
# MEXC Trading API Configuration
|
# MEXC Trading API Configuration
|
||||||
mexc_trading:
|
mexc_trading:
|
||||||
enabled: true
|
enabled: true
|
||||||
trading_mode: simulation # simulation, testnet, live
|
trading_mode: live # simulation, testnet, live
|
||||||
|
|
||||||
# Position sizing as percentage of account balance
|
# Position sizing as percentage of account balance
|
||||||
base_position_percent: 5.0 # 5% base position of account
|
base_position_percent: 5.0 # 5% base position of account
|
||||||
|
@ -214,38 +214,40 @@ class TradingExecutor:
|
|||||||
# Determine the quote asset (e.g., USDT, USDC) from the symbol
|
# Determine the quote asset (e.g., USDT, USDC) from the symbol
|
||||||
if '/' in symbol:
|
if '/' in symbol:
|
||||||
quote_asset = symbol.split('/')[1].upper() # Assuming symbol is like ETH/USDT
|
quote_asset = symbol.split('/')[1].upper() # Assuming symbol is like ETH/USDT
|
||||||
# Convert USDT to USDC for MEXC spot trading
|
# Keep USDT as USDT for MEXC spot trading (no conversion needed)
|
||||||
if quote_asset == 'USDT':
|
|
||||||
quote_asset = 'USDC'
|
|
||||||
else:
|
else:
|
||||||
# Fallback for symbols like ETHUSDT (assuming last 4 chars are quote)
|
# Fallback for symbols like ETHUSDT (assuming last 4 chars are quote)
|
||||||
quote_asset = symbol[-4:].upper()
|
quote_asset = symbol[-4:].upper()
|
||||||
# Convert USDT to USDC for MEXC spot trading
|
# Keep USDT as USDT for MEXC spot trading (no conversion needed)
|
||||||
if quote_asset == 'USDT':
|
|
||||||
quote_asset = 'USDC'
|
|
||||||
|
|
||||||
# Calculate required capital for the trade
|
# Calculate required capital for the trade
|
||||||
# If we are selling (to open a short position), we need collateral based on the position size
|
# If we are selling (to open a short position), we need collateral based on the position size
|
||||||
# For simplicity, assume required capital is the full position value in USD
|
# For simplicity, assume required capital is the full position value in USD
|
||||||
required_capital = self._calculate_position_size(confidence, current_price)
|
required_capital = self._calculate_position_size(confidence, current_price)
|
||||||
|
|
||||||
# Get available balance for the quote asset
|
# Get available balance for the quote asset (try USDT first, then USDC as fallback)
|
||||||
|
if quote_asset == 'USDT':
|
||||||
|
available_balance = self.exchange.get_balance('USDT')
|
||||||
|
if available_balance < required_capital:
|
||||||
|
# If USDT balance is insufficient, check USDC as fallback
|
||||||
|
usdc_balance = self.exchange.get_balance('USDC')
|
||||||
|
if usdc_balance >= required_capital:
|
||||||
|
available_balance = usdc_balance
|
||||||
|
quote_asset = 'USDC' # Use USDC instead
|
||||||
|
logger.info(f"BALANCE CHECK: Using USDC fallback balance for {symbol}")
|
||||||
|
else:
|
||||||
available_balance = self.exchange.get_balance(quote_asset)
|
available_balance = self.exchange.get_balance(quote_asset)
|
||||||
|
|
||||||
# If USDC balance is insufficient, check USDT as fallback (for MEXC compatibility)
|
|
||||||
if available_balance < required_capital and quote_asset == 'USDC':
|
|
||||||
usdt_balance = self.exchange.get_balance('USDT')
|
|
||||||
if usdt_balance >= required_capital:
|
|
||||||
available_balance = usdt_balance
|
|
||||||
quote_asset = 'USDT' # Use USDT instead
|
|
||||||
logger.info(f"BALANCE CHECK: Using USDT fallback balance for {symbol}")
|
|
||||||
|
|
||||||
logger.info(f"BALANCE CHECK: Symbol: {symbol}, Action: {action}, Required: ${required_capital:.2f} {quote_asset}, Available: ${available_balance:.2f} {quote_asset}")
|
logger.info(f"BALANCE CHECK: Symbol: {symbol}, Action: {action}, Required: ${required_capital:.2f} {quote_asset}, Available: ${available_balance:.2f} {quote_asset}")
|
||||||
|
|
||||||
if available_balance < required_capital:
|
# Allow some small precision tolerance (1 cent) and ensure sufficient balance
|
||||||
|
balance_tolerance = 0.01
|
||||||
|
if available_balance < (required_capital - balance_tolerance):
|
||||||
logger.warning(f"Trade blocked for {symbol} {action}: Insufficient {quote_asset} balance. "
|
logger.warning(f"Trade blocked for {symbol} {action}: Insufficient {quote_asset} balance. "
|
||||||
f"Required: ${required_capital:.2f}, Available: ${available_balance:.2f}")
|
f"Required: ${required_capital:.2f}, Available: ${available_balance:.2f}")
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
logger.info(f"BALANCE CHECK PASSED: {quote_asset} balance sufficient for trade")
|
||||||
elif self.simulation_mode:
|
elif self.simulation_mode:
|
||||||
logger.debug(f"SIMULATION MODE: Skipping balance check for {symbol} {action} - allowing trade for model training")
|
logger.debug(f"SIMULATION MODE: Skipping balance check for {symbol} {action} - allowing trade for model training")
|
||||||
# --- End Balance check ---
|
# --- End Balance check ---
|
||||||
@ -765,34 +767,25 @@ class TradingExecutor:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _calculate_position_size(self, confidence: float, current_price: float) -> float:
|
def _calculate_position_size(self, confidence: float, current_price: float) -> float:
|
||||||
"""Calculate position size based on percentage of account balance, confidence, and leverage"""
|
"""Calculate position size - use 100% of account balance for short-term scalping"""
|
||||||
# Get account balance (simulation or real)
|
# Get account balance (simulation or real)
|
||||||
account_balance = self._get_account_balance_for_sizing()
|
account_balance = self._get_account_balance_for_sizing()
|
||||||
|
|
||||||
# Get position sizing percentages
|
# Use 100% of account balance since we're holding for seconds/minutes only
|
||||||
max_percent = self.mexc_config.get('max_position_percent', 20.0) / 100.0
|
# Scale by confidence: 70-100% of balance based on confidence (0.7-1.0 range)
|
||||||
min_percent = self.mexc_config.get('min_position_percent', 2.0) / 100.0
|
confidence_multiplier = max(0.7, min(1.0, confidence))
|
||||||
base_percent = self.mexc_config.get('base_position_percent', 5.0) / 100.0
|
position_value = account_balance * confidence_multiplier
|
||||||
leverage = self.mexc_config.get('leverage', 50.0)
|
|
||||||
|
|
||||||
# Scale position size by confidence
|
# Apply reduction based on consecutive losses (risk management)
|
||||||
position_percent = min(max_percent, max(min_percent, base_percent * confidence))
|
|
||||||
position_value = account_balance * position_percent
|
|
||||||
|
|
||||||
# Apply leverage to get effective position size
|
|
||||||
leveraged_position_value = position_value * leverage
|
|
||||||
|
|
||||||
# Apply reduction based on consecutive losses
|
|
||||||
reduction_factor = self.mexc_config.get('consecutive_loss_reduction_factor', 0.8)
|
reduction_factor = self.mexc_config.get('consecutive_loss_reduction_factor', 0.8)
|
||||||
adjusted_reduction_factor = reduction_factor ** self.consecutive_losses
|
adjusted_reduction_factor = reduction_factor ** self.consecutive_losses
|
||||||
leveraged_position_value *= adjusted_reduction_factor
|
position_value *= adjusted_reduction_factor
|
||||||
|
|
||||||
logger.debug(f"Position calculation: account=${account_balance:.2f}, "
|
logger.debug(f"Position calculation: account=${account_balance:.2f}, "
|
||||||
f"percent={position_percent*100:.1f}%, base=${position_value:.2f}, "
|
f"confidence_mult={confidence_multiplier:.2f}, "
|
||||||
f"leverage={leverage}x, effective=${leveraged_position_value:.2f}, "
|
f"position=${position_value:.2f}, confidence={confidence:.2f}")
|
||||||
f"confidence={confidence:.2f}")
|
|
||||||
|
|
||||||
return leveraged_position_value
|
return position_value
|
||||||
|
|
||||||
def _get_account_balance_for_sizing(self) -> float:
|
def _get_account_balance_for_sizing(self) -> float:
|
||||||
"""Get account balance for position sizing calculations"""
|
"""Get account balance for position sizing calculations"""
|
||||||
|
97
get_mexc_exchange_info.py
Normal file
97
get_mexc_exchange_info.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Get MEXC Exchange Info for ETHUSDC
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
def get_mexc_exchange_info():
|
||||||
|
"""Get detailed MEXC exchange information for ETHUSDC"""
|
||||||
|
print("Getting MEXC Exchange Info for ETHUSDC...")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get exchange info from MEXC public API
|
||||||
|
resp = requests.get('https://api.mexc.com/api/v3/exchangeInfo')
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
# Find ETHUSDC symbol
|
||||||
|
ethusdc_info = None
|
||||||
|
for symbol_info in data.get('symbols', []):
|
||||||
|
if symbol_info['symbol'] == 'ETHUSDC':
|
||||||
|
ethusdc_info = symbol_info
|
||||||
|
break
|
||||||
|
|
||||||
|
if not ethusdc_info:
|
||||||
|
print("❌ ETHUSDC symbol not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("✅ Found ETHUSDC symbol information")
|
||||||
|
print(f"Symbol: {ethusdc_info['symbol']}")
|
||||||
|
print(f"Status: {ethusdc_info['status']}")
|
||||||
|
print(f"Base Asset: {ethusdc_info['baseAsset']}")
|
||||||
|
print(f"Quote Asset: {ethusdc_info['quoteAsset']}")
|
||||||
|
print(f"Base Precision: {ethusdc_info['baseAssetPrecision']}")
|
||||||
|
print(f"Quote Precision: {ethusdc_info['quoteAssetPrecision']}")
|
||||||
|
|
||||||
|
print("\nFilters:")
|
||||||
|
print("-" * 20)
|
||||||
|
|
||||||
|
lot_size_filter = None
|
||||||
|
min_notional_filter = None
|
||||||
|
|
||||||
|
for filter_info in ethusdc_info.get('filters', []):
|
||||||
|
filter_type = filter_info['filterType']
|
||||||
|
print(f"\n{filter_type}:")
|
||||||
|
|
||||||
|
if filter_type == 'LOT_SIZE':
|
||||||
|
lot_size_filter = filter_info
|
||||||
|
elif filter_type == 'MIN_NOTIONAL':
|
||||||
|
min_notional_filter = filter_info
|
||||||
|
|
||||||
|
for key, value in filter_info.items():
|
||||||
|
if key != 'filterType':
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Analyze requirements
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("ANALYSIS:")
|
||||||
|
|
||||||
|
if lot_size_filter:
|
||||||
|
min_qty = float(lot_size_filter['minQty'])
|
||||||
|
step_size = lot_size_filter['stepSize']
|
||||||
|
|
||||||
|
print(f"Minimum Quantity: {min_qty} ETH")
|
||||||
|
print(f"Step Size: {step_size}")
|
||||||
|
|
||||||
|
# Check our test quantity
|
||||||
|
test_qty = 0.009871
|
||||||
|
print(f"\nOur quantity: {test_qty}")
|
||||||
|
print(f"Meets minimum?: {'✅ Yes' if test_qty >= min_qty else '❌ No'}")
|
||||||
|
|
||||||
|
# Check step size compliance
|
||||||
|
if '.' in step_size:
|
||||||
|
decimal_places = len(step_size.split('.')[-1].rstrip('0'))
|
||||||
|
properly_rounded = round(test_qty, decimal_places)
|
||||||
|
print(f"Required decimals: {decimal_places}")
|
||||||
|
print(f"Properly rounded: {properly_rounded}")
|
||||||
|
|
||||||
|
if min_notional_filter:
|
||||||
|
min_notional = float(min_notional_filter['minNotional'])
|
||||||
|
test_value = 0.009871 * 3031 # approximate price
|
||||||
|
print(f"\nMinimum Order Value: ${min_notional}")
|
||||||
|
print(f"Our order value: ${test_value:.2f}")
|
||||||
|
print(f"Meets minimum?: {'✅ Yes' if test_value >= min_notional else '❌ No'}")
|
||||||
|
|
||||||
|
print("\n💡 RECOMMENDATIONS:")
|
||||||
|
if lot_size_filter and min_notional_filter:
|
||||||
|
safe_qty = max(float(lot_size_filter['minQty']) * 1.1, min_notional / 3000)
|
||||||
|
print(f"Use quantity >= {safe_qty:.6f} ETH for safe trading")
|
||||||
|
print(f"This equals ~${safe_qty * 3031:.2f} USD")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
get_mexc_exchange_info()
|
170
test_live_trading.py
Normal file
170
test_live_trading.py
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test Live Trading - Verify MEXC Connection and Trading
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from core.trading_executor import TradingExecutor
|
||||||
|
from core.config import get_config
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def test_live_trading():
|
||||||
|
"""Test live trading functionality"""
|
||||||
|
try:
|
||||||
|
logger.info("=== LIVE TRADING TEST ===")
|
||||||
|
logger.info("Testing MEXC connection and account balance reading")
|
||||||
|
|
||||||
|
# Initialize trading executor
|
||||||
|
logger.info("Initializing Trading Executor...")
|
||||||
|
executor = TradingExecutor("config.yaml")
|
||||||
|
|
||||||
|
# Check trading mode
|
||||||
|
logger.info(f"Trading Mode: {executor.trading_mode}")
|
||||||
|
logger.info(f"Simulation Mode: {executor.simulation_mode}")
|
||||||
|
logger.info(f"Trading Enabled: {executor.trading_enabled}")
|
||||||
|
|
||||||
|
if executor.simulation_mode:
|
||||||
|
logger.warning("WARNING: Still in simulation mode. Check config.yaml")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 1: Get account balance
|
||||||
|
logger.info("\n=== TEST 1: ACCOUNT BALANCE ===")
|
||||||
|
try:
|
||||||
|
balances = executor.get_account_balance()
|
||||||
|
logger.info("Account Balances:")
|
||||||
|
|
||||||
|
total_value = 0.0
|
||||||
|
for asset, balance_info in balances.items():
|
||||||
|
if balance_info['total'] > 0:
|
||||||
|
logger.info(f" {asset}: {balance_info['total']:.6f} ({balance_info['type']})")
|
||||||
|
if asset in ['USDT', 'USDC', 'USD']:
|
||||||
|
total_value += balance_info['total']
|
||||||
|
|
||||||
|
logger.info(f"Total USD Value: ${total_value:.2f}")
|
||||||
|
|
||||||
|
if total_value < 25:
|
||||||
|
logger.warning(f"Account balance ${total_value:.2f} may be insufficient for testing")
|
||||||
|
else:
|
||||||
|
logger.info(f"Account balance ${total_value:.2f} looks good for testing")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting account balance: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 2: Get current ETH price
|
||||||
|
logger.info("\n=== TEST 2: MARKET DATA ===")
|
||||||
|
try:
|
||||||
|
# Test getting current price for ETH/USDT
|
||||||
|
if executor.exchange:
|
||||||
|
ticker = executor.exchange.get_ticker("ETH/USDT")
|
||||||
|
if ticker and 'last' in ticker:
|
||||||
|
current_price = ticker['last']
|
||||||
|
logger.info(f"Current ETH/USDT Price: ${current_price:.2f}")
|
||||||
|
else:
|
||||||
|
logger.error("Failed to get ETH/USDT ticker data")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
logger.error("Exchange interface not available")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting market data: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 3: Calculate position sizing
|
||||||
|
logger.info("\n=== TEST 3: POSITION SIZING ===")
|
||||||
|
try:
|
||||||
|
# Test position size calculation with different confidence levels
|
||||||
|
test_confidences = [0.3, 0.5, 0.7, 0.9]
|
||||||
|
|
||||||
|
for confidence in test_confidences:
|
||||||
|
position_size = executor._calculate_position_size(confidence, current_price)
|
||||||
|
quantity = position_size / current_price
|
||||||
|
logger.info(f"Confidence {confidence:.1f}: ${position_size:.2f} = {quantity:.6f} ETH")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating position sizes: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 4: Small test trade (optional - requires confirmation)
|
||||||
|
logger.info("\n=== TEST 4: TEST TRADE (OPTIONAL) ===")
|
||||||
|
|
||||||
|
user_input = input("Do you want to execute a SMALL test trade? (type 'YES' to confirm): ")
|
||||||
|
if user_input.upper() == 'YES':
|
||||||
|
try:
|
||||||
|
logger.info("Executing SMALL test BUY order...")
|
||||||
|
|
||||||
|
# Execute a very small buy order with low confidence (minimum position size)
|
||||||
|
success = executor.execute_signal(
|
||||||
|
symbol="ETH/USDT",
|
||||||
|
action="BUY",
|
||||||
|
confidence=0.3, # Low confidence = minimum position size
|
||||||
|
current_price=current_price
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info("✅ Test BUY order executed successfully!")
|
||||||
|
|
||||||
|
# Wait a moment, then try to sell
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
logger.info("Executing corresponding SELL order...")
|
||||||
|
success = executor.execute_signal(
|
||||||
|
symbol="ETH/USDT",
|
||||||
|
action="SELL",
|
||||||
|
confidence=0.9, # High confidence to ensure execution
|
||||||
|
current_price=current_price
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info("✅ Test SELL order executed successfully!")
|
||||||
|
logger.info("✅ Full test trade cycle completed!")
|
||||||
|
else:
|
||||||
|
logger.warning("❌ Test SELL order failed")
|
||||||
|
else:
|
||||||
|
logger.warning("❌ Test BUY order failed")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing test trade: {e}")
|
||||||
|
else:
|
||||||
|
logger.info("Test trade skipped")
|
||||||
|
|
||||||
|
# Test 5: Position and trade history
|
||||||
|
logger.info("\n=== TEST 5: POSITIONS AND HISTORY ===")
|
||||||
|
try:
|
||||||
|
positions = executor.get_positions()
|
||||||
|
trade_history = executor.get_trade_history()
|
||||||
|
|
||||||
|
logger.info(f"Current Positions: {len(positions)}")
|
||||||
|
for symbol, position in positions.items():
|
||||||
|
logger.info(f" {symbol}: {position.side} {position.quantity:.6f} @ ${position.entry_price:.2f}")
|
||||||
|
|
||||||
|
logger.info(f"Trade History: {len(trade_history)} trades")
|
||||||
|
for trade in trade_history[-5:]: # Last 5 trades
|
||||||
|
pnl_str = f"${trade.pnl:+.2f}" if trade.pnl else "$0.00"
|
||||||
|
logger.info(f" {trade.symbol} {trade.side}: {pnl_str}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting positions/history: {e}")
|
||||||
|
|
||||||
|
logger.info("\n=== LIVE TRADING TEST COMPLETED ===")
|
||||||
|
logger.info("If all tests passed, live trading is ready!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in live trading test: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(test_live_trading())
|
174
test_mexc_order_fix.py
Normal file
174
test_mexc_order_fix.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test MEXC Order Fix
|
||||||
|
|
||||||
|
Tests the fixed MEXC interface to ensure order execution works correctly
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_mexc_order_fix():
|
||||||
|
"""Test the fixed MEXC interface"""
|
||||||
|
print("Testing Fixed MEXC Interface")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Import after path setup
|
||||||
|
try:
|
||||||
|
from NN.exchanges.mexc_interface import MEXCInterface
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ Import error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get API credentials
|
||||||
|
api_key = os.getenv('MEXC_API_KEY', '')
|
||||||
|
api_secret = os.getenv('MEXC_SECRET_KEY', '')
|
||||||
|
|
||||||
|
if not api_key or not api_secret:
|
||||||
|
print("❌ No MEXC API credentials found")
|
||||||
|
print("Set MEXC_API_KEY and MEXC_SECRET_KEY environment variables")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Initialize MEXC interface
|
||||||
|
mexc = MEXCInterface(
|
||||||
|
api_key=api_key,
|
||||||
|
api_secret=api_secret,
|
||||||
|
test_mode=False, # Use live API (MEXC doesn't have testnet)
|
||||||
|
trading_mode='live'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test 1: Connection
|
||||||
|
print("\n1. Testing connection...")
|
||||||
|
if mexc.connect():
|
||||||
|
print("✅ Connection successful")
|
||||||
|
else:
|
||||||
|
print("❌ Connection failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test 2: Account info
|
||||||
|
print("\n2. Testing account info...")
|
||||||
|
account_info = mexc.get_account_info()
|
||||||
|
if account_info:
|
||||||
|
print("✅ Account info retrieved")
|
||||||
|
print(f"Account type: {account_info.get('accountType', 'N/A')}")
|
||||||
|
else:
|
||||||
|
print("❌ Failed to get account info")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test 3: Balance check
|
||||||
|
print("\n3. Testing balance retrieval...")
|
||||||
|
usdc_balance = mexc.get_balance('USDC')
|
||||||
|
usdt_balance = mexc.get_balance('USDT')
|
||||||
|
print(f"USDC balance: {usdc_balance}")
|
||||||
|
print(f"USDT balance: {usdt_balance}")
|
||||||
|
|
||||||
|
if usdc_balance <= 0 and usdt_balance <= 0:
|
||||||
|
print("❌ No USDC or USDT balance for testing")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test 4: Symbol support check
|
||||||
|
print("\n4. Testing symbol support...")
|
||||||
|
symbol = 'ETH/USDT' # Will be converted to ETHUSDC internally
|
||||||
|
formatted_symbol = mexc._format_spot_symbol(symbol)
|
||||||
|
print(f"Symbol {symbol} formatted to: {formatted_symbol}")
|
||||||
|
|
||||||
|
if mexc.is_symbol_supported(symbol):
|
||||||
|
print(f"✅ Symbol {formatted_symbol} is supported")
|
||||||
|
else:
|
||||||
|
print(f"❌ Symbol {formatted_symbol} is not supported")
|
||||||
|
print("Checking supported symbols...")
|
||||||
|
supported = mexc.get_api_symbols()
|
||||||
|
print(f"Found {len(supported)} supported symbols")
|
||||||
|
if 'ETHUSDC' in supported:
|
||||||
|
print("✅ ETHUSDC is in supported list")
|
||||||
|
else:
|
||||||
|
print("❌ ETHUSDC not in supported list")
|
||||||
|
|
||||||
|
# Test 5: Get ticker
|
||||||
|
print("\n5. Testing ticker retrieval...")
|
||||||
|
ticker = mexc.get_ticker(symbol)
|
||||||
|
if ticker:
|
||||||
|
print(f"✅ Ticker retrieved for {symbol}")
|
||||||
|
print(f"Last price: ${ticker['last']:.2f}")
|
||||||
|
print(f"Bid: ${ticker['bid']:.2f}, Ask: ${ticker['ask']:.2f}")
|
||||||
|
else:
|
||||||
|
print(f"❌ Failed to get ticker for {symbol}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test 6: Small test order (only if balance available)
|
||||||
|
print("\n6. Testing small order placement...")
|
||||||
|
if usdc_balance >= 10.0: # Need at least $10 for minimum order
|
||||||
|
try:
|
||||||
|
# Calculate small test quantity
|
||||||
|
test_price = ticker['last'] * 1.01 # 1% above market for quick execution
|
||||||
|
test_quantity = round(10.0 / test_price, 5) # $10 worth
|
||||||
|
|
||||||
|
print(f"Attempting to place test order:")
|
||||||
|
print(f"- Symbol: {symbol} -> {formatted_symbol}")
|
||||||
|
print(f"- Side: BUY")
|
||||||
|
print(f"- Type: LIMIT")
|
||||||
|
print(f"- Quantity: {test_quantity}")
|
||||||
|
print(f"- Price: ${test_price:.2f}")
|
||||||
|
|
||||||
|
# Note: This is a real order that will use real funds!
|
||||||
|
confirm = input("⚠️ This will place a REAL order with REAL funds! Continue? (yes/no): ")
|
||||||
|
if confirm.lower() != 'yes':
|
||||||
|
print("❌ Order test skipped by user")
|
||||||
|
return True
|
||||||
|
|
||||||
|
order_result = mexc.place_order(
|
||||||
|
symbol=symbol,
|
||||||
|
side='BUY',
|
||||||
|
order_type='LIMIT',
|
||||||
|
quantity=test_quantity,
|
||||||
|
price=test_price
|
||||||
|
)
|
||||||
|
|
||||||
|
if order_result:
|
||||||
|
print("✅ Order placed successfully!")
|
||||||
|
print(f"Order ID: {order_result.get('orderId')}")
|
||||||
|
print(f"Order result: {order_result}")
|
||||||
|
|
||||||
|
# Try to cancel the order immediately
|
||||||
|
order_id = order_result.get('orderId')
|
||||||
|
if order_id:
|
||||||
|
print(f"\n7. Testing order cancellation...")
|
||||||
|
cancel_result = mexc.cancel_order(symbol, str(order_id))
|
||||||
|
if cancel_result:
|
||||||
|
print("✅ Order cancelled successfully")
|
||||||
|
else:
|
||||||
|
print("❌ Failed to cancel order")
|
||||||
|
print("⚠️ You may have an open order to manually cancel")
|
||||||
|
else:
|
||||||
|
print("❌ Order placement failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Order test failed with exception: {e}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Insufficient balance for order test (need $10+, have ${usdc_balance:.2f} USDC)")
|
||||||
|
print("✅ All other tests passed - order API should work when balance is sufficient")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("✅ MEXC Interface Test Completed Successfully!")
|
||||||
|
print("✅ Order execution should now work correctly")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_mexc_order_fix()
|
||||||
|
sys.exit(0 if success else 1)
|
50
test_symbol_conversion.py
Normal file
50
test_symbol_conversion.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test MEXC Symbol Conversion
|
||||||
|
Verify that USDT symbols are properly converted to USDC for MEXC execution
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
from NN.exchanges.mexc_interface import MEXCInterface
|
||||||
|
|
||||||
|
def test_symbol_conversion():
|
||||||
|
"""Test the symbol conversion logic"""
|
||||||
|
print("Testing MEXC Symbol Conversion...")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Create MEXC interface (no need for real API keys for symbol conversion)
|
||||||
|
mexc = MEXCInterface(api_key="dummy", api_secret="dummy", test_mode=True)
|
||||||
|
|
||||||
|
# Test cases
|
||||||
|
test_symbols = [
|
||||||
|
"ETH/USDT",
|
||||||
|
"BTC/USDT",
|
||||||
|
"ETHUSDT",
|
||||||
|
"BTCUSDT",
|
||||||
|
"ETH/USDC", # Should stay as USDC
|
||||||
|
"ETHUSDC" # Should stay as USDC
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Symbol Conversion Results:")
|
||||||
|
print("-" * 30)
|
||||||
|
|
||||||
|
for symbol in test_symbols:
|
||||||
|
converted = mexc._format_spot_symbol(symbol)
|
||||||
|
print(f"{symbol:12} -> {converted}")
|
||||||
|
|
||||||
|
print("\nExpected Results for MEXC Trading:")
|
||||||
|
print("- ETH/USDT should become ETHUSDC")
|
||||||
|
print("- BTC/USDT should become BTCUSDC")
|
||||||
|
print("- ETHUSDT should become ETHUSDC")
|
||||||
|
print("- BTCUSDT should become BTCUSDC")
|
||||||
|
print("- ETH/USDC should stay ETHUSDC")
|
||||||
|
print("- ETHUSDC should stay ETHUSDC")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_symbol_conversion()
|
Reference in New Issue
Block a user