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:
@@ -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",
|
||||
}
|
||||
Reference in New Issue
Block a user