From dd8467468c1f42d859a0494229ecbfeaeda98ea6 Mon Sep 17 00:00:00 2001 From: rob Date: Sun, 1 Mar 2026 18:03:55 -0400 Subject: [PATCH] Fix Binance API error -2015 when fetching positions on spot exchanges - Check if exchange supports fetchPositions before calling it - Handle PermissionDenied (error -2015) gracefully with debug log instead of error - Handle NotSupported exception for exchanges that don't support positions - Use ccxt standard field names (contracts, entryPrice) with fallbacks - Skip positions with zero size - Add documentation noting this is a futures/derivatives feature Co-Authored-By: Claude Opus 4.5 --- src/Exchange.py | 67 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/Exchange.py b/src/Exchange.py index 33faf9c..1d218ef 100644 --- a/src/Exchange.py +++ b/src/Exchange.py @@ -512,28 +512,59 @@ class Exchange: def get_active_trades(self) -> List[Dict[str, Union[str, float]]]: """ - Returns a list of active trades. + Returns a list of active trades/positions. + + Note: This method uses fetch_positions() which is only available on futures/derivatives + exchanges. For spot exchanges, this will return an empty list since spot trading + doesn't have the concept of "positions" - only balances. Returns: List[Dict[str, Union[str, float]]]: A list of active trades with symbol, side, quantity, and price. """ - if self.api_key and self.api_key_secret: - try: - positions = self.client.fetch_positions() - formatted_trades = [] - for position in positions: - active_trade = { - 'symbol': position['symbol'], - 'side': 'buy' if float(position['quantity']) > 0 else 'sell', - 'quantity': abs(float(position['quantity'])), - 'price': float(position['entry_price']) - } - formatted_trades.append(active_trade) - return formatted_trades - except ccxt.BaseError as e: - logger.error(f"Error fetching active trades: {str(e)}") - return [] - else: + if not self.api_key or not self.api_key_secret: + return [] + + # Check if the exchange supports fetching positions (futures/derivatives feature) + if not self.client.has.get('fetchPositions'): + logger.debug(f"Exchange {self.exchange_id} does not support fetchPositions (spot exchange)") + return [] + + try: + positions = self.client.fetch_positions() + formatted_trades = [] + for position in positions: + # Get position size - ccxt uses 'contracts' or 'contractSize', fallback to legacy 'quantity' + quantity = position.get('contracts') or position.get('contractSize') or position.get('quantity', 0) + if quantity is None: + quantity = 0 + quantity = float(quantity) + + # Skip positions with zero size + if quantity == 0: + continue + + # Get entry price - ccxt uses 'entryPrice', fallback to legacy 'entry_price' + entry_price = position.get('entryPrice') or position.get('entry_price', 0) + if entry_price is None: + entry_price = 0 + + active_trade = { + 'symbol': position['symbol'], + 'side': position.get('side') or ('buy' if quantity > 0 else 'sell'), + 'quantity': abs(quantity), + 'price': float(entry_price) + } + formatted_trades.append(active_trade) + return formatted_trades + except ccxt.PermissionDenied as e: + # Error -2015: API key doesn't have permission for this endpoint + logger.debug(f"API key lacks permission for fetchPositions on {self.exchange_id}: {e}") + return [] + except ccxt.NotSupported as e: + logger.debug(f"fetchPositions not supported on {self.exchange_id}: {e}") + return [] + except ccxt.BaseError as e: + logger.error(f"Error fetching active trades from {self.exchange_id}: {str(e)}") return [] def get_open_orders(self) -> List[Dict[str, Union[str, float]]]: