0c0d2124ad
config/fee_tiers.py: complete Hyperliquid fee schedule with perps and spot
base rates plus staking discount multipliers. effective_rate() computes
the actual fee after staking discount. get_perp_fees() returns the
effective rate for a given VIP tier, staking tier, and fee model.
Backtest runner: added --fee-tier (0-6) and --staking-tier flags.
Regenerated all 12 backtests at VIP 0 baseline. Runner now shows fee tier
info at startup.
Server: /api/backtest/{name}/recalc endpoint accepts ?fee_tier=X&staking_tier=Y
and returns recalculated PnL with the new fee structure. On-the-fly
recalculation — no need to re-run the backtest.
Dashboard: VIP tier dropdown (VIP 0-6) and staking tier dropdown
(None/Wood/Bronze/Silver/Gold/Platinum/Diamond) in backtest detail panel.
Changing either instantly recalculates PnL via the API.
Key finding: Cartea-Jaimungal goes from -5.58% net at VIP0 to +2.39% net
at VIP6+Diamond (maker rebate: exchange pays YOU -0.0024% to provide
liquidity). Fee structure completely changes strategy viability assessment.
92 lines
3.8 KiB
Python
92 lines
3.8 KiB
Python
"""
|
||
Hyperliquid fee tiers — perps and spot, base rates + staking discounts.
|
||
|
||
Source: https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees
|
||
|
||
Fee = base_rate × staking_multiplier
|
||
Staking tiers are based on staked HYPE tokens.
|
||
"""
|
||
|
||
from dataclasses import dataclass, field
|
||
from typing import Optional
|
||
|
||
# ── Perps fee tiers (base rates) ──
|
||
PERPS_TIERS = {
|
||
0: {"name": "VIP 0", "volume": 0, "taker": 0.00045, "maker": 0.00015},
|
||
1: {"name": "VIP 1", "volume": 5_000_000, "taker": 0.00040, "maker": 0.00012},
|
||
2: {"name": "VIP 2", "volume": 25_000_000, "taker": 0.00035, "maker": 0.00008},
|
||
3: {"name": "VIP 3", "volume": 100_000_000,"taker": 0.00030, "maker": 0.00004},
|
||
4: {"name": "VIP 4", "volume": 250_000_000,"taker": 0.00025, "maker": 0.00000},
|
||
5: {"name": "VIP 5", "volume": 750_000_000,"taker": 0.00020, "maker": -0.00002},
|
||
6: {"name": "VIP 6", "volume": 2_500_000_000,"taker":0.00015,"maker": -0.00004},
|
||
}
|
||
|
||
# ── Spot fee tiers (base rates) ──
|
||
SPOT_TIERS = {
|
||
0: {"name": "VIP 0", "volume": 0, "taker": 0.00070, "maker": 0.00040},
|
||
1: {"name": "VIP 1", "volume": 100_000, "taker": 0.00060, "maker": 0.00030},
|
||
2: {"name": "VIP 2", "volume": 1_000_000, "taker": 0.00050, "maker": 0.00020},
|
||
3: {"name": "VIP 3", "volume": 10_000_000, "taker": 0.00040, "maker": 0.00010},
|
||
4: {"name": "VIP 4", "volume": 50_000_000, "taker": 0.00030, "maker": 0.00005},
|
||
5: {"name": "VIP 5", "volume": 200_000_000, "taker": 0.00020, "maker": 0.00000},
|
||
6: {"name": "VIP 6", "volume": 1_000_000_000,"taker":0.00010,"maker": -0.00005},
|
||
}
|
||
|
||
# ── Staking discount multipliers ──
|
||
STAKING_TIERS = {
|
||
"none": {"name": "No Stake", "multiplier": 1.00},
|
||
"wood": {"name": "Wood", "multiplier": 0.95},
|
||
"bronze": {"name": "Bronze", "multiplier": 0.90},
|
||
"silver": {"name": "Silver", "multiplier": 0.85},
|
||
"gold": {"name": "Gold", "multiplier": 0.80},
|
||
"platinum": {"name": "Platinum", "multiplier": 0.70},
|
||
"diamond": {"name": "Diamond", "multiplier": 0.60},
|
||
}
|
||
|
||
|
||
def effective_rate(base_rate: float, staking_tier: str = "none") -> float:
|
||
"""Calculate effective fee rate after staking discount."""
|
||
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") -> float:
|
||
"""Get effective perp fee for a given VIP tier and staking tier."""
|
||
tier = PERPS_TIERS.get(vip_tier, PERPS_TIERS[0])
|
||
base = tier[fee_model] if fee_model in ("taker", "maker") else tier["taker"]
|
||
return effective_rate(base, staking_tier)
|
||
|
||
|
||
def get_spot_fees(vip_tier: int, staking_tier: str = "none", fee_model: str = "taker") -> float:
|
||
"""Get effective spot fee for a given VIP tier and staking tier."""
|
||
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]["volume"]:
|
||
current = t
|
||
return current
|
||
|
||
|
||
# ── Strategy-specific defaults (matching existing classification) ──
|
||
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",
|
||
}
|