leverae tweak
This commit is contained in:
@ -52,6 +52,15 @@ class BybitInterface(ExchangeInterface):
|
||||
self._open_orders_cache_time = 0
|
||||
self._cache_timeout = 5 # 5 seconds cache timeout
|
||||
|
||||
# Instrument info caching for minimum order size validation
|
||||
self._instrument_cache = {}
|
||||
self._instrument_cache_time = 0
|
||||
self._instrument_cache_timeout = 300 # 5 minutes cache for instrument info
|
||||
|
||||
# Leverage settings
|
||||
self.default_leverage = 10.0 # Default 10x leverage
|
||||
self.leverage_cache = {} # Cache leverage settings per symbol
|
||||
|
||||
# Load credentials from environment if not provided
|
||||
if not api_key:
|
||||
self.api_key = os.getenv('BYBIT_API_KEY', '')
|
||||
@ -120,8 +129,12 @@ class BybitInterface(ExchangeInterface):
|
||||
def _load_instruments(self) -> None:
|
||||
"""Load available trading instruments."""
|
||||
try:
|
||||
if not self.session:
|
||||
logger.warning("No session available for loading instruments")
|
||||
return
|
||||
|
||||
instruments_response = self.session.get_instruments_info(category=self.category)
|
||||
if instruments_response.get('retCode') == 0:
|
||||
if instruments_response and 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")
|
||||
@ -140,8 +153,12 @@ class BybitInterface(ExchangeInterface):
|
||||
List of instrument dictionaries
|
||||
"""
|
||||
try:
|
||||
if not self.session:
|
||||
logger.error("No session available for getting instruments")
|
||||
return []
|
||||
|
||||
response = self.session.get_instruments_info(category=category)
|
||||
if response.get('retCode') == 0:
|
||||
if response and response.get('retCode') == 0:
|
||||
return response.get('result', {}).get('list', [])
|
||||
else:
|
||||
logger.error(f"Failed to get instruments: {response}")
|
||||
@ -320,9 +337,219 @@ class BybitInterface(ExchangeInterface):
|
||||
logger.error(f"Error getting ticker for {symbol}: {e}")
|
||||
return {}
|
||||
|
||||
def get_instrument_info(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get instrument information including minimum order size with caching.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'ETHUSDT')
|
||||
|
||||
Returns:
|
||||
Dictionary with instrument information
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
current_time = time.time()
|
||||
|
||||
# Check cache first
|
||||
if (formatted_symbol in self._instrument_cache and
|
||||
current_time - self._instrument_cache_time < self._instrument_cache_timeout):
|
||||
logger.debug(f"Returning cached instrument info for {formatted_symbol}")
|
||||
return self._instrument_cache[formatted_symbol]
|
||||
|
||||
# Get fresh instrument data - check if session is available
|
||||
if not self.session:
|
||||
logger.error("No session available for getting instruments")
|
||||
return {}
|
||||
|
||||
instruments = self.get_instruments(self.category)
|
||||
|
||||
# Update cache with all instruments
|
||||
self._instrument_cache.clear()
|
||||
for instrument in instruments:
|
||||
if isinstance(instrument, dict) and 'symbol' in instrument:
|
||||
self._instrument_cache[instrument['symbol']] = instrument
|
||||
self._instrument_cache_time = current_time
|
||||
|
||||
# Return the requested instrument
|
||||
instrument_info = self._instrument_cache.get(formatted_symbol, {})
|
||||
if not instrument_info:
|
||||
logger.warning(f"Instrument {formatted_symbol} not found")
|
||||
|
||||
return instrument_info
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting instrument info for {symbol}: {e}")
|
||||
return {}
|
||||
|
||||
def _validate_order_size(self, symbol: str, quantity: float) -> Tuple[bool, float, str]:
|
||||
"""Validate and adjust order size according to instrument requirements.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
quantity: Requested quantity
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, adjusted_quantity, error_message)
|
||||
"""
|
||||
try:
|
||||
instrument_info = self.get_instrument_info(symbol)
|
||||
if not instrument_info:
|
||||
return False, quantity, f"Could not get instrument info for {symbol}"
|
||||
|
||||
lot_size_filter = instrument_info.get('lotSizeFilter', {})
|
||||
min_order_qty = float(lot_size_filter.get('minOrderQty', 0.01))
|
||||
max_order_qty = float(lot_size_filter.get('maxOrderQty', 10000))
|
||||
qty_step = float(lot_size_filter.get('qtyStep', 0.01))
|
||||
|
||||
logger.debug(f"Validation for {symbol}: min={min_order_qty}, max={max_order_qty}, step={qty_step}, requested={quantity}")
|
||||
|
||||
# Check minimum order size
|
||||
if quantity < min_order_qty:
|
||||
adjusted_quantity = min_order_qty
|
||||
logger.warning(f"Order quantity {quantity} below minimum {min_order_qty} for {symbol}, adjusting to {adjusted_quantity}")
|
||||
return True, adjusted_quantity, f"Adjusted quantity from {quantity:.6f} to minimum {adjusted_quantity:.6f}"
|
||||
|
||||
# Check maximum order size
|
||||
if quantity > max_order_qty:
|
||||
return False, quantity, f"Order quantity {quantity} exceeds maximum {max_order_qty} for {symbol}"
|
||||
|
||||
# Round to correct step size
|
||||
if qty_step > 0:
|
||||
steps = round(quantity / qty_step)
|
||||
adjusted_quantity = steps * qty_step
|
||||
# Ensure we don't go below minimum after rounding
|
||||
if adjusted_quantity < min_order_qty:
|
||||
adjusted_quantity = min_order_qty
|
||||
|
||||
if abs(adjusted_quantity - quantity) > 0.000001: # Only log if there's a meaningful difference
|
||||
logger.info(f"Adjusted quantity for step size: {quantity:.6f} -> {adjusted_quantity:.6f}")
|
||||
|
||||
return True, adjusted_quantity, ""
|
||||
|
||||
return True, quantity, ""
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating order size for {symbol}: {e}")
|
||||
return False, quantity, f"Validation error: {e}"
|
||||
|
||||
def set_leverage(self, symbol: str, leverage: float) -> bool:
|
||||
"""Set leverage for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'ETHUSDT')
|
||||
leverage: Leverage value (e.g., 10.0 for 10x)
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not self.session:
|
||||
logger.error("No session available for setting leverage")
|
||||
return False
|
||||
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
# Validate leverage value
|
||||
if leverage < 1.0 or leverage > 100.0:
|
||||
logger.error(f"Invalid leverage value: {leverage}. Must be between 1.0 and 100.0")
|
||||
return False
|
||||
|
||||
# Set leverage via Bybit API
|
||||
response = self.session.set_leverage(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol,
|
||||
buyLeverage=str(leverage),
|
||||
sellLeverage=str(leverage)
|
||||
)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
# Cache the leverage setting
|
||||
self.leverage_cache[formatted_symbol] = leverage
|
||||
logger.info(f"Successfully set leverage for {symbol} to {leverage}x")
|
||||
return True
|
||||
else:
|
||||
error_msg = response.get('retMsg', 'Unknown error')
|
||||
logger.error(f"Failed to set leverage for {symbol}: {error_msg}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting leverage for {symbol}: {e}")
|
||||
return False
|
||||
|
||||
def get_leverage(self, symbol: str) -> float:
|
||||
"""Get current leverage for a symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'ETHUSDT')
|
||||
|
||||
Returns:
|
||||
float: Current leverage value
|
||||
"""
|
||||
try:
|
||||
if not self.session:
|
||||
logger.error("No session available for getting leverage")
|
||||
return self.default_leverage
|
||||
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
# Check cache first
|
||||
if formatted_symbol in self.leverage_cache:
|
||||
return self.leverage_cache[formatted_symbol]
|
||||
|
||||
# Get leverage from API
|
||||
response = self.session.get_positions(
|
||||
category=self.category,
|
||||
symbol=formatted_symbol
|
||||
)
|
||||
|
||||
if response.get('retCode') == 0:
|
||||
positions = response.get('result', {}).get('list', [])
|
||||
for position in positions:
|
||||
if position.get('symbol') == formatted_symbol:
|
||||
leverage = float(position.get('leverage', self.default_leverage))
|
||||
# Cache the leverage
|
||||
self.leverage_cache[formatted_symbol] = leverage
|
||||
logger.debug(f"Current leverage for {symbol}: {leverage}x")
|
||||
return leverage
|
||||
|
||||
# If no position found, return default
|
||||
logger.debug(f"No position found for {symbol}, using default leverage: {self.default_leverage}x")
|
||||
return self.default_leverage
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting leverage for {symbol}: {e}")
|
||||
return self.default_leverage
|
||||
|
||||
def ensure_leverage(self, symbol: str, target_leverage: float = None) -> bool:
|
||||
"""Ensure symbol has the target leverage set.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
target_leverage: Target leverage (uses default if None)
|
||||
|
||||
Returns:
|
||||
bool: True if leverage is set correctly
|
||||
"""
|
||||
try:
|
||||
if target_leverage is None:
|
||||
target_leverage = self.default_leverage
|
||||
|
||||
current_leverage = self.get_leverage(symbol)
|
||||
|
||||
if abs(current_leverage - target_leverage) < 0.1: # Allow small tolerance
|
||||
logger.debug(f"Leverage for {symbol} already set to {current_leverage}x (target: {target_leverage}x)")
|
||||
return True
|
||||
else:
|
||||
logger.info(f"Setting leverage for {symbol} from {current_leverage}x to {target_leverage}x")
|
||||
return self.set_leverage(symbol, target_leverage)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error ensuring leverage for {symbol}: {e}")
|
||||
return False
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
quantity: float, price: float = None) -> Dict[str, Any]:
|
||||
"""Place an order.
|
||||
"""Place an order with minimum size validation and leverage support.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'BTCUSDT')
|
||||
@ -336,6 +563,21 @@ class BybitInterface(ExchangeInterface):
|
||||
"""
|
||||
try:
|
||||
formatted_symbol = self._format_symbol(symbol)
|
||||
|
||||
# Ensure leverage is set before placing order
|
||||
if not self.ensure_leverage(symbol, self.default_leverage):
|
||||
logger.warning(f"Failed to set leverage for {symbol}, proceeding with order anyway")
|
||||
|
||||
# Validate and adjust order size
|
||||
is_valid, adjusted_quantity, error_msg = self._validate_order_size(formatted_symbol, quantity)
|
||||
if not is_valid:
|
||||
logger.error(f"Order validation failed: {error_msg}")
|
||||
return {'error': error_msg}
|
||||
|
||||
# Log adjustment if made
|
||||
if adjusted_quantity != quantity:
|
||||
logger.info(f"BYBIT ORDER SIZE ADJUSTMENT: {symbol} quantity {quantity:.6f} -> {adjusted_quantity:.6f}")
|
||||
|
||||
bybit_side = side.capitalize() # 'Buy' or 'Sell'
|
||||
bybit_order_type = self._map_order_type(order_type)
|
||||
|
||||
@ -344,7 +586,7 @@ class BybitInterface(ExchangeInterface):
|
||||
'symbol': formatted_symbol,
|
||||
'side': bybit_side,
|
||||
'orderType': bybit_order_type,
|
||||
'qty': str(quantity),
|
||||
'qty': str(adjusted_quantity),
|
||||
}
|
||||
|
||||
if order_type.lower() == 'limit' and price is not None:
|
||||
@ -360,13 +602,14 @@ class BybitInterface(ExchangeInterface):
|
||||
'symbol': symbol,
|
||||
'side': side,
|
||||
'type': order_type,
|
||||
'quantity': quantity,
|
||||
'quantity': adjusted_quantity, # Return the actual quantity used
|
||||
'price': price,
|
||||
'status': 'submitted',
|
||||
'timestamp': int(time.time() * 1000)
|
||||
}
|
||||
|
||||
logger.info(f"Successfully placed {order_type} {side} order for {quantity} {symbol}")
|
||||
current_leverage = self.get_leverage(symbol)
|
||||
logger.info(f"Successfully placed {order_type} {side} order for {adjusted_quantity} {symbol} at {current_leverage}x leverage")
|
||||
return order_info
|
||||
else:
|
||||
error_msg = response.get('retMsg', 'Unknown error')
|
||||
|
@ -85,4 +85,9 @@ we should load the models in a way that we do a back propagation and other model
|
||||
|
||||
|
||||
|
||||
also, adjust our bybit api so we trade with usdt futures - where we can have up to 50x leverage. on spots we can have 10x max
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user