Add Hyperliquid fee tier selector — 7 VIP levels × 7 staking tiers

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.
This commit is contained in:
ramseshk
2026-08-04 07:26:40 +00:00
parent ecfdd56d8f
commit 0c0d2124ad
16 changed files with 45910 additions and 6 deletions
+91
View File
@@ -0,0 +1,91 @@
"""
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",
}