leverae tweak

This commit is contained in:
Dobromir Popov
2025-07-15 00:51:42 +03:00
parent 154fa75c93
commit 24230f7f79
2 changed files with 254 additions and 6 deletions

View File

@ -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')

View File

@ -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