showing trades on realtime chart - chart broken
This commit is contained in:
5
NN/exchanges/__init__.py
Normal file
5
NN/exchanges/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .exchange_interface import ExchangeInterface
|
||||
from .mexc_interface import MEXCInterface
|
||||
from .binance_interface import BinanceInterface
|
||||
|
||||
__all__ = ['ExchangeInterface', 'MEXCInterface', 'BinanceInterface']
|
276
NN/exchanges/binance_interface.py
Normal file
276
NN/exchanges/binance_interface.py
Normal file
@ -0,0 +1,276 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional
|
||||
import requests
|
||||
import hmac
|
||||
import hashlib
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from .exchange_interface import ExchangeInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class BinanceInterface(ExchangeInterface):
|
||||
"""Binance Exchange API Interface"""
|
||||
|
||||
def __init__(self, api_key: str = None, api_secret: str = None, test_mode: bool = True):
|
||||
"""Initialize Binance exchange interface.
|
||||
|
||||
Args:
|
||||
api_key: Binance API key
|
||||
api_secret: Binance API secret
|
||||
test_mode: If True, use testnet environment
|
||||
"""
|
||||
super().__init__(api_key, api_secret, test_mode)
|
||||
|
||||
# Use testnet URLs if in test mode
|
||||
if test_mode:
|
||||
self.base_url = "https://testnet.binance.vision"
|
||||
else:
|
||||
self.base_url = "https://api.binance.com"
|
||||
|
||||
self.api_version = "v3"
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to Binance API. This is a no-op for REST API."""
|
||||
if not self.api_key or not self.api_secret:
|
||||
logger.warning("Binance API credentials not provided. Running in read-only mode.")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Test connection by pinging server and checking account info
|
||||
ping_result = self._send_public_request('GET', 'ping')
|
||||
|
||||
if self.api_key and self.api_secret:
|
||||
# Check account connectivity
|
||||
self.get_account_info()
|
||||
|
||||
logger.info(f"Successfully connected to Binance API ({'testnet' if self.test_mode else 'live'})")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to Binance API: {str(e)}")
|
||||
return False
|
||||
|
||||
def _generate_signature(self, params: Dict[str, Any]) -> str:
|
||||
"""Generate signature for authenticated requests."""
|
||||
query_string = urlencode(params)
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode('utf-8'),
|
||||
query_string.encode('utf-8'),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
return signature
|
||||
|
||||
def _send_public_request(self, method: str, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Send public request to Binance API."""
|
||||
url = f"{self.base_url}/api/{self.api_version}/{endpoint}"
|
||||
|
||||
try:
|
||||
if method.upper() == 'GET':
|
||||
response = requests.get(url, params=params)
|
||||
else:
|
||||
response = requests.post(url, json=params)
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in public request to {endpoint}: {str(e)}")
|
||||
raise
|
||||
|
||||
def _send_private_request(self, method: str, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Send private/authenticated request to Binance API."""
|
||||
if not self.api_key or not self.api_secret:
|
||||
raise ValueError("API key and secret are required for private requests")
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
# Add timestamp
|
||||
params['timestamp'] = int(time.time() * 1000)
|
||||
|
||||
# Generate signature
|
||||
signature = self._generate_signature(params)
|
||||
params['signature'] = signature
|
||||
|
||||
# Set headers
|
||||
headers = {
|
||||
'X-MBX-APIKEY': self.api_key
|
||||
}
|
||||
|
||||
url = f"{self.base_url}/api/{self.api_version}/{endpoint}"
|
||||
|
||||
try:
|
||||
if method.upper() == 'GET':
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
elif method.upper() == 'POST':
|
||||
response = requests.post(url, data=params, headers=headers)
|
||||
elif method.upper() == 'DELETE':
|
||||
response = requests.delete(url, params=params, headers=headers)
|
||||
else:
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
|
||||
# Log detailed error if available
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Binance API error: {response.text}")
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in private request to {endpoint}: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_account_info(self) -> Dict[str, Any]:
|
||||
"""Get account information."""
|
||||
return self._send_private_request('GET', 'account')
|
||||
|
||||
def get_balance(self, asset: str) -> float:
|
||||
"""Get balance of a specific asset.
|
||||
|
||||
Args:
|
||||
asset: Asset symbol (e.g., 'BTC', 'USDT')
|
||||
|
||||
Returns:
|
||||
float: Available balance of the asset
|
||||
"""
|
||||
try:
|
||||
account_info = self._send_private_request('GET', 'account')
|
||||
balances = account_info.get('balances', [])
|
||||
|
||||
for balance in balances:
|
||||
if balance['asset'] == asset:
|
||||
return float(balance['free'])
|
||||
|
||||
# Asset not found
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting balance for {asset}: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get current ticker data for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
|
||||
Returns:
|
||||
dict: Ticker data including price information
|
||||
"""
|
||||
binance_symbol = symbol.replace('/', '')
|
||||
try:
|
||||
ticker = self._send_public_request('GET', 'ticker/24hr', {'symbol': binance_symbol})
|
||||
|
||||
# Convert to a standardized format
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'bid': float(ticker['bidPrice']),
|
||||
'ask': float(ticker['askPrice']),
|
||||
'last': float(ticker['lastPrice']),
|
||||
'volume': float(ticker['volume']),
|
||||
'timestamp': int(ticker['closeTime'])
|
||||
}
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting ticker for {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
quantity: float, price: float = None) -> Dict[str, Any]:
|
||||
"""Place an order on the exchange.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
side: Order side ('buy' or 'sell')
|
||||
order_type: Order type ('market', 'limit', etc.)
|
||||
quantity: Order quantity
|
||||
price: Order price (for limit orders)
|
||||
|
||||
Returns:
|
||||
dict: Order information including order ID
|
||||
"""
|
||||
binance_symbol = symbol.replace('/', '')
|
||||
params = {
|
||||
'symbol': binance_symbol,
|
||||
'side': side.upper(),
|
||||
'type': order_type.upper(),
|
||||
'quantity': quantity,
|
||||
}
|
||||
|
||||
if order_type.lower() == 'limit' and price is not None:
|
||||
params['price'] = price
|
||||
params['timeInForce'] = 'GTC' # Good Till Cancelled
|
||||
|
||||
# Use test order endpoint in test mode
|
||||
endpoint = 'order/test' if self.test_mode else 'order'
|
||||
|
||||
try:
|
||||
order_result = self._send_private_request('POST', endpoint, params)
|
||||
return order_result
|
||||
except Exception as e:
|
||||
logger.error(f"Error placing {side} {order_type} order for {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> bool:
|
||||
"""Cancel an existing order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
order_id: ID of the order to cancel
|
||||
|
||||
Returns:
|
||||
bool: True if cancellation successful, False otherwise
|
||||
"""
|
||||
binance_symbol = symbol.replace('/', '')
|
||||
params = {
|
||||
'symbol': binance_symbol,
|
||||
'orderId': order_id
|
||||
}
|
||||
|
||||
try:
|
||||
cancel_result = self._send_private_request('DELETE', 'order', params)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling order {order_id} for {symbol}: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_order_status(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Get status of an existing order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
order_id: ID of the order
|
||||
|
||||
Returns:
|
||||
dict: Order status information
|
||||
"""
|
||||
binance_symbol = symbol.replace('/', '')
|
||||
params = {
|
||||
'symbol': binance_symbol,
|
||||
'orderId': order_id
|
||||
}
|
||||
|
||||
try:
|
||||
order_info = self._send_private_request('GET', 'order', params)
|
||||
return order_info
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting order status for {order_id} on {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_open_orders(self, symbol: str = None) -> List[Dict[str, Any]]:
|
||||
"""Get all open orders, optionally filtered by symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT'), or None for all symbols
|
||||
|
||||
Returns:
|
||||
list: List of open orders
|
||||
"""
|
||||
params = {}
|
||||
if symbol:
|
||||
params['symbol'] = symbol.replace('/', '')
|
||||
|
||||
try:
|
||||
open_orders = self._send_private_request('GET', 'openOrders', params)
|
||||
return open_orders
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting open orders: {str(e)}")
|
||||
return []
|
191
NN/exchanges/exchange_interface.py
Normal file
191
NN/exchanges/exchange_interface.py
Normal file
@ -0,0 +1,191 @@
|
||||
import abc
|
||||
import logging
|
||||
from typing import Dict, Any, List, Tuple, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ExchangeInterface(abc.ABC):
|
||||
"""Base class for all exchange interfaces.
|
||||
|
||||
This abstract class defines the required methods that all exchange
|
||||
implementations must provide to ensure compatibility with the trading system.
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: str = None, api_secret: str = None, test_mode: bool = True):
|
||||
"""Initialize the exchange interface.
|
||||
|
||||
Args:
|
||||
api_key: API key for the exchange
|
||||
api_secret: API secret for the exchange
|
||||
test_mode: If True, use test/sandbox environment
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
self.test_mode = test_mode
|
||||
self.client = None
|
||||
self.last_price_cache = {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def connect(self) -> bool:
|
||||
"""Connect to the exchange API.
|
||||
|
||||
Returns:
|
||||
bool: True if connection successful, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_balance(self, asset: str) -> float:
|
||||
"""Get balance of a specific asset.
|
||||
|
||||
Args:
|
||||
asset: Asset symbol (e.g., 'BTC', 'USDT')
|
||||
|
||||
Returns:
|
||||
float: Available balance of the asset
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get current ticker data for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
|
||||
Returns:
|
||||
dict: Ticker data including price information
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
quantity: float, price: float = None) -> Dict[str, Any]:
|
||||
"""Place an order on the exchange.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
side: Order side ('buy' or 'sell')
|
||||
order_type: Order type ('market', 'limit', etc.)
|
||||
quantity: Order quantity
|
||||
price: Order price (for limit orders)
|
||||
|
||||
Returns:
|
||||
dict: Order information including order ID
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def cancel_order(self, symbol: str, order_id: str) -> bool:
|
||||
"""Cancel an existing order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
order_id: ID of the order to cancel
|
||||
|
||||
Returns:
|
||||
bool: True if cancellation successful, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_order_status(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Get status of an existing order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
order_id: ID of the order
|
||||
|
||||
Returns:
|
||||
dict: Order status information
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_open_orders(self, symbol: str = None) -> List[Dict[str, Any]]:
|
||||
"""Get all open orders, optionally filtered by symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT'), or None for all symbols
|
||||
|
||||
Returns:
|
||||
list: List of open orders
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_last_price(self, symbol: str) -> float:
|
||||
"""Get last known price for a symbol, may use cached value.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
|
||||
Returns:
|
||||
float: Last price
|
||||
"""
|
||||
try:
|
||||
ticker = self.get_ticker(symbol)
|
||||
price = float(ticker['last'])
|
||||
self.last_price_cache[symbol] = price
|
||||
return price
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting price for {symbol}: {str(e)}")
|
||||
# Return cached price if available
|
||||
return self.last_price_cache.get(symbol, 0.0)
|
||||
|
||||
def execute_trade(self, symbol: str, action: str, quantity: float = None,
|
||||
percent_of_balance: float = None) -> Optional[Dict[str, Any]]:
|
||||
"""Execute a trade based on a signal.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
action: Trade action ('BUY', 'SELL')
|
||||
quantity: Specific quantity to trade
|
||||
percent_of_balance: Alternative to quantity - percentage of available balance to use
|
||||
|
||||
Returns:
|
||||
dict: Order information or None if order failed
|
||||
"""
|
||||
if action not in ['BUY', 'SELL']:
|
||||
logger.error(f"Invalid action: {action}. Must be 'BUY' or 'SELL'")
|
||||
return None
|
||||
|
||||
side = action.lower()
|
||||
|
||||
try:
|
||||
# Determine base and quote assets from symbol (e.g., BTC/USDT -> BTC, USDT)
|
||||
base_asset, quote_asset = symbol.split('/')
|
||||
|
||||
# Calculate quantity if percent_of_balance is provided
|
||||
if quantity is None and percent_of_balance is not None:
|
||||
if percent_of_balance <= 0 or percent_of_balance > 1:
|
||||
logger.error(f"Invalid percent_of_balance: {percent_of_balance}. Must be between 0 and 1")
|
||||
return None
|
||||
|
||||
if side == 'buy':
|
||||
# For buy, use quote asset (e.g., USDT)
|
||||
balance = self.get_balance(quote_asset)
|
||||
price = self.get_last_price(symbol)
|
||||
quantity = (balance * percent_of_balance) / price
|
||||
else:
|
||||
# For sell, use base asset (e.g., BTC)
|
||||
balance = self.get_balance(base_asset)
|
||||
quantity = balance * percent_of_balance
|
||||
|
||||
if not quantity or quantity <= 0:
|
||||
logger.error(f"Invalid quantity: {quantity}")
|
||||
return None
|
||||
|
||||
# Place market order
|
||||
order = self.place_order(
|
||||
symbol=symbol,
|
||||
side=side,
|
||||
order_type='market',
|
||||
quantity=quantity
|
||||
)
|
||||
|
||||
logger.info(f"Executed {side.upper()} order for {quantity} {base_asset} at market price")
|
||||
return order
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing {action} trade for {symbol}: {str(e)}")
|
||||
return None
|
258
NN/exchanges/mexc_interface.py
Normal file
258
NN/exchanges/mexc_interface.py
Normal file
@ -0,0 +1,258 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional
|
||||
import requests
|
||||
import hmac
|
||||
import hashlib
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from .exchange_interface import ExchangeInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MEXCInterface(ExchangeInterface):
|
||||
"""MEXC Exchange API Interface"""
|
||||
|
||||
def __init__(self, api_key: str = None, api_secret: str = None, test_mode: bool = True):
|
||||
"""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)
|
||||
"""
|
||||
super().__init__(api_key, api_secret, test_mode)
|
||||
self.base_url = "https://api.mexc.com"
|
||||
self.api_version = "v3"
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to MEXC API. This is a no-op for REST API."""
|
||||
if not self.api_key or not self.api_secret:
|
||||
logger.warning("MEXC API credentials not provided. Running in read-only mode.")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Test connection by getting account info
|
||||
self.get_account_info()
|
||||
logger.info("Successfully connected to MEXC API")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to MEXC API: {str(e)}")
|
||||
return False
|
||||
|
||||
def _generate_signature(self, params: Dict[str, Any]) -> str:
|
||||
"""Generate signature for authenticated requests."""
|
||||
query_string = urlencode(params)
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode('utf-8'),
|
||||
query_string.encode('utf-8'),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
return signature
|
||||
|
||||
def _send_public_request(self, method: str, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Send public request to MEXC API."""
|
||||
url = f"{self.base_url}/{self.api_version}/{endpoint}"
|
||||
|
||||
try:
|
||||
if method.upper() == 'GET':
|
||||
response = requests.get(url, params=params)
|
||||
else:
|
||||
response = requests.post(url, json=params)
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in public request to {endpoint}: {str(e)}")
|
||||
raise
|
||||
|
||||
def _send_private_request(self, method: str, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""Send private/authenticated request to MEXC API."""
|
||||
if not self.api_key or not self.api_secret:
|
||||
raise ValueError("API key and secret are required for private requests")
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
# Add timestamp
|
||||
params['timestamp'] = int(time.time() * 1000)
|
||||
|
||||
# Generate signature
|
||||
signature = self._generate_signature(params)
|
||||
params['signature'] = signature
|
||||
|
||||
# Set headers
|
||||
headers = {
|
||||
'X-MEXC-APIKEY': self.api_key
|
||||
}
|
||||
|
||||
url = f"{self.base_url}/{self.api_version}/{endpoint}"
|
||||
|
||||
try:
|
||||
if method.upper() == 'GET':
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
elif method.upper() == 'POST':
|
||||
response = requests.post(url, json=params, headers=headers)
|
||||
elif method.upper() == 'DELETE':
|
||||
response = requests.delete(url, params=params, headers=headers)
|
||||
else:
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in private request to {endpoint}: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_account_info(self) -> Dict[str, Any]:
|
||||
"""Get account information."""
|
||||
return self._send_private_request('GET', 'account')
|
||||
|
||||
def get_balance(self, asset: str) -> float:
|
||||
"""Get balance of a specific asset.
|
||||
|
||||
Args:
|
||||
asset: Asset symbol (e.g., 'BTC', 'USDT')
|
||||
|
||||
Returns:
|
||||
float: Available balance of the asset
|
||||
"""
|
||||
try:
|
||||
account_info = self._send_private_request('GET', 'account')
|
||||
balances = account_info.get('balances', [])
|
||||
|
||||
for balance in balances:
|
||||
if balance['asset'] == asset:
|
||||
return float(balance['free'])
|
||||
|
||||
# Asset not found
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting balance for {asset}: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get current ticker data for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
|
||||
Returns:
|
||||
dict: Ticker data including price information
|
||||
"""
|
||||
mexc_symbol = symbol.replace('/', '')
|
||||
try:
|
||||
ticker = self._send_public_request('GET', 'ticker/24hr', {'symbol': mexc_symbol})
|
||||
|
||||
# Convert to a standardized format
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'bid': float(ticker['bidPrice']),
|
||||
'ask': float(ticker['askPrice']),
|
||||
'last': float(ticker['lastPrice']),
|
||||
'volume': float(ticker['volume']),
|
||||
'timestamp': int(ticker['closeTime'])
|
||||
}
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting ticker for {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
quantity: float, price: float = None) -> Dict[str, Any]:
|
||||
"""Place an order on the exchange.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
side: Order side ('buy' or 'sell')
|
||||
order_type: Order type ('market', 'limit', etc.)
|
||||
quantity: Order quantity
|
||||
price: Order price (for limit orders)
|
||||
|
||||
Returns:
|
||||
dict: Order information including order ID
|
||||
"""
|
||||
mexc_symbol = symbol.replace('/', '')
|
||||
params = {
|
||||
'symbol': mexc_symbol,
|
||||
'side': side.upper(),
|
||||
'type': order_type.upper(),
|
||||
'quantity': quantity,
|
||||
}
|
||||
|
||||
if order_type.lower() == 'limit' and price is not None:
|
||||
params['price'] = price
|
||||
params['timeInForce'] = 'GTC' # Good Till Cancelled
|
||||
|
||||
try:
|
||||
order_result = self._send_private_request('POST', 'order', params)
|
||||
return order_result
|
||||
except Exception as e:
|
||||
logger.error(f"Error placing {side} {order_type} order for {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> bool:
|
||||
"""Cancel an existing order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
order_id: ID of the order to cancel
|
||||
|
||||
Returns:
|
||||
bool: True if cancellation successful, False otherwise
|
||||
"""
|
||||
mexc_symbol = symbol.replace('/', '')
|
||||
params = {
|
||||
'symbol': mexc_symbol,
|
||||
'orderId': order_id
|
||||
}
|
||||
|
||||
try:
|
||||
cancel_result = self._send_private_request('DELETE', 'order', params)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling order {order_id} for {symbol}: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_order_status(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Get status of an existing order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT')
|
||||
order_id: ID of the order
|
||||
|
||||
Returns:
|
||||
dict: Order status information
|
||||
"""
|
||||
mexc_symbol = symbol.replace('/', '')
|
||||
params = {
|
||||
'symbol': mexc_symbol,
|
||||
'orderId': order_id
|
||||
}
|
||||
|
||||
try:
|
||||
order_info = self._send_private_request('GET', 'order', params)
|
||||
return order_info
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting order status for {order_id} on {symbol}: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_open_orders(self, symbol: str = None) -> List[Dict[str, Any]]:
|
||||
"""Get all open orders, optionally filtered by symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTC/USDT'), or None for all symbols
|
||||
|
||||
Returns:
|
||||
list: List of open orders
|
||||
"""
|
||||
params = {}
|
||||
if symbol:
|
||||
params['symbol'] = symbol.replace('/', '')
|
||||
|
||||
try:
|
||||
open_orders = self._send_private_request('GET', 'openOrders', params)
|
||||
return open_orders
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting open orders: {str(e)}")
|
||||
return []
|
Reference in New Issue
Block a user