Spaces:
Sleeping
Sleeping
| """ | |
| Fee calculation module for triangular arbitrage. | |
| Handles trading fees (maker/taker) and slippage for different exchanges. | |
| """ | |
| from typing import Optional, Dict | |
| from dataclasses import dataclass | |
| import ccxt.async_support as ccxt | |
| class ExchangeFees: | |
| """Fee structure for an exchange.""" | |
| maker_fee: float # Maker fee as decimal (e.g., 0.001 for 0.1%) | |
| taker_fee: float # Taker fee as decimal (e.g., 0.001 for 0.1%) | |
| name: str | |
| # Standard fee structures for exchanges | |
| EXCHANGE_FEES: Dict[str, ExchangeFees] = { | |
| "binance": ExchangeFees( | |
| maker_fee=0.001, # 0.1% | |
| taker_fee=0.001, # 0.1% | |
| name="Binance" | |
| ), | |
| "binanceus": ExchangeFees( | |
| maker_fee=0.001, # 0.1% | |
| taker_fee=0.001, # 0.1% | |
| name="Binance US" | |
| ), | |
| "huobi": ExchangeFees( | |
| maker_fee=0.002, # 0.2% | |
| taker_fee=0.002, # 0.2% | |
| name="Huobi" | |
| ), | |
| "htx": ExchangeFees( # Huobi rebranded as HTX | |
| maker_fee=0.002, # 0.2% | |
| taker_fee=0.002, # 0.2% | |
| name="HTX" | |
| ), | |
| } | |
| def get_exchange_fees(exchange_name: str) -> ExchangeFees: | |
| """ | |
| Get fee structure for an exchange. | |
| Args: | |
| exchange_name: Name of the exchange (e.g., 'binance', 'huobi') | |
| Returns: | |
| ExchangeFees object with maker and taker fees | |
| """ | |
| # Normalize exchange name | |
| exchange_name_lower = exchange_name.lower() | |
| # Check direct match | |
| if exchange_name_lower in EXCHANGE_FEES: | |
| return EXCHANGE_FEES[exchange_name_lower] | |
| # Check partial matches | |
| for key, fees in EXCHANGE_FEES.items(): | |
| if key in exchange_name_lower or exchange_name_lower in key: | |
| return fees | |
| # Default fees (conservative estimate) | |
| return ExchangeFees( | |
| maker_fee=0.002, # 0.2% default | |
| taker_fee=0.002, # 0.2% default | |
| name=exchange_name | |
| ) | |
| async def fetch_user_fee_tier( | |
| exchange_name: str, | |
| api_key: Optional[str] = None, | |
| api_secret: Optional[str] = None | |
| ) -> Optional[ExchangeFees]: | |
| """ | |
| Fetch actual fee tier from exchange API if API keys are provided. | |
| This allows for VIP tier discounts and BNB/HT holdings discounts. | |
| Args: | |
| exchange_name: Name of the exchange | |
| api_key: API key for authenticated requests | |
| api_secret: API secret for authenticated requests | |
| Returns: | |
| ExchangeFees with actual user fees, or None if not available | |
| """ | |
| if not api_key or not api_secret: | |
| return None | |
| try: | |
| exchange_class = getattr(ccxt, exchange_name) | |
| exchange = exchange_class({ | |
| 'apiKey': api_key, | |
| 'secret': api_secret, | |
| 'enableRateLimit': True, | |
| 'options': { | |
| 'defaultType': 'spot', # Ensure we're using spot trading | |
| } | |
| }) | |
| # Try to fetch fee information | |
| exchange_name_lower = exchange_name.lower() | |
| if exchange_name_lower in ['binance', 'binanceus']: | |
| try: | |
| # Binance: fetch trading fees | |
| if hasattr(exchange, 'fetch_trading_fees'): | |
| fees = await exchange.fetch_trading_fees() | |
| if fees and 'trading' in fees: | |
| trading_fees = fees['trading'] | |
| # Get fees for a common trading pair (e.g., BTC/USDT) | |
| if 'BTC/USDT' in trading_fees: | |
| btc_fees = trading_fees['BTC/USDT'] | |
| maker_fee = btc_fees.get('maker', 0.001) | |
| taker_fee = btc_fees.get('taker', 0.001) | |
| await exchange.close() | |
| return ExchangeFees( | |
| maker_fee=maker_fee, | |
| taker_fee=taker_fee, | |
| name=f"{exchange_name} (User Tier)" | |
| ) | |
| # Alternative: try fetchBalance which sometimes includes fee info | |
| balance = await exchange.fetch_balance() | |
| if 'info' in balance: | |
| info = balance['info'] | |
| # Binance account info might contain fee tier | |
| # This varies by exchange API version | |
| pass | |
| except Exception as e: | |
| # If specific method fails, try general approach | |
| pass | |
| elif exchange_name_lower in ['huobi', 'htx']: | |
| try: | |
| # Huobi: fetch trading fees | |
| if hasattr(exchange, 'fetch_trading_fees'): | |
| fees = await exchange.fetch_trading_fees() | |
| if fees and 'trading' in fees: | |
| trading_fees = fees['trading'] | |
| if 'BTC/USDT' in trading_fees: | |
| btc_fees = trading_fees['BTC/USDT'] | |
| maker_fee = btc_fees.get('maker', 0.002) | |
| taker_fee = btc_fees.get('taker', 0.002) | |
| await exchange.close() | |
| return ExchangeFees( | |
| maker_fee=maker_fee, | |
| taker_fee=taker_fee, | |
| name=f"{exchange_name} (User Tier)" | |
| ) | |
| except Exception: | |
| pass | |
| # Try generic fetchTradingFees if available | |
| try: | |
| if hasattr(exchange, 'fetch_trading_fees'): | |
| fees = await exchange.fetch_trading_fees() | |
| if fees: | |
| # Extract maker/taker from first available trading pair | |
| if isinstance(fees, dict) and 'trading' in fees: | |
| trading_fees = fees['trading'] | |
| for pair, pair_fees in trading_fees.items(): | |
| if isinstance(pair_fees, dict): | |
| maker_fee = pair_fees.get('maker') | |
| taker_fee = pair_fees.get('taker') | |
| if maker_fee is not None and taker_fee is not None: | |
| await exchange.close() | |
| return ExchangeFees( | |
| maker_fee=maker_fee, | |
| taker_fee=taker_fee, | |
| name=f"{exchange_name} (User Tier)" | |
| ) | |
| except Exception: | |
| pass | |
| await exchange.close() | |
| except Exception: | |
| # If fetching fails, return None to use default fees | |
| pass | |
| return None | |
| def calculate_trading_fees( | |
| amount: float, | |
| fee_rate: float, | |
| num_trades: int = 3 | |
| ) -> float: | |
| """ | |
| Calculate total trading fees for a triangular arbitrage cycle. | |
| Args: | |
| amount: Starting amount (e.g., 1.0 for calculating profit ratio) | |
| fee_rate: Fee rate per trade (as decimal, e.g., 0.001 for 0.1%) | |
| num_trades: Number of trades in the cycle (default 3 for triangular) | |
| Returns: | |
| Total fees as a multiplier (e.g., 0.003 for 0.3% total fees) | |
| """ | |
| # For triangular arbitrage, we pay fees on each of the 3 trades | |
| # Fee is deducted from the amount received, so we multiply by (1 - fee_rate) for each trade | |
| return (1 - fee_rate) ** num_trades | |
| def calculate_slippage( | |
| trade_size_usd: float = 1000.0, | |
| liquidity_factor: float = 0.0005 # 0.05% slippage per $1000 traded | |
| ) -> float: | |
| """ | |
| Calculate slippage as a percentage based on trade size and liquidity. | |
| Args: | |
| trade_size_usd: Estimated trade size in USD (default 1000) | |
| liquidity_factor: Slippage factor per $1000 (default 0.0005 = 0.05%) | |
| Higher liquidity = lower slippage | |
| Returns: | |
| Slippage multiplier (e.g., 0.9995 for 0.05% slippage) | |
| """ | |
| # Slippage increases with trade size | |
| # Formula: slippage = 1 - (trade_size / 1000) * liquidity_factor | |
| slippage_percentage = min((trade_size_usd / 1000.0) * liquidity_factor, 0.01) # Cap at 1% | |
| return 1 - slippage_percentage | |
| def calculate_net_profit( | |
| gross_profit: float, | |
| exchange_fees: ExchangeFees, | |
| num_trades: int = 3, | |
| use_taker: bool = True, | |
| trade_size_usd: float = 1000.0, | |
| slippage_factor: float = 0.0005 | |
| ) -> float: | |
| """ | |
| Calculate net profit after fees and slippage. | |
| Args: | |
| gross_profit: Gross profit multiplier (e.g., 1.002 for 0.2% profit) | |
| exchange_fees: ExchangeFees object with maker/taker rates | |
| num_trades: Number of trades in the cycle (default 3) | |
| use_taker: Whether to use taker fees (True) or maker fees (False) | |
| trade_size_usd: Estimated trade size for slippage calculation | |
| slippage_factor: Slippage factor per $1000 traded | |
| Returns: | |
| Net profit multiplier after fees and slippage | |
| """ | |
| # Select appropriate fee rate | |
| fee_rate = exchange_fees.taker_fee if use_taker else exchange_fees.maker_fee | |
| # Calculate fee impact | |
| fee_multiplier = calculate_trading_fees(1.0, fee_rate, num_trades) | |
| # Calculate slippage impact (applied to each trade) | |
| slippage_per_trade = calculate_slippage(trade_size_usd, slippage_factor) | |
| slippage_multiplier = slippage_per_trade ** num_trades | |
| # Net profit = gross profit * fee multiplier * slippage multiplier | |
| net_profit = gross_profit * fee_multiplier * slippage_multiplier | |
| return net_profit | |
| def get_fee_breakdown( | |
| gross_profit: float, | |
| exchange_fees: ExchangeFees, | |
| num_trades: int = 3, | |
| use_taker: bool = True, | |
| trade_size_usd: float = 1000.0, | |
| slippage_factor: float = 0.0005 | |
| ) -> Dict[str, float]: | |
| """ | |
| Get detailed breakdown of fees and slippage. | |
| Returns: | |
| Dictionary with gross_profit, total_fees, total_slippage, net_profit | |
| """ | |
| fee_rate = exchange_fees.taker_fee if use_taker else exchange_fees.maker_fee | |
| fee_multiplier = calculate_trading_fees(1.0, fee_rate, num_trades) | |
| slippage_per_trade = calculate_slippage(trade_size_usd, slippage_factor) | |
| slippage_multiplier = slippage_per_trade ** num_trades | |
| total_fees = 1 - fee_multiplier | |
| total_slippage = 1 - slippage_multiplier | |
| net_profit = gross_profit * fee_multiplier * slippage_multiplier | |
| return { | |
| "gross_profit": gross_profit - 1, | |
| "total_fees": total_fees, | |
| "total_slippage": total_slippage, | |
| "net_profit": net_profit - 1, | |
| "fee_rate_per_trade": fee_rate, | |
| "slippage_per_trade": 1 - slippage_per_trade, | |
| } | |