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 <noreply@anthropic.com>
This commit is contained in:
rob 2026-03-01 18:03:55 -04:00
parent 3644c219d4
commit dd8467468c
1 changed files with 49 additions and 18 deletions

View File

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