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