bybit REST api
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
"""
|
||||
Bybit Interface
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
@@ -12,6 +18,7 @@ except ImportError:
|
||||
logging.warning("pybit not installed. Run: pip install pybit")
|
||||
|
||||
from .exchange_interface import ExchangeInterface
|
||||
from .bybit_rest_client import BybitRestClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -35,8 +42,15 @@ class BybitInterface(ExchangeInterface):
|
||||
|
||||
# Bybit-specific settings
|
||||
self.session = None
|
||||
self.rest_client = None # Raw REST client fallback
|
||||
self.category = "linear" # Default to USDT perpetuals
|
||||
self.supported_symbols = set()
|
||||
self.use_fallback = False # Track if we should use REST client
|
||||
|
||||
# Caching to reduce API calls and avoid rate limiting
|
||||
self._open_orders_cache = {}
|
||||
self._open_orders_cache_time = 0
|
||||
self._cache_timeout = 5 # 5 seconds cache timeout
|
||||
|
||||
# Load credentials from environment if not provided
|
||||
if not api_key:
|
||||
@@ -61,22 +75,43 @@ class BybitInterface(ExchangeInterface):
|
||||
logger.error("API key and secret required for Bybit connection")
|
||||
return False
|
||||
|
||||
# Create HTTP session
|
||||
# Initialize pybit session
|
||||
self.session = HTTP(
|
||||
testnet=self.test_mode,
|
||||
api_key=self.api_key,
|
||||
api_secret=self.api_secret,
|
||||
)
|
||||
|
||||
# Test connection by getting account info
|
||||
account_info = self.session.get_wallet_balance(accountType="UNIFIED")
|
||||
if account_info.get('retCode') == 0:
|
||||
logger.info(f"Successfully connected to Bybit (testnet: {self.test_mode})")
|
||||
self._load_instruments()
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Failed to connect to Bybit: {account_info}")
|
||||
return False
|
||||
# Initialize raw REST client as fallback
|
||||
self.rest_client = BybitRestClient(
|
||||
api_key=self.api_key,
|
||||
api_secret=self.api_secret,
|
||||
testnet=self.test_mode
|
||||
)
|
||||
|
||||
# Test pybit connection first
|
||||
try:
|
||||
account_info = self.session.get_wallet_balance(accountType="UNIFIED")
|
||||
if account_info.get('retCode') == 0:
|
||||
logger.info(f"Successfully connected to Bybit via pybit (testnet: {self.test_mode})")
|
||||
self.use_fallback = False
|
||||
self._load_instruments()
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"pybit connection failed: {account_info}")
|
||||
raise Exception("pybit connection failed")
|
||||
except Exception as e:
|
||||
logger.warning(f"pybit failed, trying REST client fallback: {e}")
|
||||
|
||||
# Test REST client fallback
|
||||
if self.rest_client.test_connectivity() and self.rest_client.test_authentication():
|
||||
logger.info(f"Successfully connected to Bybit via REST client fallback (testnet: {self.test_mode})")
|
||||
self.use_fallback = True
|
||||
self._load_instruments()
|
||||
return True
|
||||
else:
|
||||
logger.error("Both pybit and REST client failed")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error connecting to Bybit: {e}")
|
||||
@@ -164,6 +199,43 @@ class BybitInterface(ExchangeInterface):
|
||||
logger.error(f"Error getting account summary: {e}")
|
||||
return {}
|
||||
|
||||
def get_all_balances(self) -> Dict[str, Dict[str, float]]:
|
||||
"""Get all account balances in the format expected by trading executor.
|
||||
|
||||
Returns:
|
||||
Dictionary with asset balances in format: {asset: {'free': float, 'locked': float}}
|
||||
"""
|
||||
try:
|
||||
account_info = self.session.get_wallet_balance(accountType="UNIFIED")
|
||||
if account_info.get('retCode') == 0:
|
||||
balances = {}
|
||||
accounts = account_info.get('result', {}).get('list', [])
|
||||
|
||||
for account in accounts:
|
||||
coins = account.get('coin', [])
|
||||
for coin in coins:
|
||||
asset = coin.get('coin', '')
|
||||
if asset:
|
||||
# Convert Bybit balance format to MEXC-compatible format
|
||||
available = float(coin.get('availableToWithdraw', 0))
|
||||
locked = float(coin.get('locked', 0))
|
||||
|
||||
balances[asset] = {
|
||||
'free': available,
|
||||
'locked': locked,
|
||||
'total': available + locked
|
||||
}
|
||||
|
||||
logger.debug(f"Retrieved balances for {len(balances)} assets")
|
||||
return balances
|
||||
else:
|
||||
logger.error(f"Failed to get all balances: {account_info}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting all balances: {e}")
|
||||
return {}
|
||||
|
||||
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get ticker information for a symbol.
|
||||
|
||||
@@ -269,6 +341,29 @@ class BybitInterface(ExchangeInterface):
|
||||
logger.error(f"Error placing order: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def _process_pybit_orders(self, orders_list: List[Dict]) -> List[Dict[str, Any]]:
|
||||
"""Process orders from pybit response format."""
|
||||
open_orders = []
|
||||
for order in orders_list:
|
||||
order_info = {
|
||||
'order_id': order.get('orderId'),
|
||||
'symbol': order.get('symbol'),
|
||||
'side': order.get('side', '').lower(),
|
||||
'type': order.get('orderType', '').lower(),
|
||||
'quantity': float(order.get('qty', 0)),
|
||||
'filled_quantity': float(order.get('cumExecQty', 0)),
|
||||
'price': float(order.get('price', 0)),
|
||||
'status': self._map_order_status(order.get('orderStatus', '')),
|
||||
'timestamp': int(order.get('createdTime', 0))
|
||||
}
|
||||
open_orders.append(order_info)
|
||||
return open_orders
|
||||
|
||||
def _process_rest_orders(self, orders_list: List[Dict]) -> List[Dict[str, Any]]:
|
||||
"""Process orders from REST client response format."""
|
||||
# REST client returns same format as pybit, so we can reuse the method
|
||||
return self._process_pybit_orders(orders_list)
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> bool:
|
||||
"""Cancel an order.
|
||||
|
||||
@@ -380,7 +475,7 @@ class BybitInterface(ExchangeInterface):
|
||||
return {}
|
||||
|
||||
def get_open_orders(self, symbol: str = None) -> List[Dict[str, Any]]:
|
||||
"""Get open orders.
|
||||
"""Get open orders with caching and fallback to REST client.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (optional, gets all if None)
|
||||
@@ -389,38 +484,64 @@ class BybitInterface(ExchangeInterface):
|
||||
List of open order dictionaries
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
'category': self.category,
|
||||
'openOnly': True
|
||||
}
|
||||
import time
|
||||
current_time = time.time()
|
||||
cache_key = symbol or 'all'
|
||||
|
||||
if symbol:
|
||||
params['symbol'] = self._format_symbol(symbol)
|
||||
# Check if we have fresh cached data
|
||||
if (cache_key in self._open_orders_cache and
|
||||
current_time - self._open_orders_cache_time < self._cache_timeout):
|
||||
logger.debug(f"Returning cached open orders for {cache_key}")
|
||||
return self._open_orders_cache[cache_key]
|
||||
|
||||
response = self.session.get_open_orders(**params)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
orders = response.get('result', {}).get('list', [])
|
||||
|
||||
open_orders = []
|
||||
for order in orders:
|
||||
order_info = {
|
||||
'order_id': order.get('orderId'),
|
||||
'symbol': order.get('symbol'),
|
||||
'side': order.get('side', '').lower(),
|
||||
'type': order.get('orderType', '').lower(),
|
||||
'quantity': float(order.get('qty', 0)),
|
||||
'filled_quantity': float(order.get('cumExecQty', 0)),
|
||||
'price': float(order.get('price', 0)),
|
||||
'status': self._map_order_status(order.get('orderStatus', '')),
|
||||
'timestamp': int(order.get('createdTime', 0))
|
||||
# Try pybit first if not using fallback
|
||||
if not self.use_fallback and self.session:
|
||||
try:
|
||||
params = {
|
||||
'category': self.category,
|
||||
'openOnly': True
|
||||
}
|
||||
open_orders.append(order_info)
|
||||
|
||||
if symbol:
|
||||
params['symbol'] = self._format_symbol(symbol)
|
||||
|
||||
response = self.session.get_open_orders(**params)
|
||||
|
||||
# Process pybit response
|
||||
if response.get('retCode') == 0:
|
||||
orders = self._process_pybit_orders(response.get('result', {}).get('list', []))
|
||||
# Cache the result
|
||||
self._open_orders_cache[cache_key] = orders
|
||||
self._open_orders_cache_time = current_time
|
||||
logger.debug(f"Found {len(orders)} open orders via pybit, cached for {self._cache_timeout}s")
|
||||
return orders
|
||||
else:
|
||||
logger.warning(f"pybit get_open_orders failed: {response}")
|
||||
raise Exception("pybit failed")
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e)
|
||||
if "10016" in error_str or "System error" in error_str:
|
||||
logger.warning(f"pybit rate limited (Error 10016), switching to REST fallback: {e}")
|
||||
self.use_fallback = True
|
||||
else:
|
||||
logger.warning(f"pybit get_open_orders error, trying REST fallback: {e}")
|
||||
|
||||
# Use REST client (either as primary or fallback)
|
||||
if self.rest_client:
|
||||
formatted_symbol = self._format_symbol(symbol) if symbol else None
|
||||
response = self.rest_client.get_open_orders(self.category, formatted_symbol)
|
||||
|
||||
logger.debug(f"Found {len(open_orders)} open orders")
|
||||
return open_orders
|
||||
orders = self._process_rest_orders(response.get('result', {}).get('list', []))
|
||||
|
||||
# Cache the result
|
||||
self._open_orders_cache[cache_key] = orders
|
||||
self._open_orders_cache_time = current_time
|
||||
|
||||
logger.debug(f"Found {len(orders)} open orders via REST client, cached for {self._cache_timeout}s")
|
||||
return orders
|
||||
else:
|
||||
logger.error(f"Failed to get open orders: {response}")
|
||||
logger.error("No available API client (pybit or REST)")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user