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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13 -4
View File
@@ -7,6 +7,7 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from common.metrics import sharpe, sortino, max_drawdown, win_rate
from config.fee_tiers import get_perp_fees, PERPS_TIERS, STAKING_TIERS
RESULTS_DIR = Path(__file__).resolve().parent / "results"
os.makedirs(RESULTS_DIR, exist_ok=True)
@@ -30,14 +31,15 @@ CONFIGS = {
"gueant": {"name":"Guéant Market Making","desc":"Closed-form asymptotic MM — adverse selection handling","alloc":100.0,"daily_ret":0.0018,"daily_vol":0.005,"fee_model":"maker"},
}
def simulate(key, periods=720, include_fees=True):
def simulate(key, periods=720, include_fees=True, fee_tier=0, staking_tier="none"):
# Deterministic seed per strategy (hash() is randomized per Python process)
_fixed_seeds = {"ofi":42,"iceberg":43,"funding_arb":44,"pairs":45,"avellaneda":46,
"momentum":47,"mean_rev":48,"hawkes":49,"deep_lob":50,
"cartea":51,"queue_imb":52,"gueant":53}
random.seed(_fixed_seeds.get(key, 42))
cfg = CONFIGS[key]
fee_rate = MAKER_FEE if cfg.get("fee_model") == "maker" else TAKER_FEE
fee_model = cfg.get("fee_model", "taker")
fee_rate = get_perp_fees(fee_tier, staking_tier, fee_model)
hr = cfg["daily_ret"]/24; hv = cfg["daily_vol"]/(24**0.5)
eq_gross=100.0; eq_net=100.0; curve_gross=[]; curve_net=[]; rets=[]; trades=[]
total_fees=0.0
@@ -92,16 +94,23 @@ def main():
p=argparse.ArgumentParser()
p.add_argument("--strategy","-s",choices=list(CONFIGS)+["all"],default="all")
p.add_argument("--no-fees",action="store_true",help="Exclude simulated fees from PnL")
p.add_argument("--fee-tier",type=int,default=0,choices=range(7),help="VIP fee tier 0-6 (default: 0)")
p.add_argument("--staking-tier",default="none",choices=list(STAKING_TIERS.keys()),help="Staking discount tier (default: none)")
a=p.parse_args()
keys=list(CONFIGS) if a.strategy=="all" else [a.strategy]
include_fees=not a.no_fees
ft_info = PERPS_TIERS[a.fee_tier]
st_info = STAKING_TIERS[a.staking_tier]
print("="*60)
print(f" FTDT Quant Lab — Backtest Runner ({len(keys)} strategies)")
print(f" Fees: {'INCLUDED (default)' if include_fees else 'EXCLUDED (--no-fees)'}")
print(f" Fee Tier: {ft_info['name']} (taker={ft_info['taker']*100:.3f}%, maker={ft_info['maker']*100:.3f}%)")
print(f" Staking: {st_info['name']} ({st_info['multiplier']*100:.0f}% multiplier)")
print(f" Effective taker: {get_perp_fees(a.fee_tier,a.staking_tier,'taker')*100:.4f}%")
print(f" Effective maker: {get_perp_fees(a.fee_tier,a.staking_tier,'maker')*100:.4f}%")
print("="*60)
for k in keys:
cfg=CONFIGS[k]; print(f"\n Running: {cfg['name']}...")
r=simulate(k, include_fees=include_fees); save(r)
r=simulate(k, include_fees=include_fees, fee_tier=a.fee_tier, staking_tier=a.staking_tier); save(r)
print(f" Net PnL: {r['pnl_pct']:+.2f}% | Gross: {r['pnl_gross_pct']:+.2f}% | Fees: ${r['fees_total']:.2f} | Sharpe: {r['sharpe']:.2f} | Win: {r['win_rate']:.0%}")
print("\n"+"="*60); print(" Results in backtests/results/"); print(" View at: https://ftdt.io/cv (Backtest tab)"); print("="*60)