diff --git a/index.html b/index.html
index a0a3e3d..1b65c68 100644
--- a/index.html
+++ b/index.html
@@ -183,6 +183,12 @@
+
+
+
+
+
+
@@ -247,8 +253,8 @@
- 说明:真正把亏损锁定在风险金额的是止损单,不是杠杆。止盈默认按盈亏比自动反算——
- 止盈点位差 = 止损点位差 × 盈亏比 R,按方向算出止盈价;也可切「按价格」手动输入。
+ 说明:真正把亏损锁定在风险金额的是止损单,不是杠杆。止盈默认按盈亏比自动反算(止盈点位差 = 止损点位差 × R);
+ 开启点位差对称后,输入止盈或止损价,另一个会按相同点位差自动镜像(即 1:1)。所有输入会自动记住上次的值。
表中「状态」校验爆仓价是否在止损之外——安全表示会先触发止损、不会被强平;危险表示杠杆过高、爆仓在止损之前。
爆仓价为逐仓简化估算(爆仓距离≈1/杠杆,忽略维持保证金与手续费),实盘以交易所显示为准。这只是数学计算,不构成投资建议。
@@ -258,36 +264,44 @@
(function(){
const $=id=>document.getElementById(id);
const levs=[3,5,10,20,25,50,75,100];
- let side='long', slMode='pct', tpMode='rr';
+ const KEY='posCalc.v1';
+ const FIELDS=['balance','riskPct','entry','slPct','slPrice','tp','tpRR'];
+ let side='long', slMode='pct', tpMode='rr', symOn=false, lastPrice='sl';
+
+ const setOn=(group,val)=>[...$(group).children].forEach(x=>x.classList.toggle('on', x.dataset.mode===val || x.dataset.side===val));
+ function applySlMode(){ setOn('slMode',slMode); $('slPct').disabled=slMode!=='pct'; $('slPrice').disabled=slMode!=='price'; }
+ function applyTpMode(){ setOn('tpMode',tpMode); $('tpRR').disabled=tpMode!=='rr'; $('tp').disabled=tpMode!=='price'; }
+ function applySym(){
+ $('symBtn').classList.toggle('on',symOn);
+ $('symBtn').textContent = symOn ? '🔗 点位差对称:开' : '🔗 点位差对称:关';
+ if(symOn){ slMode='price'; tpMode='price'; applySlMode(); applyTpMode(); }
+ }
// 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();
+ side=b.dataset.side; setOn('side',side); 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();
+ slMode=b.dataset.mode; if(symOn){ symOn=false; applySym(); }
+ applySlMode(); 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();
+ tpMode=b.dataset.mode; if(symOn){ symOn=false; applySym(); }
+ applyTpMode(); calc();
});
+ // 点位差对称 toggle
+ $('symBtn').addEventListener('click',()=>{ symOn=!symOn; applySym(); calc(); });
- ['balance','riskPct','entry','slPct','slPrice','tp','tpRR'].forEach(id=>$(id).addEventListener('input',calc));
+ // 记录最后编辑的价格框,对称时以它为基准镜像另一个
+ $('slPrice').addEventListener('input',()=>{ lastPrice='sl'; });
+ $('tp').addEventListener('input',()=>{ lastPrice='tp'; });
+
+ FIELDS.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}):'—';
@@ -298,26 +312,55 @@
return n.toLocaleString('en-US',{minimumFractionDigits:0,maximumFractionDigits:d});
};
+ // 持久化:记住上次输入而非固定默认值
+ function save(){
+ try{
+ const d={ side, slMode, tpMode, symOn, lastPrice };
+ FIELDS.forEach(id=>d[id]=$(id).value);
+ localStorage.setItem(KEY, JSON.stringify(d));
+ }catch(e){}
+ }
+ function restore(){
+ let d; try{ d=JSON.parse(localStorage.getItem(KEY)||'null'); }catch(e){ d=null; }
+ if(!d) return;
+ FIELDS.forEach(id=>{ if(d[id]!==undefined && d[id]!=='') $(id).value=d[id]; });
+ if(d.side) side=d.side;
+ if(d.slMode) slMode=d.slMode;
+ if(d.tpMode) tpMode=d.tpMode;
+ symOn=!!d.symOn; lastPrice=d.lastPrice||'sl';
+ }
+
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; }
+ if(!(entry>0)||!(risk>0)){ clearOut(); save(); return; }
+
+ // 对称联动:以最后编辑的价格框为准,关于入场价镜像出另一个(等距,多空自适应)
+ if(symOn){
+ if(lastPrice==='tp'){
+ const t=+$('tp').value;
+ if(t>0) $('slPrice').value = (+(2*entry - t).toPrecision(8));
+ }else{
+ const s=+$('slPrice').value;
+ if(s>0) $('tp').value = (+(2*entry - s).toPrecision(8));
+ }
+ }
// resolve stop-loss price + distance
let slPrice, slPctVal;
if(slMode==='pct'){
slPctVal=+$('slPct').value;
- if(!(slPctVal>0)){ clearOut(); return; }
+ if(!(slPctVal>0)){ clearOut(); save(); return; }
slPrice = side==='long' ? entry*(1-slPctVal/100) : entry*(1+slPctVal/100);
}else{
slPrice=+$('slPrice').value;
- if(!(slPrice>0)){ clearOut(); return; }
+ if(!(slPrice>0)){ clearOut(); save(); return; }
slPctVal = Math.abs(entry-slPrice)/entry*100;
}
const dist=Math.abs(entry-slPrice);
- if(!(dist>0)){ clearOut(); return; }
+ if(!(dist>0)){ clearOut(); save(); return; }
const qty = risk/dist; // 合约数量
const notional = qty*entry; // 名义价值 U
@@ -370,6 +413,7 @@
`;
}).join('');
$('ladder').innerHTML=rows;
+ save();
}
function clearOut(){
@@ -377,6 +421,10 @@
$('slShow').textContent=''; $('ladder').innerHTML='';
}
+ // 初始化:先恢复上次输入,再应用状态,最后计算
+ restore();
+ setOn('side',side);
+ applySlMode(); applyTpMode(); applySym();
calc();
})();