Files
position-calculator/index.html

379 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>合约仓位计算器</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#0e1419;
--panel:#161d27;
--panel-2:#1b2430;
--border:#283340;
--border-hi:#36465a;
--text:#e6edf3;
--muted:#7d8896;
--faint:#566374;
--long:#16c784;
--short:#ea3943;
--gold:#f0b90b;
--mono:"JetBrains Mono",ui-monospace,"SF Mono","Menlo",monospace;
--sans:"Inter",system-ui,-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
background:var(--bg);
color:var(--text);
font-family:var(--sans);
line-height:1.5;
padding:24px 16px 64px;
-webkit-font-smoothing:antialiased;
text-rendering:optimizeLegibility;
}
.mono,input[type=number],.val,.v,.lev-table,.tag,.result-head .risk b,.big .lbl,.pair .lbl,.eyebrow,.card h2,.lev-table th{
font-variant-numeric:tabular-nums;
font-feature-settings:"tnum" 1,"cv01" 1,"ss01" 1;
}
.wrap{max-width:880px;margin:0 auto}
header{margin-bottom:24px}
.eyebrow{
font-family:var(--mono);font-size:11px;letter-spacing:.18em;
text-transform:uppercase;color:var(--gold);margin-bottom:6px;
}
h1{font-size:22px;font-weight:700;letter-spacing:-.02em}
h1 .sym{color:var(--muted);font-weight:400;font-family:var(--mono)}
.sub{color:var(--muted);font-size:13px;margin-top:4px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:20px}
@media(max-width:640px){.grid{grid-template-columns:1fr}}
.card{
background:var(--panel);border:1px solid var(--border);
border-radius:12px;padding:18px;
}
.card h2{
font-size:11px;letter-spacing:.14em;text-transform:uppercase;
color:var(--faint);font-weight:600;margin-bottom:14px;font-family:var(--mono);
}
.field{margin-bottom:14px}
.field:last-child{margin-bottom:0}
label{display:block;font-size:12px;color:var(--muted);margin-bottom:6px}
input[type=number]{
width:100%;background:var(--panel-2);border:1px solid var(--border);
color:var(--text);font-family:var(--mono);font-size:15px;
padding:9px 12px;border-radius:8px;outline:none;
transition:border-color .15s;
}
input[type=number]:focus{border-color:var(--border-hi)}
input[type=number]:disabled{opacity:.5;cursor:not-allowed}
input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.side-toggle{display:flex;gap:8px}
.side-toggle button{
flex:1;background:var(--panel-2);border:1px solid var(--border);
color:var(--muted);padding:9px;border-radius:8px;cursor:pointer;
font-size:14px;font-weight:600;transition:all .15s;
}
.side-toggle button.on[data-side=long]{background:rgba(22,199,132,.14);border-color:var(--long);color:var(--long)}
.side-toggle button.on[data-side=short]{background:rgba(234,57,67,.14);border-color:var(--short);color:var(--short)}
.sl-mode{display:flex;gap:6px;margin-bottom:8px}
.sl-mode button{
background:transparent;border:1px solid var(--border);color:var(--faint);
padding:4px 10px;border-radius:6px;cursor:pointer;font-size:11px;
font-family:var(--mono);transition:all .15s;
}
.sl-mode button.on{border-color:var(--gold);color:var(--gold)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
/* results */
.result-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:16px}
.result-head .risk{font-size:12px;color:var(--muted)}
.result-head .risk b{color:var(--short);font-family:var(--mono);font-weight:600}
.big{margin-bottom:16px}
.big .lbl{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.1em;font-family:var(--mono);margin-bottom:3px}
.big .val{font-family:var(--mono);font-size:27px;font-weight:700;letter-spacing:-.02em}
.big .val .u{font-size:13px;color:var(--muted);font-weight:400;margin-left:4px}
.pair{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
.pair .lbl{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.08em;font-family:var(--mono);margin-bottom:2px}
.pair .v{font-family:var(--mono);font-size:16px;font-weight:600}
.pair .v .u{font-size:11px;color:var(--muted);font-weight:400}
.lev-table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:13px}
.lev-table th{
text-align:right;font-weight:500;color:var(--faint);font-size:10px;
text-transform:uppercase;letter-spacing:.08em;padding:0 0 8px;
}
.lev-table th:first-child{text-align:left}
.lev-table td{padding:7px 0;border-top:1px solid var(--border);text-align:right}
.lev-table td:first-child{text-align:left;color:var(--gold);font-weight:600}
.lev-table .liq{color:var(--muted)}
.safe{color:var(--long)}
.danger{color:var(--short)}
.tag{font-size:10px;padding:1px 6px;border-radius:4px;font-family:var(--mono)}
.tag.ok{background:rgba(22,199,132,.12);color:var(--long)}
.tag.no{background:rgba(234,57,67,.12);color:var(--short)}
.note{
margin-top:18px;padding:12px 14px;background:var(--panel);
border:1px solid var(--border);border-radius:10px;
font-size:12px;color:var(--muted);line-height:1.6;
}
.note b{color:var(--text);font-weight:600}
.full{grid-column:1/-1}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="eyebrow">Position Sizer · 逐仓</div>
<h1>合约仓位计算器 <span class="sym">/ DOGE-USDT</span></h1>
<div class="sub">先定亏损上限,再反推仓位。杠杆只决定爆仓距离,不决定你亏多少。</div>
</header>
<div class="grid">
<!-- INPUTS -->
<div class="card">
<h2>输入</h2>
<div class="field">
<label>方向</label>
<div class="side-toggle" id="side">
<button data-side="long" class="on">做多 Long</button>
<button data-side="short">做空 Short</button>
</div>
</div>
<div class="field">
<label>账户余额 (USDT)</label>
<input type="number" id="balance" value="200" step="any">
</div>
<div class="field">
<label>单笔风险 (% of 余额)</label>
<input type="number" id="riskPct" value="5" step="any">
</div>
<div class="field">
<label>入场价</label>
<input type="number" id="entry" value="0.08505" step="any">
</div>
<div class="field">
<label>止损</label>
<div class="sl-mode" id="slMode">
<button data-mode="pct" class="on">按百分比</button>
<button data-mode="price">按价格</button>
</div>
<div class="row2">
<input type="number" id="slPct" value="2" step="any" placeholder="止损 %">
<input type="number" id="slPrice" step="any" placeholder="止损价" disabled>
</div>
</div>
<div class="field">
<label>止盈</label>
<div class="sl-mode" id="tpMode">
<button data-mode="rr" class="on">按盈亏比</button>
<button data-mode="price">按价格</button>
</div>
<div class="row2">
<input type="number" id="tpRR" value="2" step="any" placeholder="盈亏比 R如 2">
<input type="number" id="tp" step="any" placeholder="止盈价" disabled>
</div>
</div>
</div>
<!-- RESULTS -->
<div class="card">
<h2>仓位</h2>
<div class="result-head">
<span class="risk">最大亏损 <b id="riskAmt">10.00 U</b></span>
<span class="risk" id="slShow"></span>
</div>
<div class="big">
<div class="lbl">建议仓位 · DOGE 数量</div>
<div class="val" id="qty"></div>
</div>
<div class="pair">
<div>
<div class="lbl">名义价值</div>
<div class="v" id="notional"></div>
</div>
<div>
<div class="lbl">盈亏比 R:R</div>
<div class="v" id="rr"></div>
</div>
</div>
<div class="pair">
<div>
<div class="lbl">止损价</div>
<div class="v" id="slPriceShow"></div>
</div>
<div>
<div class="lbl">止盈价</div>
<div class="v" id="tpPriceShow"></div>
</div>
</div>
<div class="pair" style="margin-bottom:0">
<div>
<div class="lbl">若止盈 · 盈利</div>
<div class="v" id="tpProfit"></div>
</div>
<div>
<div class="lbl">止盈幅度</div>
<div class="v" id="tpPctShow"></div>
</div>
</div>
</div>
<!-- LEVERAGE LADDER -->
<div class="card full">
<h2>杠杆阶梯 · 保证金 & 爆仓校验</h2>
<table class="lev-table">
<thead>
<tr>
<th>杠杆</th>
<th>占用保证金</th>
<th>爆仓价</th>
<th>爆仓距离</th>
<th>状态</th>
</tr>
</thead>
<tbody id="ladder"></tbody>
</table>
</div>
</div>
<div class="note">
<b>说明:</b>真正把亏损锁定在风险金额的是<b>止损单</b>,不是杠杆。止盈默认<b>按盈亏比自动反算</b>——
止盈点位差 = 止损点位差 × 盈亏比 R按方向算出止盈价也可切「按价格」手动输入。
表中「状态」校验爆仓价是否在止损之外——<span class="safe">安全</span>表示会先触发止损、不会被强平;<span class="danger">危险</span>表示杠杆过高、爆仓在止损之前。
爆仓价为<b>逐仓简化估算</b>爆仓距离≈1/杠杆,忽略维持保证金与手续费),实盘以交易所显示为准。这只是数学计算,不构成投资建议。
</div>
</div>
<script>
(function(){
const $=id=>document.getElementById(id);
const levs=[3,5,10,20,25,50,75,100];
let side='long', slMode='pct', tpMode='rr';
// side toggle
$('side').addEventListener('click',e=>{
const b=e.target.closest('button'); if(!b)return;
side=b.dataset.side;
[...$('side').children].forEach(x=>x.classList.toggle('on',x===b));
calc();
});
// sl mode toggle
$('slMode').addEventListener('click',e=>{
const b=e.target.closest('button'); if(!b)return;
slMode=b.dataset.mode;
[...$('slMode').children].forEach(x=>x.classList.toggle('on',x===b));
$('slPct').disabled = slMode!=='pct';
$('slPrice').disabled = slMode!=='price';
calc();
});
// take-profit mode toggle
$('tpMode').addEventListener('click',e=>{
const b=e.target.closest('button'); if(!b)return;
tpMode=b.dataset.mode;
[...$('tpMode').children].forEach(x=>x.classList.toggle('on',x===b));
$('tpRR').disabled = tpMode!=='rr';
$('tp').disabled = tpMode!=='price';
calc();
});
['balance','riskPct','entry','slPct','slPrice','tp','tpRR'].forEach(id=>$(id).addEventListener('input',calc));
const fmtN=(n,d=2)=>isFinite(n)?n.toLocaleString('en-US',{minimumFractionDigits:d,maximumFractionDigits:d}):'—';
const fmtP=n=>isFinite(n)?n.toLocaleString('en-US',{minimumFractionDigits:5,maximumFractionDigits:5}):'—';
function calc(){
const bal=+$('balance').value, riskPct=+$('riskPct').value, entry=+$('entry').value;
const risk = bal*riskPct/100;
$('riskAmt').textContent = isFinite(risk)? fmtN(risk)+' U' : '—';
if(!(entry>0)||!(risk>0)){ clearOut(); return; }
// resolve stop-loss price + distance
let slPrice, slPctVal;
if(slMode==='pct'){
slPctVal=+$('slPct').value;
if(!(slPctVal>0)){ clearOut(); return; }
slPrice = side==='long' ? entry*(1-slPctVal/100) : entry*(1+slPctVal/100);
}else{
slPrice=+$('slPrice').value;
if(!(slPrice>0)){ clearOut(); return; }
slPctVal = Math.abs(entry-slPrice)/entry*100;
}
const dist=Math.abs(entry-slPrice);
if(!(dist>0)){ clearOut(); return; }
const qty = risk/dist; // DOGE 数量
const notional = qty*entry; // 名义价值 U
$('qty').innerHTML = fmtN(qty,0)+' <span class="u">DOGE</span>';
$('notional').innerHTML = fmtN(notional)+' <span class="u">U</span>';
$('slPriceShow').textContent = fmtP(slPrice);
$('slShow').textContent = '止损 '+fmtN(slPctVal)+'%';
// take profit — 两种模式:按盈亏比反算 / 按价格
let tpPrice, rr;
if(tpMode==='rr'){
rr=+$('tpRR').value; // 目标盈亏比
if(rr>0){
// 止盈点位差 = 止损点位差 × 盈亏比,按方向反算止盈价
tpPrice = side==='long' ? entry + dist*rr : entry - dist*rr;
$('tp').value = (+tpPrice.toPrecision(8)); // 同步显示到价格框,方便核对/切换
}
}else{
tpPrice=+$('tp').value;
if(tpPrice>0) rr = Math.abs(tpPrice-entry)/dist;
}
if(tpPrice>0 && rr>0){
const tpDist=Math.abs(tpPrice-entry);
const profit=qty*tpDist;
const tpPct=tpDist/entry*100;
$('rr').textContent = '1 : '+fmtN(rr);
$('tpPriceShow').textContent = fmtP(tpPrice);
$('tpProfit').innerHTML = '+'+fmtN(profit)+' <span class="u">U</span>';
$('tpPctShow').textContent = fmtN(tpPct)+'%';
}else{
$('rr').textContent='—'; $('tpPriceShow').textContent='—';
$('tpProfit').textContent='—'; $('tpPctShow').textContent='—';
}
// leverage ladder
const rows=levs.map(L=>{
const margin=notional/L;
const liq = side==='long' ? entry*(1-1/L) : entry*(1+1/L);
const liqDistPct = 100/L;
// safe if liquidation is farther than stop-loss
const safe = liqDistPct > slPctVal;
return `<tr>
<td>${L}x</td>
<td>${fmtN(margin)} U</td>
<td class="liq">${fmtP(liq)}</td>
<td>${fmtN(liqDistPct)}%</td>
<td><span class="tag ${safe?'ok':'no'}">${safe?'安全':'危险'}</span></td>
</tr>`;
}).join('');
$('ladder').innerHTML=rows;
}
function clearOut(){
['qty','notional','rr','slPriceShow','tpPriceShow','tpProfit','tpPctShow'].forEach(id=>$(id).textContent='—');
$('slShow').textContent=''; $('ladder').innerHTML='';
}
calc();
})();
</script>
</body>
</html>