import asyncio import logging from typing import Dict, List, Optional from datetime import datetime from core.exchange_manager import ExchangeManager logger = logging.getLogger(__name__) class FundingRateScanner: """Scans for funding rate arbitrage opportunities""" def __init__(self, exchange_manager: ExchangeManager): self.exchange_manager = exchange_manager self.min_funding_rate_diff = 0.0001 # 0.01% minimum difference async def scan_opportunities(self, symbols: Optional[List[str]] = None) -> List[Dict]: """Scan for arbitrage opportunities across all exchanges""" if symbols is None: # Default popular trading pairs symbols = [ 'BTC/USDT:USDT', 'ETH/USDT:USDT', 'SOL/USDT:USDT', 'BNB/USDT:USDT', 'XRP/USDT:USDT', 'ADA/USDT:USDT', 'DOGE/USDT:USDT', 'AVAX/USDT:USDT', 'MATIC/USDT:USDT', 'LINK/USDT:USDT', 'DOT/USDT:USDT', 'UNI/USDT:USDT', ] logger.info(f"Scanning {len(symbols)} symbols across {len(self.exchange_manager.exchanges)} exchanges...") # Get funding rates for all symbols all_rates = await self.exchange_manager.get_all_funding_rates(symbols) opportunities = [] # Find arbitrage opportunities for each symbol for symbol, rates in all_rates.items(): if len(rates) < 2: continue # Find best long and short exchanges opp = self._find_opportunity(symbol, rates) if opp: opportunities.append(opp) # Sort by net funding rate (descending) opportunities.sort(key=lambda x: x.get('net_funding_rate', 0), reverse=True) logger.info(f"Found {len(opportunities)} arbitrage opportunities") return opportunities def _find_opportunity(self, symbol: str, rates: List[Dict]) -> Optional[Dict]: """Find the best arbitrage opportunity for a symbol""" if len(rates) < 2: return None best_long = None best_short = None max_net_rate = 0 # Find exchange with most negative funding (we go long there - paying funding) # and exchange with most positive funding (we go short there - receiving funding) for i, rate1 in enumerate(rates): for j, rate2 in enumerate(rates): if i == j: continue long_rate = rate1['funding_rate'] short_rate = rate2['funding_rate'] # We want to long where funding is negative (we pay) and short where it's positive (we receive) if long_rate < 0 and short_rate > 0: # Net rate = absolute of long rate (what we pay) + short rate (what we receive) net_rate = abs(long_rate) + short_rate # Account for fees (estimate 0.02% per side = 0.04% total) net_rate_after_fees = net_rate - 0.0004 if net_rate_after_fees > max_net_rate and net_rate_after_fees > self.min_funding_rate_diff: max_net_rate = net_rate_after_fees best_long = rate1 best_short = rate2 if best_long and best_short: return { 'symbol': symbol, 'long_exchange': best_long['exchange'], 'short_exchange': best_short['exchange'], 'long_funding_rate': best_long['funding_rate'], 'short_funding_rate': best_short['funding_rate'], 'net_funding_rate': max_net_rate, 'annualized_return': max_net_rate * 3 * 365, # 3 funding periods per day 'timestamp': datetime.now().isoformat(), } return None async def get_symbol_opportunities(self, symbol: str) -> List[Dict]: """Get opportunities for a specific symbol""" rates = await self.exchange_manager.get_funding_rates_batch(symbol) if len(rates) < 2: return [] opp = self._find_opportunity(symbol, rates) return [opp] if opp else []