bybit
This commit is contained in:
@ -2,5 +2,6 @@ from .mexc_interface import MEXCInterface
|
||||
from .binance_interface import BinanceInterface
|
||||
from .exchange_interface import ExchangeInterface
|
||||
from .deribit_interface import DeribitInterface
|
||||
from .bybit_interface import BybitInterface
|
||||
|
||||
__all__ = ['ExchangeInterface', 'MEXCInterface', 'BinanceInterface', 'DeribitInterface']
|
||||
__all__ = ['ExchangeInterface', 'MEXCInterface', 'BinanceInterface', 'DeribitInterface', 'BybitInterface']
|
621
NN/exchanges/bybit_interface.py
Normal file
621
NN/exchanges/bybit_interface.py
Normal file
@ -0,0 +1,621 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import os
|
||||
|
||||
try:
|
||||
from pybit.unified_trading import HTTP
|
||||
except ImportError:
|
||||
HTTP = None
|
||||
logging.warning("pybit not installed. Run: pip install pybit")
|
||||
|
||||
from .exchange_interface import ExchangeInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BybitInterface(ExchangeInterface):
|
||||
"""Bybit Exchange API Interface for cryptocurrency derivatives trading.
|
||||
|
||||
Supports both testnet and live trading environments.
|
||||
Focus on USDT perpetuals and spot trading.
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: str = "", api_secret: str = "", test_mode: bool = True):
|
||||
"""Initialize Bybit exchange interface.
|
||||
|
||||
Args:
|
||||
api_key: Bybit API key
|
||||
api_secret: Bybit API secret
|
||||
test_mode: If True, use testnet environment
|
||||
"""
|
||||
super().__init__(api_key, api_secret, test_mode)
|
||||
|
||||
# Bybit-specific settings
|
||||
self.session = None
|
||||
self.category = "linear" # Default to USDT perpetuals
|
||||
self.supported_symbols = set()
|
||||
|
||||
# Load credentials from environment if not provided
|
||||
if not api_key:
|
||||
self.api_key = os.getenv('BYBIT_API_KEY', '')
|
||||
if not api_secret:
|
||||
self.api_secret = os.getenv('BYBIT_API_SECRET', '')
|
||||
|
||||
logger.info(f"Initialized BybitInterface (testnet: {test_mode})")
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to Bybit API.
|
||||
|
||||
Returns:
|
||||
bool: True if connection successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if HTTP is None:
|
||||
logger.error("pybit library not installed")
|
||||
return False
|
||||
|
||||
if not self.api_key or not self.api_secret:
|
||||
logger.error("API key and secret required for Bybit connection")
|
||||
return False
|
||||
|
||||
# Create HTTP 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
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error connecting to Bybit: {e}")
|
||||
return False
|
||||
|
||||
def _load_instruments(self) -> None:
|
||||
"""Load available trading instruments."""
|
||||
try:
|
||||
instruments_response = self.session.get_instruments_info(category=self.category)
|
||||
if instruments_response.get('retCode') == 0:
|
||||
instruments = instruments_response.get('result', {}).get('list', [])
|
||||
self.supported_symbols = {instr['symbol'] for instr in instruments}
|
||||
logger.info(f"Loaded {len(self.supported_symbols)} instruments")
|
||||
else:
|
||||
logger.warning(f"Failed to load instruments: {instruments_response}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error loading instruments: {e}")
|
||||
|
||||
def get_instruments(self, category: str = "linear") -> List[Dict[str, Any]]:
|
||||
"""Get available trading instruments.
|
||||
|
||||
Args:
|
||||
category: Instrument category (linear, spot, inverse, option)
|
||||
|
||||
Returns:
|
||||
List of instrument dictionaries
|
||||
"""
|
||||
try:
|
||||
response = self.session.get_instruments_info(category=category)
|
||||
if response.get('retCode') == 0:
|
||||
return response.get('result', {}).get('list', [])
|
||||
else:
|
||||
logger.error(f"Failed to get instruments: {response}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting instruments: {e}")
|
||||
return []
|
||||
|
||||
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.session.get_wallet_balance(accountType="UNIFIED")
|
||||
if account_info.get('retCode') == 0:
|
||||
balances = account_info.get('result', {}).get('list', [])
|
||||
|
||||
for account in balances:
|
||||
coins = account.get('coin', [])
|
||||
for coin in coins:
|
||||
if coin.get('coin', '').upper() == asset.upper():
|
||||
available_balance = float(coin.get('availableToWithdraw', 0))
|
||||
logger.debug(f"Balance for {asset}: {available_balance}")
|
||||
return available_balance
|
||||
|
||||
logger.debug(f"No balance found for asset {asset}")
|
||||
return 0.0
|
||||
else:
|
||||
logger.error(f"Failed to get account balance: {account_info}")
|
||||
return 0.0
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting balance for {asset}: {e}")
|
||||
return 0.0
|
||||
|
||||
def get_account_summary(self) -> Dict[str, Any]:
|
||||
"""Get account summary with all balances and positions.
|
||||
|
||||
Returns:
|
||||
Dictionary with account information
|
||||
"""
|
||||
try:
|
||||
account_info = self.session.get_wallet_balance(accountType="UNIFIED")
|
||||
if account_info.get('retCode') == 0:
|
||||
return account_info
|
||||
else:
|
||||
logger.error(f"Failed to get account summary: {account_info}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting account summary: {e}")
|
||||
return {}
|
||||
|
||||
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get ticker information for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTCUSDT')
|
||||
|
||||
Returns:
|
||||
Dictionary with ticker information
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
ticker_response = self.session.get_tickers(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol
|
||||
)
|
||||
|
||||
if ticker_response.get('retCode') == 0:
|
||||
ticker_data = ticker_response.get('result', {}).get('list', [])
|
||||
if ticker_data:
|
||||
ticker = ticker_data[0]
|
||||
|
||||
# Cache the last price
|
||||
last_price = float(ticker.get('lastPrice', 0))
|
||||
self.last_price_cache[symbol] = last_price
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'last_price': last_price,
|
||||
'bid_price': float(ticker.get('bid1Price', 0)),
|
||||
'ask_price': float(ticker.get('ask1Price', 0)),
|
||||
'volume_24h': float(ticker.get('volume24h', 0)),
|
||||
'change_24h': float(ticker.get('price24hPcnt', 0)),
|
||||
'high_24h': float(ticker.get('highPrice24h', 0)),
|
||||
'low_24h': float(ticker.get('lowPrice24h', 0)),
|
||||
'timestamp': int(ticker.get('time', 0))
|
||||
}
|
||||
else:
|
||||
logger.error(f"No ticker data for {symbol}")
|
||||
return {}
|
||||
else:
|
||||
logger.error(f"Failed to get ticker for {symbol}: {ticker_response}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting ticker for {symbol}: {e}")
|
||||
return {}
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
quantity: float, price: float = None) -> Dict[str, Any]:
|
||||
"""Place an order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTCUSDT')
|
||||
side: 'buy' or 'sell'
|
||||
order_type: 'market' or 'limit'
|
||||
quantity: Order quantity
|
||||
price: Order price (required for limit orders)
|
||||
|
||||
Returns:
|
||||
Dictionary with order information
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
bybit_side = side.capitalize() # 'Buy' or 'Sell'
|
||||
bybit_order_type = self._map_order_type(order_type)
|
||||
|
||||
order_params = {
|
||||
'category': self.category,
|
||||
'symbol': formatted_symbol,
|
||||
'side': bybit_side,
|
||||
'orderType': bybit_order_type,
|
||||
'qty': str(quantity),
|
||||
}
|
||||
|
||||
if order_type.lower() == 'limit' and price is not None:
|
||||
order_params['price'] = str(price)
|
||||
order_params['timeInForce'] = 'GTC' # Good Till Cancelled
|
||||
|
||||
response = self.session.place_order(**order_params)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
result = response.get('result', {})
|
||||
order_info = {
|
||||
'order_id': result.get('orderId'),
|
||||
'symbol': symbol,
|
||||
'side': side,
|
||||
'type': order_type,
|
||||
'quantity': quantity,
|
||||
'price': price,
|
||||
'status': 'submitted',
|
||||
'timestamp': int(time.time() * 1000)
|
||||
}
|
||||
|
||||
logger.info(f"Successfully placed {order_type} {side} order for {quantity} {symbol}")
|
||||
return order_info
|
||||
else:
|
||||
error_msg = response.get('retMsg', 'Unknown error')
|
||||
logger.error(f"Failed to place order: {error_msg}")
|
||||
return {'error': error_msg}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error placing order: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> bool:
|
||||
"""Cancel an order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
order_id: Order ID to cancel
|
||||
|
||||
Returns:
|
||||
bool: True if order was cancelled successfully
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
response = self.session.cancel_order(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol,
|
||||
orderId=order_id
|
||||
)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
logger.info(f"Successfully cancelled order {order_id}")
|
||||
return True
|
||||
else:
|
||||
error_msg = response.get('retMsg', 'Unknown error')
|
||||
logger.error(f"Failed to cancel order {order_id}: {error_msg}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling order {order_id}: {e}")
|
||||
return False
|
||||
|
||||
def get_order_status(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Get status of an order.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
order_id: Order ID
|
||||
|
||||
Returns:
|
||||
Dictionary with order status information
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
response = self.session.get_open_orders(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol,
|
||||
orderId=order_id
|
||||
)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
orders = response.get('result', {}).get('list', [])
|
||||
if orders:
|
||||
order = orders[0]
|
||||
return {
|
||||
'order_id': order.get('orderId'),
|
||||
'symbol': 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)),
|
||||
'average_price': float(order.get('avgPrice', 0)),
|
||||
'status': self._map_order_status(order.get('orderStatus', '')),
|
||||
'timestamp': int(order.get('createdTime', 0))
|
||||
}
|
||||
else:
|
||||
# Order might be filled/cancelled, check order history
|
||||
return self._get_order_from_history(symbol, order_id)
|
||||
else:
|
||||
logger.error(f"Failed to get order status: {response}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting order status for {order_id}: {e}")
|
||||
return {}
|
||||
|
||||
def _get_order_from_history(self, symbol: str, order_id: str) -> Dict[str, Any]:
|
||||
"""Get order from order history (for filled/cancelled orders)."""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
response = self.session.get_order_history(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol,
|
||||
orderId=order_id
|
||||
)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
orders = response.get('result', {}).get('list', [])
|
||||
if orders:
|
||||
order = orders[0]
|
||||
return {
|
||||
'order_id': order.get('orderId'),
|
||||
'symbol': 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)),
|
||||
'average_price': float(order.get('avgPrice', 0)),
|
||||
'status': self._map_order_status(order.get('orderStatus', '')),
|
||||
'timestamp': int(order.get('createdTime', 0))
|
||||
}
|
||||
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting order from history: {e}")
|
||||
return {}
|
||||
|
||||
def get_open_orders(self, symbol: str = None) -> List[Dict[str, Any]]:
|
||||
"""Get open orders.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (optional, gets all if None)
|
||||
|
||||
Returns:
|
||||
List of open order dictionaries
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
'category': self.category,
|
||||
'openOnly': True
|
||||
}
|
||||
|
||||
if symbol:
|
||||
params['symbol'] = self._format_symbol(symbol)
|
||||
|
||||
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))
|
||||
}
|
||||
open_orders.append(order_info)
|
||||
|
||||
logger.debug(f"Found {len(open_orders)} open orders")
|
||||
return open_orders
|
||||
else:
|
||||
logger.error(f"Failed to get open orders: {response}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting open orders: {e}")
|
||||
return []
|
||||
|
||||
def get_positions(self, symbol: str = None) -> List[Dict[str, Any]]:
|
||||
"""Get position information.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (optional, gets all if None)
|
||||
|
||||
Returns:
|
||||
List of position dictionaries
|
||||
"""
|
||||
try:
|
||||
params = {'category': self.category}
|
||||
if symbol:
|
||||
params['symbol'] = self._format_symbol(symbol)
|
||||
|
||||
response = self.session.get_positions(**params)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
positions = response.get('result', {}).get('list', [])
|
||||
|
||||
position_list = []
|
||||
for pos in positions:
|
||||
# Only include positions with non-zero size
|
||||
size = float(pos.get('size', 0))
|
||||
if size != 0:
|
||||
position_info = {
|
||||
'symbol': pos.get('symbol'),
|
||||
'side': pos.get('side', '').lower(),
|
||||
'size': size,
|
||||
'entry_price': float(pos.get('avgPrice', 0)),
|
||||
'mark_price': float(pos.get('markPrice', 0)),
|
||||
'unrealized_pnl': float(pos.get('unrealisedPnl', 0)),
|
||||
'percentage': float(pos.get('unrealisedPnlPct', 0)),
|
||||
'leverage': float(pos.get('leverage', 0)),
|
||||
'timestamp': int(pos.get('updatedTime', 0))
|
||||
}
|
||||
position_list.append(position_info)
|
||||
|
||||
logger.debug(f"Found {len(position_list)} positions")
|
||||
return position_list
|
||||
else:
|
||||
logger.error(f"Failed to get positions: {response}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting positions: {e}")
|
||||
return []
|
||||
|
||||
def _format_symbol(self, symbol: str) -> str:
|
||||
"""Format symbol for Bybit API.
|
||||
|
||||
Args:
|
||||
symbol: Symbol in various formats
|
||||
|
||||
Returns:
|
||||
Formatted symbol for Bybit
|
||||
"""
|
||||
# Remove any separators and convert to uppercase
|
||||
clean_symbol = symbol.replace('/', '').replace('-', '').replace('_', '').upper()
|
||||
|
||||
# Common mappings
|
||||
symbol_mapping = {
|
||||
'BTCUSDT': 'BTCUSDT',
|
||||
'ETHUSDT': 'ETHUSDT',
|
||||
'BTCUSD': 'BTCUSDT', # Map to USDT perpetual
|
||||
'ETHUSD': 'ETHUSDT', # Map to USDT perpetual
|
||||
}
|
||||
|
||||
return symbol_mapping.get(clean_symbol, clean_symbol)
|
||||
|
||||
def _map_order_type(self, order_type: str) -> str:
|
||||
"""Map order type to Bybit format.
|
||||
|
||||
Args:
|
||||
order_type: Order type ('market', 'limit')
|
||||
|
||||
Returns:
|
||||
Bybit order type
|
||||
"""
|
||||
type_mapping = {
|
||||
'market': 'Market',
|
||||
'limit': 'Limit',
|
||||
'stop': 'Stop',
|
||||
'stop_limit': 'StopLimit'
|
||||
}
|
||||
|
||||
return type_mapping.get(order_type.lower(), 'Market')
|
||||
|
||||
def _map_order_status(self, status: str) -> str:
|
||||
"""Map Bybit order status to standard format.
|
||||
|
||||
Args:
|
||||
status: Bybit order status
|
||||
|
||||
Returns:
|
||||
Standardized order status
|
||||
"""
|
||||
status_mapping = {
|
||||
'New': 'open',
|
||||
'PartiallyFilled': 'partially_filled',
|
||||
'Filled': 'filled',
|
||||
'Cancelled': 'cancelled',
|
||||
'Rejected': 'rejected',
|
||||
'PartiallyFilledCanceled': 'cancelled'
|
||||
}
|
||||
|
||||
return status_mapping.get(status, status.lower())
|
||||
|
||||
def get_orderbook(self, symbol: str, depth: int = 25) -> Dict[str, Any]:
|
||||
"""Get orderbook for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
depth: Number of price levels to return (max 200)
|
||||
|
||||
Returns:
|
||||
Dictionary with orderbook data
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
response = self.session.get_orderbook(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol,
|
||||
limit=min(depth, 200) # Bybit max limit is 200
|
||||
)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
orderbook_data = response.get('result', {})
|
||||
|
||||
bids = []
|
||||
asks = []
|
||||
|
||||
# Process bids (buy orders)
|
||||
for bid in orderbook_data.get('b', []):
|
||||
bids.append([float(bid[0]), float(bid[1])])
|
||||
|
||||
# Process asks (sell orders)
|
||||
for ask in orderbook_data.get('a', []):
|
||||
asks.append([float(ask[0]), float(ask[1])])
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'bids': bids,
|
||||
'asks': asks,
|
||||
'timestamp': int(orderbook_data.get('ts', 0))
|
||||
}
|
||||
else:
|
||||
logger.error(f"Failed to get orderbook for {symbol}: {response}")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting orderbook for {symbol}: {e}")
|
||||
return {}
|
||||
|
||||
def close_position(self, symbol: str, quantity: float = None) -> Dict[str, Any]:
|
||||
"""Close a position (market order in opposite direction).
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
quantity: Quantity to close (None for full position)
|
||||
|
||||
Returns:
|
||||
Dictionary with order information
|
||||
"""
|
||||
try:
|
||||
# Get current position
|
||||
positions = self.get_positions(symbol)
|
||||
if not positions:
|
||||
logger.warning(f"No position found for {symbol}")
|
||||
return {'error': 'No position found'}
|
||||
|
||||
position = positions[0]
|
||||
position_size = position['size']
|
||||
position_side = position['side']
|
||||
|
||||
# Determine close quantity
|
||||
close_quantity = quantity if quantity is not None else abs(position_size)
|
||||
|
||||
# Determine opposite side
|
||||
close_side = 'sell' if position_side == 'buy' else 'buy'
|
||||
|
||||
# Place market order to close position
|
||||
return self.place_order(
|
||||
symbol=symbol,
|
||||
side=close_side,
|
||||
order_type='market',
|
||||
quantity=close_quantity
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing position for {symbol}: {e}")
|
||||
return {'error': str(e)}
|
@ -8,6 +8,7 @@ from .exchange_interface import ExchangeInterface
|
||||
from .mexc_interface import MEXCInterface
|
||||
from .binance_interface import BinanceInterface
|
||||
from .deribit_interface import DeribitInterface
|
||||
from .bybit_interface import BybitInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -18,7 +19,8 @@ class ExchangeFactory:
|
||||
SUPPORTED_EXCHANGES = {
|
||||
'mexc': MEXCInterface,
|
||||
'binance': BinanceInterface,
|
||||
'deribit': DeribitInterface
|
||||
'deribit': DeribitInterface,
|
||||
'bybit': BybitInterface
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@ -62,6 +64,12 @@ class ExchangeFactory:
|
||||
api_secret=api_secret,
|
||||
test_mode=test_mode
|
||||
)
|
||||
elif exchange_name == 'bybit':
|
||||
exchange = exchange_class(
|
||||
api_key=api_key,
|
||||
api_secret=api_secret,
|
||||
test_mode=test_mode
|
||||
)
|
||||
else: # binance and others
|
||||
exchange = exchange_class(
|
||||
api_key=api_key,
|
||||
@ -100,6 +108,9 @@ class ExchangeFactory:
|
||||
elif exchange_name == 'binance':
|
||||
api_key = os.getenv('BINANCE_API_KEY', '')
|
||||
api_secret = os.getenv('BINANCE_SECRET_KEY', '')
|
||||
elif exchange_name == 'bybit':
|
||||
api_key = os.getenv('BYBIT_API_KEY', '')
|
||||
api_secret = os.getenv('BYBIT_API_SECRET', '')
|
||||
else:
|
||||
logger.warning(f"Unknown exchange credentials for {exchange_name}")
|
||||
api_key = api_secret = ''
|
||||
|
Reference in New Issue
Block a user