Files
ftdt-quant-lab/config/fee_tiers.py
T
ramseshk 0446443d36 feat: creative alpha models + portfolio layer targeting Sharpe > 1.5
New strategies:
  - Cross-Sectional Momentum: long top-N, short bottom-N across HL universe
  - Spot-Perp Basis Arbitrage: delta-neutral spot vs perp price gap trading
  - Regime-Switching Ensemble: dynamically allocates strategies by market regime
  - Portfolio Construction: risk parity, vol targeting, correlation penalty

Infrastructure:
  - DuckDBDataProvider: real tick/candle data for backtests (replaces synthetic)
  - Walk-Forward Validation: systematic IS/OOS across all 12 strategies
  - 3 Jupyter research notebooks (EDA, strategy research, portfolio)

Pipeline integration:
  - deploy.py registry, sweep_runner, vbt_runner all updated
  - fee_tiers support for new strategies
  - All modules syntax-validated and import-tested
2026-08-12 12:26:29 +08:00

211 lines
10 KiB
Python

"""
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",
"cross_sectional": "taker",
"spot_perp_basis": "taker",
"regime_ensemble": "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"