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
+36 -2
View File
@@ -126,10 +126,28 @@ footer a{color:#3f3f4a;text-decoration:none}footer a:hover{color:var(--tx)}
<div class="detail-panel" id="detail-panel">
<div class="detail-header">
<h2 id="det-name">Strategy Detail</h2>
<div style="display:flex;align-items:center;gap:10px">
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<label id="fee-toggle-wrap" style="display:none;font-size:11px;color:var(--tx);cursor:pointer;user-select:none">
<input type="checkbox" id="fee-toggle" checked onchange="toggleFees()" style="cursor:pointer;margin-right:4px">Inc. fees
</label>
<select id="fee-tier-sel" style="display:none;font-size:10px;background:var(--srf);color:var(--hi);border:1px solid var(--ln);border-radius:4px;padding:3px 6px;font-family:var(--f)" onchange="onFeeTierChange()">
<option value="0">VIP 0 (0.045/0.015%)</option>
<option value="1">VIP 1 (0.040/0.012%)</option>
<option value="2">VIP 2 (0.035/0.008%)</option>
<option value="3">VIP 3 (0.030/0.004%)</option>
<option value="4">VIP 4 (0.025/0.000%)</option>
<option value="5">VIP 5 (0.020/-0.002%)</option>
<option value="6">VIP 6 (0.015/-0.004%)</option>
</select>
<select id="stake-tier-sel" style="display:none;font-size:10px;background:var(--srf);color:var(--hi);border:1px solid var(--ln);border-radius:4px;padding:3px 6px;font-family:var(--f)" onchange="onFeeTierChange()">
<option value="none">No Stake</option>
<option value="wood">Wood (×0.95)</option>
<option value="bronze">Bronze (×0.90)</option>
<option value="silver">Silver (×0.85)</option>
<option value="gold">Gold (×0.80)</option>
<option value="platinum">Platinum (×0.70)</option>
<option value="diamond">Diamond (×0.60)</option>
</select>
<a id="dl-csv" href="#" style="display:none;font-size:11px;color:var(--bl);text-decoration:none;padding:4px 10px;border:1px solid var(--ln);border-radius:5px" download>↓ CSV</a>
<button class="close-btn" onclick="closeDetail()">✕ Close</button>
</div>
@@ -205,10 +223,23 @@ function renCards(sgridId,ss,baseEq,tab,statsRowId){
}
// ═══════════ Fee toggle ═══════════
var currentBTName=null;
function toggleFees(){
feeOn=document.getElementById('fee-toggle').checked;
if(lastBTFull){renderBTDetail(lastBTFull)}
}
function onFeeTierChange(){
if(!currentBTName)return;
var ft=document.getElementById('fee-tier-sel').value;
var st=document.getElementById('stake-tier-sel').value;
document.getElementById('det-trades').innerHTML='<tr><td colspan="7" style="text-align:center;color:var(--tx);padding:20px">Recalculating with '+document.getElementById('fee-tier-sel').selectedOptions[0].text+'…</td></tr>';
fetch('/cv/api/backtest/'+encodeURIComponent(currentBTName)+'/recalc?fee_tier='+ft+'&staking_tier='+st)
.then(function(r){return r.json()}).then(function(full){
lastBTFull=full; renderBTDetail(full);
}).catch(function(e){
document.getElementById('det-trades').innerHTML='<tr><td colspan="7" style="text-align:center;color:var(--rd);padding:20px">Recalc failed: '+e.message+'</td></tr>';
});
}
// ═══════════ Render backtest detail with fee toggle ──
function renderBTDetail(full){
@@ -255,8 +286,11 @@ function openDetail(name,tab){
ss=lastData.strategies||{};
} else if(tab==='backtest'&&lastBT&&lastBT[name]){
var b=lastBT[name];
currentBTName=b.name;
document.getElementById('fee-toggle-wrap').style.display='inline';
document.getElementById('fee-toggle').checked=true; feeOn=true;
document.getElementById('fee-tier-sel').style.display='inline';
document.getElementById('stake-tier-sel').style.display='inline';
document.getElementById('dl-csv').style.display='inline';
document.getElementById('dl-csv').href='/cv/api/backtest/'+encodeURIComponent(b.name)+'/csv';
document.getElementById('det-desc').textContent='';
@@ -305,7 +339,7 @@ function openDetail(name,tab){
setTimeout(function(){if(detChart){detChart.applyOptions({width:document.getElementById('det-chart').offsetWidth,height:280});detChart.timeScale().fitContent()}},300);
}
function closeDetail(){document.getElementById('detail-overlay').classList.remove('on');document.getElementById('fee-toggle-wrap').style.display='none';document.getElementById('dl-csv').style.display='none';lastBTFull=null}
function closeDetail(){document.getElementById('detail-overlay').classList.remove('on');document.getElementById('fee-toggle-wrap').style.display='none';document.getElementById('fee-tier-sel').style.display='none';document.getElementById('stake-tier-sel').style.display='none';document.getElementById('dl-csv').style.display='none';lastBTFull=null;currentBTName=null}
document.addEventListener('keydown',function(e){if(e.key==='Escape')closeDetail()});
// ═══════════ WebSocket + render ═══════════