""" Hyperliquid fee schedule — perps, spot, staking discounts, maker rebates. Source: https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees Perps fee tiers: 14d rolling volume determines base taker/maker rate. Staking tiers: staked HYPE discount applied on top of base rates. Maker rebates: extra rebate for high maker-volume-ratio accounts. """ from __future__ import annotations # ═══════════════════════════════════════════════════════════════ # Perps fee tiers — base taker & maker rates (decimal) # ═══════════════════════════════════════════════════════════════ PERPS_TIERS = { 0: {"name": "Tier 0 (<$5M)", "min_volume": 0, "taker": 0.00045, "maker": 0.00015}, 1: {"name": "Tier 1 (>$5M)", "min_volume": 5_000_000, "taker": 0.00040, "maker": 0.00012}, 2: {"name": "Tier 2 (>$25M)", "min_volume": 25_000_000, "taker": 0.00035, "maker": 0.00008}, 3: {"name": "Tier 3 (>$100M)", "min_volume": 100_000_000, "taker": 0.00030, "maker": 0.00004}, 4: {"name": "Tier 4 (>$500M)", "min_volume": 500_000_000, "taker": 0.00028, "maker": 0.00000}, 5: {"name": "Tier 5 (>$2B)", "min_volume": 2_000_000_000,"taker": 0.00026, "maker": 0.00000}, 6: {"name": "Tier 6 (>$7B)", "min_volume": 7_000_000_000,"taker": 0.00024, "maker": 0.00000}, } # ═══════════════════════════════════════════════════════════════ # Spot fee tiers — base taker & maker rates (decimal) # ═══════════════════════════════════════════════════════════════ SPOT_TIERS = { 0: {"name": "Tier 0 (<$5M)", "min_volume": 0, "taker": 0.00070, "maker": 0.00040}, 1: {"name": "Tier 1 (>$5M)", "min_volume": 5_000_000, "taker": 0.00060, "maker": 0.00030}, 2: {"name": "Tier 2 (>$25M)", "min_volume": 25_000_000, "taker": 0.00050, "maker": 0.00020}, 3: {"name": "Tier 3 (>$100M)", "min_volume": 100_000_000, "taker": 0.00040, "maker": 0.00010}, 4: {"name": "Tier 4 (>$500M)", "min_volume": 500_000_000, "taker": 0.00035, "maker": 0.00000}, 5: {"name": "Tier 5 (>$2B)", "min_volume": 2_000_000_000,"taker": 0.00030, "maker": 0.00000}, 6: {"name": "Tier 6 (>$7B)", "min_volume": 7_000_000_000,"taker": 0.00025, "maker": 0.00000}, } # ═══════════════════════════════════════════════════════════════ # Staking tiers — discount multiplier on all fees # ═══════════════════════════════════════════════════════════════ STAKING_TIERS = { "none": {"name": "No Stake", "min_hype": 0, "multiplier": 1.00}, "wood": {"name": "Wood (>10)", "min_hype": 10, "multiplier": 0.95}, "bronze": {"name": "Bronze (>100)", "min_hype": 100, "multiplier": 0.90}, "silver": {"name": "Silver (>1K)", "min_hype": 1_000, "multiplier": 0.85}, "gold": {"name": "Gold (>10K)", "min_hype": 10_000, "multiplier": 0.80}, "platinum": {"name": "Platinum (>100K)", "min_hype": 100_000, "multiplier": 0.70}, "diamond": {"name": "Diamond (>500K)","min_hype": 500_000, "multiplier": 0.60}, } # ═══════════════════════════════════════════════════════════════ # Maker rebates — extra rebate for high maker-volume accounts # ONLY applies when maker fee > 0 (doesn't stack with zero maker) # ═══════════════════════════════════════════════════════════════ MAKER_REBATES = { 0: {"name": "No rebate", "min_maker_ratio": 0.0, "rebate": 0}, 1: {"name": "Rebate 1 (>0.5%)", "min_maker_ratio": 0.005, "rebate": -0.00001}, 2: {"name": "Rebate 2 (>1.5%)", "min_maker_ratio": 0.015, "rebate": -0.00002}, 3: {"name": "Rebate 3 (>3%)", "min_maker_ratio": 0.03, "rebate": -0.00003}, } # ═══════════════════════════════════════════════════════════════ # Compute effective rates # ═══════════════════════════════════════════════════════════════ def effective_rate(base_rate: float, staking_tier: str = "none") -> float: """Apply staking discount to base rate.""" mult = STAKING_TIERS.get(staking_tier, STAKING_TIERS["none"])["multiplier"] return base_rate * mult def get_perp_fees(vip_tier: int, staking_tier: str = "none", fee_model: str = "taker", maker_rebate_tier: int = 0) -> float: """Get effective perp fee rate. Args: vip_tier: 0-6 volume tier staking_tier: none/wood/bronze/silver/gold/platinum/diamond fee_model: 'taker' or 'maker' maker_rebate_tier: 0-3 maker volume rebate tier Returns fee rate as decimal (e.g. 0.00045 = 0.045%) """ tier = PERPS_TIERS.get(vip_tier, PERPS_TIERS[0]) base = tier[fee_model] if fee_model in ("taker", "maker") else tier["taker"] rate = effective_rate(base, staking_tier) if fee_model == "maker" and base > 0 and maker_rebate_tier > 0: rebate = MAKER_REBATES.get(maker_rebate_tier, {}).get("rebate", 0) rate = rate + rebate return rate def get_spot_fees(vip_tier: int, staking_tier: str = "none", fee_model: str = "taker") -> float: """Get effective spot fee rate.""" tier = SPOT_TIERS.get(vip_tier, SPOT_TIERS[0]) base = tier[fee_model] if fee_model in ("taker", "maker") else tier["taker"] return effective_rate(base, staking_tier) def fee_tier_from_volume(volume_14d: float, market: str = "perps") -> int: """Determine fee tier from 14-day rolling volume.""" tiers = PERPS_TIERS if market == "perps" else SPOT_TIERS current = 0 for t in sorted(tiers.keys()): if volume_14d >= tiers[t]["min_volume"]: current = t return current def staking_tier_from_hype(hype_staked: float) -> str: """Determine staking tier from staked HYPE amount.""" current = "none" for name, info in STAKING_TIERS.items(): if hype_staked >= info["min_hype"]: current = name return current # ═══════════════════════════════════════════════════════════════ # Trade PnL computation # ═══════════════════════════════════════════════════════════════ def compute_trade_fees( side: str, size: float, entry_px: float, exit_px: float, vip_tier: int = 0, staking_tier: str = "none", fee_model: str = "taker", maker_rebate_tier: int = 0, ) -> dict: """Compute entry, exit, and total fees for a round-trip trade. Returns dict with entry_fee, exit_fee, total_fee, effective_rate_taker, effective_rate_maker. """ notional_entry = size * entry_px notional_exit = size * exit_px taker_rate = get_perp_fees(vip_tier, staking_tier, "taker", maker_rebate_tier) maker_rate = get_perp_fees(vip_tier, staking_tier, "maker", maker_rebate_tier) rate = taker_rate if fee_model == "taker" else maker_rate entry_fee = notional_entry * rate exit_fee = notional_exit * rate total_fee = entry_fee + exit_fee return { "entry_fee": round(entry_fee, 6), "exit_fee": round(exit_fee, 6), "total_fee": round(total_fee, 6), "taker_rate_pct": round(taker_rate * 100, 4), "maker_rate_pct": round(maker_rate * 100, 4), "effective_rate_pct": round(rate * 100, 4) if rate else 0, "vip_tier": vip_tier, "staking_tier": staking_tier, "fee_model": fee_model, "tier_name": PERPS_TIERS[vip_tier]["name"], "staking_name": STAKING_TIERS.get(staking_tier, STAKING_TIERS["none"])["name"], } # ═══════════════════════════════════════════════════════════════ # Strategy-specific defaults # ═══════════════════════════════════════════════════════════════ STRATEGY_FEE_MODELS = { "Order Book Imbalance": "taker", "Iceberg Detection": "taker", "Funding Rate Arb": "taker", "Pairs Trading": "taker", "Avellaneda-Stoikov": "maker", "Momentum Breakout": "taker", "Mean Reversion": "taker", "Hawkes OFI": "taker", "Deep LOB": "maker", "Cartea-Jaimungal": "maker", "Queue Imbalance": "taker", "Guéant Market Making": "maker", # VBT strategy names "pairs": "taker", "hurst_vpin": "taker", "as_mm": "maker", "obi": "taker", "grid_mm": "maker", "composite_mm": "maker", "iceberg": "taker", "momentum": "taker", "mean_rev": "taker", } def get_strategy_fee_model(strategy_name: str) -> str: """Get the default fee model for a strategy name.""" for key, model in STRATEGY_FEE_MODELS.items(): if key.lower() in strategy_name.lower(): return model return "taker"