Spaces:
Sleeping
Sleeping
File size: 10,739 Bytes
2dee41b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | """
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
@dataclass
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,
}
|