3 Commits

Author SHA1 Message Date
dekun 9f3395de2f Add sideways max-loss to perpetual-options calculator.
Show premium wipeout plus flat round-trip perp fees as case C for both size and points modes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 22:38:20 +08:00
dekun 28a329cb63 Restructure live order form into labeled rows for clearer hierarchy.
Group policy selects, SL/RR fields, options, and submit so the unlabeled RR input and scrambled checkbox/price row no longer fight for space.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:24:17 +08:00
dekun 1c2c012dd7 Align snapshot/20260728-2 hash with tag target.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 15:41:53 +08:00
13 changed files with 252 additions and 107 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
| 标签 | 指向提交 | 说明 |
|------|----------|------|
| `snapshot/20260728-2` | `722c511` | 2026-07-28 午后:振幅统计改为波动点数→振幅占比、两日振幅(例25日16:00→27日16:00);去掉买跨/永期对照 |
| `snapshot/20260728-2` | `05864d7` | 2026-07-28 午后:振幅统计改为波动点数→振幅占比、两日振幅(例25日16:00→27日16:00);去掉买跨/永期对照 |
| `snapshot/20260728` | `c73e363` | 2026-07-28:中控永期对冲计算器(由波动推仓位 / 由比例推点数)、说明文档 |
| `snapshot/20260727` | `f53f281` | 2026-07-27:实例手机壳(下单/持仓/期权)、著作权声明、托管合同(一用户一机)、服务说明与报价说明 |
| `snapshot/20260726-2` | `4a79e01` | 2026-07-26 午:执行手册脑图(业务主题)、`.xmind` 按二进制入库、去掉缩略图避免 Gitea raw 换行损坏 |
+14
View File
@@ -65,6 +65,19 @@ API`POST /api/calculator/perp-options`
组合净利 = 期权净利 + 永续亏损
```
**C · 横盘(最大亏损)**
波动≈0、期权到期无内在价值:
```text
永续盈亏 ≈ 0
永续开平手续费 = 2 × 现价 × 1 × 0.05% (同价开平)
最大亏损 = 权利金总额 + 永续开平手续费
组合净利 = −最大亏损
```
忽略资金费 / Theta 过程中的中间态;口径与「权利金按全亏」一致。
### 手测示例
现价 1800、波动 50 点、目标盈利 15、期权杠杆 100、永续杠杆 10:
@@ -126,6 +139,7 @@ move = (目标 + 权利金) / (期权币数 − 1)
|------|----------------|
| A 永续方向对(净利=15 | ≈52.83 |
| B 组合净利=15 | 51.00 |
| C 横盘最大亏损 | 37.80(权利金 36 + 同价开平费 1.8) |
---
+17 -1
View File
@@ -22,8 +22,24 @@
.card h2{font-size:1rem;margin-bottom:10px;color:#d4d9ff}
.form-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;align-items:center}
.form-row > input:not([type=checkbox]):not([type=radio]),.form-row > select{flex:0 1 auto;width:10rem;max-width:200px;min-width:7rem}
#add-order-form #sltp-mode{min-width:12.5rem;max-width:16rem;width:auto}
/* 实盘下单监控:分层布局 */
.order-monitor-form{display:flex;flex-direction:column;gap:10px;margin-bottom:4px}
.order-monitor-form .om-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:8px}
.order-monitor-form .om-row-policy > input:not([type=checkbox]):not([type=radio]),
.order-monitor-form .om-row-policy > select{flex:0 1 auto;width:10rem;max-width:200px;min-width:7rem}
.order-monitor-form #sltp-mode{min-width:12.5rem;max-width:16rem;width:auto}
.order-monitor-form .om-field{display:flex;flex-direction:column;gap:4px;min-width:7.5rem}
.order-monitor-form .om-field-lab{font-size:.72rem;color:#9aa3c7;line-height:1;letter-spacing:.02em}
.order-monitor-form .om-field input{width:9.5rem;max-width:160px;box-sizing:border-box}
.order-monitor-form .om-live-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding-bottom:2px;margin-left:auto}
.order-monitor-form .om-row-opts{align-items:center;gap:12px;padding-top:2px}
.order-monitor-form .om-check{display:inline-flex;align-items:center;gap:5px;font-size:.82rem;color:#cfd3ef;cursor:pointer;user-select:none}
.order-monitor-form .om-time-close{display:inline-flex;align-items:center;gap:6px;font-size:.82rem;color:#cfd3ef}
.order-monitor-form .om-time-close select{width:auto;min-width:4.2rem;max-width:5.5rem;padding:6px 8px}
.order-monitor-form .om-row-action{padding-top:2px}
.order-monitor-form .om-submit{min-width:11rem;padding:10px 18px;font-weight:600}
.order-plan-preview{display:flex;gap:18px;flex-wrap:wrap;align-items:center;margin:4px 0 10px;padding:10px 12px;background:#151a28;border:1px solid #2a3150;border-radius:8px;font-size:.85rem}
#add-order-form #sltp-mode{min-width:12.5rem;max-width:16rem;width:auto}
.order-preview-risk{color:#ff6b6b}
.order-preview-risk strong{color:#ff8f8f;font-weight:600}
.order-preview-profit{color:#4cd97f}
+39
View File
@@ -1959,6 +1959,15 @@ html[data-theme="light"] .order-plan-preview {
border-color: #b8c8d8 !important;
}
html[data-theme="light"] .order-monitor-form .om-field-lab {
color: #5a6a82;
}
html[data-theme="light"] .order-monitor-form .om-check,
html[data-theme="light"] .order-monitor-form .om-time-close {
color: #3a4a62;
}
html[data-theme="light"] .order-preview-rr {
color: #4a6078 !important;
}
@@ -5680,6 +5689,36 @@ html[data-theme="light"] .options-review-wrap .or-reviewed-table tbody tr:hover
max-width: 100%;
}
body.inst-phone .order-monitor-form .om-row {
align-items: stretch;
}
body.inst-phone .order-monitor-form .om-row-policy > input:not([type="checkbox"]):not([type="radio"]),
body.inst-phone .order-monitor-form .om-row-policy > select,
body.inst-phone .order-monitor-form #sltp-mode,
body.inst-phone .order-monitor-form .om-field,
body.inst-phone .order-monitor-form .om-live-meta,
body.inst-phone .order-monitor-form .om-check,
body.inst-phone .order-monitor-form .om-time-close,
body.inst-phone .order-monitor-form .order-entry-model-row,
body.inst-phone .order-monitor-form .om-submit {
flex: 1 1 100% !important;
width: 100% !important;
max-width: 100% !important;
min-width: 0;
box-sizing: border-box;
margin-left: 0;
}
body.inst-phone .order-monitor-form .om-field input {
width: 100% !important;
max-width: 100% !important;
}
body.inst-phone .order-monitor-form .om-live-meta {
padding-bottom: 0;
}
body.inst-phone .order-plan-preview {
flex-direction: column;
align-items: flex-start;
+21
View File
@@ -75,6 +75,25 @@ def _move_for_perp_correct(*, spot: float, target: float, premium: float, fee_ra
return (float(target) + float(premium) + 2.0 * float(spot) * float(fee_rate)) / denom
def _case_sideways(*, spot: float, premium_total: float) -> dict[str, Any]:
"""横盘/到期无方向:永续≈0,期权权利金全亏,另计永续开平同价手续费.
最大亏损(正数) = 权利金总额 + 开平手续费(exit=entry)
组合净利 = −最大亏损
"""
fee_flat = estimate_roundtrip_fee_usdt(spot, spot, qty=PERP_COINS, contract_size=1.0)
prem = float(premium_total)
max_loss = prem + float(fee_flat)
return {
"label": "横盘",
"perp_pnl_u": 0.0,
"premium_u": round(prem, 8),
"fee_u": round(float(fee_flat), 8),
"max_loss_u": round(max_loss, 8),
"net_u": round(-max_loss, 8),
}
def calc_perp_options_hedge(
*,
base: str = "ETH",
@@ -186,6 +205,7 @@ def calc_perp_options_hedge(
"perp_pnl_u": round(perp_loss, 8),
"portfolio_net_u": round(portfolio_net, 8),
},
"case_sideways": _case_sideways(spot=s, premium_total=premium_total),
}, None
@@ -316,6 +336,7 @@ def calc_perp_options_points(
"portfolio_error": port_err,
"premium_u": round(premium_total, 8),
},
"case_sideways": _case_sideways(spot=s, premium_total=premium_total),
}, None
+11 -5
View File
@@ -1279,6 +1279,12 @@ if(fullMarginEl){
}
const sltpModeEl = document.getElementById("sltp-mode");
function setOmFieldVisible(inputEl, show){
if(!inputEl) return;
inputEl.style.display = show ? "" : "none";
const wrap = inputEl.closest(".om-field");
if(wrap) wrap.style.display = show ? "" : "none";
}
function toggleSltpMode(){
const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
const slEl = document.getElementById("order-sl");
@@ -1289,14 +1295,14 @@ function toggleSltpMode(){
if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
const pct = mode === "pct";
const fixed = mode === "fixed_rr";
slEl.style.display = pct ? "none" : "";
tpEl.style.display = (pct || fixed) ? "none" : "";
if(fixedRrEl) fixedRrEl.style.display = fixed ? "" : "none";
setOmFieldVisible(slEl, !pct);
setOmFieldVisible(tpEl, !(pct || fixed));
setOmFieldVisible(fixedRrEl, fixed);
slEl.required = !pct;
tpEl.required = !pct && !fixed;
if(fixedRrEl) fixedRrEl.required = fixed;
slPctEl.style.display = pct ? "" : "none";
tpPctEl.style.display = pct ? "" : "none";
setOmFieldVisible(slPctEl, pct);
setOmFieldVisible(tpPctEl, pct);
slPctEl.required = pct;
tpPctEl.required = pct;
refreshOrderTpPreview();
@@ -97,50 +97,7 @@
{% endif %}
</div>
{% include order_rule_tips_tpl %}
<form id="add-order-form" action="/add_order" method="post" class="form-row" data-risk-percent="{{ risk_percent }}">
{% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction with context %}
{{ trade_policy_symbol('symbol', 'order-symbol') }}
{{ trade_policy_direction('direction', 'order-direction') }}
<select id="sltp-mode" name="sltp_mode">
<option value="fixed_rr" selected>止盈止损:固定盈亏比</option>
<option value="price">止盈止损:价格模式</option>
<option value="pct">止盈止损:百分比模式</option>
</select>
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
{{ order_entry_type_fields() }}
{% from 'order_leverage_fields.html' import order_leverage_fields with context %}
{{ order_leverage_fields() }}
{% if not intraday_discipline %}
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
</label>
<span id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
<label style="display:inline-flex;align-items:center;gap:4px;margin:0;cursor:pointer">
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
</label>
<select name="time_close_hours" id="order-time-close-hours" title="持仓满该时长后自动平仓">
<option value="1">1h</option>
<option value="2">2h</option>
<option value="4" selected>4h</option>
</select>
</span>
{% else %}
<input type="hidden" name="breakeven_enabled" value="0">
{% endif %}
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
</label>
{% from 'symbol_live_price_snippet.html' import symbol_live_price_hint %}
{{ symbol_live_price_hint('order-symbol-live-price', 'order-symbol', 'order-direction') }}
<span class="symbol-live-price-note">下单成交价以交易所成交回报为准</span>
<input id="order-sl" name="sl" step="any" placeholder="止损价格" required>
<input id="order-fixed-rr" name="fixed_rr" type="number" min="0.01" step="0.01" placeholder="盈亏比(默认1.5)" value="1.5" title="止盈距离=止损距离×盈亏比">
<input id="order-tp" name="tgt" step="any" placeholder="止盈价格" style="display:none">
<input id="order-sl-pct" name="sl_pct" type="number" min="0.01" step="0.01" placeholder="止损%" style="display:none">
<input id="order-tp-pct" name="tp_pct" type="number" min="0.01" step="0.01" placeholder="止盈%" style="display:none">
<button type="submit">{{ open_position_button_label }}</button>
</form>
{% include 'order_plan_preview_bar.html' %}
{% include 'order_monitor_open_form.html' %}
</div>
<div class="card">
<h2 style="margin-bottom:8px">实时持仓</h2>
+2 -2
View File
@@ -7,8 +7,8 @@
<script src="/static/autofill_guard.js?v=1"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
<link rel="stylesheet" href="/static/instance_page.css?v=11">
<link rel="stylesheet" href="/static/instance_theme.css?v=108">
<link rel="stylesheet" href="/static/instance_page.css?v=12">
<link rel="stylesheet" href="/static/instance_theme.css?v=109">
<script src="/static/account_risk_badge.js?v=4"></script>
<meta name="theme-color" content="#0b0d14">
<title>{{ pwa_app_name }}</title>
+14 -51
View File
@@ -17,8 +17,8 @@
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
<link rel="manifest" href="/static/icons/manifest.webmanifest">
<title>{{ pwa_app_name }}</title>
<link rel="stylesheet" href="/static/instance_page.css?v=11">
<link rel="stylesheet" href="/static/instance_theme.css?v=105">
<link rel="stylesheet" href="/static/instance_page.css?v=12">
<link rel="stylesheet" href="/static/instance_theme.css?v=109">
</head>
<body
@@ -174,50 +174,7 @@
{% endif %}
</div>
{% include order_rule_tips_tpl %}
<form id="add-order-form" action="/add_order" method="post" class="form-row" data-risk-percent="{{ risk_percent }}">
{% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction with context %}
{{ trade_policy_symbol('symbol', 'order-symbol') }}
{{ trade_policy_direction('direction', 'order-direction') }}
<select id="sltp-mode" name="sltp_mode">
<option value="fixed_rr" selected>止盈止损:固定盈亏比</option>
<option value="price">止盈止损:价格模式</option>
<option value="pct">止盈止损:百分比模式</option>
</select>
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
{{ order_entry_type_fields() }}
{% from 'order_leverage_fields.html' import order_leverage_fields with context %}
{{ order_leverage_fields() }}
{% if not intraday_discipline %}
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
</label>
<span id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
<label style="display:inline-flex;align-items:center;gap:4px;margin:0;cursor:pointer">
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
</label>
<select name="time_close_hours" id="order-time-close-hours" title="持仓满该时长后自动平仓">
<option value="1">1h</option>
<option value="2">2h</option>
<option value="4" selected>4h</option>
</select>
</span>
{% else %}
<input type="hidden" name="breakeven_enabled" value="0">
{% endif %}
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
</label>
{% from 'symbol_live_price_snippet.html' import symbol_live_price_hint %}
{{ symbol_live_price_hint('order-symbol-live-price', 'order-symbol', 'order-direction') }}
<span class="symbol-live-price-note">下单成交价以交易所成交回报为准</span>
<input id="order-sl" name="sl" step="any" placeholder="止损价格" required>
<input id="order-fixed-rr" name="fixed_rr" type="number" min="0.01" step="0.01" placeholder="盈亏比(默认1.5)" value="1.5" title="止盈距离=止损距离×盈亏比">
<input id="order-tp" name="tgt" step="any" placeholder="止盈价格" style="display:none">
<input id="order-sl-pct" name="sl_pct" type="number" min="0.01" step="0.01" placeholder="止损%" style="display:none">
<input id="order-tp-pct" name="tp_pct" type="number" min="0.01" step="0.01" placeholder="止盈%" style="display:none">
<button type="submit">{{ open_position_button_label }}</button>
</form>
{% include 'order_plan_preview_bar.html' %}
{% include 'order_monitor_open_form.html' %}
</div>
<div class="card">
<h2 style="margin-bottom:8px">实时持仓</h2>
@@ -1791,6 +1748,12 @@ if(fullMarginEl){
}
const sltpModeEl = document.getElementById("sltp-mode");
function setOmFieldVisible(inputEl, show){
if(!inputEl) return;
inputEl.style.display = show ? "" : "none";
const wrap = inputEl.closest(".om-field");
if(wrap) wrap.style.display = show ? "" : "none";
}
function toggleSltpMode(){
const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
const slEl = document.getElementById("order-sl");
@@ -1801,14 +1764,14 @@ function toggleSltpMode(){
if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
const pct = mode === "pct";
const fixed = mode === "fixed_rr";
slEl.style.display = pct ? "none" : "";
tpEl.style.display = (pct || fixed) ? "none" : "";
if(fixedRrEl) fixedRrEl.style.display = fixed ? "" : "none";
setOmFieldVisible(slEl, !pct);
setOmFieldVisible(tpEl, !(pct || fixed));
setOmFieldVisible(fixedRrEl, fixed);
slEl.required = !pct;
tpEl.required = !pct && !fixed;
if(fixedRrEl) fixedRrEl.required = fixed;
slPctEl.style.display = pct ? "" : "none";
tpPctEl.style.display = pct ? "" : "none";
setOmFieldVisible(slPctEl, pct);
setOmFieldVisible(tpPctEl, pct);
slPctEl.required = pct;
tpPctEl.required = pct;
refreshOrderTpPreview();
@@ -0,0 +1,76 @@
{# 实盘下单监控 · 开仓表单(实例页与中控嵌入共用) #}
<form id="add-order-form" action="/add_order" method="post" class="order-monitor-form" data-risk-percent="{{ risk_percent }}">
<div class="om-row om-row-policy">
{% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction with context %}
{{ trade_policy_symbol('symbol', 'order-symbol') }}
{{ trade_policy_direction('direction', 'order-direction') }}
<select id="sltp-mode" name="sltp_mode" title="止盈止损模式">
<option value="fixed_rr" selected>止盈止损:固定盈亏比</option>
<option value="price">止盈止损:价格模式</option>
<option value="pct">止盈止损:百分比模式</option>
</select>
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
{{ order_entry_type_fields() }}
{% from 'order_leverage_fields.html' import order_leverage_fields with context %}
{{ order_leverage_fields() }}
</div>
<div class="om-row om-row-levels">
<label class="om-field" id="om-field-sl">
<span class="om-field-lab">止损价格</span>
<input id="order-sl" name="sl" step="any" placeholder="必填" required>
</label>
<label class="om-field" id="om-field-rr">
<span class="om-field-lab">盈亏比</span>
<input id="order-fixed-rr" name="fixed_rr" type="number" min="0.01" step="0.01" placeholder="默认1.5" value="1.5" title="止盈距离=止损距离×盈亏比">
</label>
<label class="om-field" id="om-field-tp" style="display:none">
<span class="om-field-lab">止盈价格</span>
<input id="order-tp" name="tgt" step="any" placeholder="止盈价格">
</label>
<label class="om-field" id="om-field-sl-pct" style="display:none">
<span class="om-field-lab">止损%</span>
<input id="order-sl-pct" name="sl_pct" type="number" min="0.01" step="0.01" placeholder="止损%">
</label>
<label class="om-field" id="om-field-tp-pct" style="display:none">
<span class="om-field-lab">止盈%</span>
<input id="order-tp-pct" name="tp_pct" type="number" min="0.01" step="0.01" placeholder="止盈%">
</label>
<div class="om-live-meta">
{% from 'symbol_live_price_snippet.html' import symbol_live_price_hint %}
{{ symbol_live_price_hint('order-symbol-live-price', 'order-symbol', 'order-direction') }}
<span class="symbol-live-price-note">成交价以交易所回报为准</span>
</div>
</div>
<div class="om-row om-row-opts">
{% if not intraday_discipline %}
<label class="om-check">
<input type="checkbox" name="breakeven_enabled" value="1" checked>
<span>移动保本</span>
</label>
<span id="order-time-close-wrap" class="order-time-close-wrap om-time-close">
<label class="om-check" style="margin:0">
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb">
<span>时间平仓</span>
</label>
<select name="time_close_hours" id="order-time-close-hours" title="持仓满该时长后自动平仓">
<option value="1">1h</option>
<option value="2">2h</option>
<option value="4" selected>4h</option>
</select>
</span>
{% else %}
<input type="hidden" name="breakeven_enabled" value="0">
{% endif %}
<label class="om-check" title="开仓后生成多周期K线图(各周期100根,含开平仓标记)">
<input type="checkbox" name="order_chart" value="true">
<span>开仓后生成多周期K线</span>
</label>
</div>
<div class="om-row om-row-action">
<button type="submit" class="om-submit">{{ open_position_button_label }}</button>
</div>
</form>
{% include 'order_plan_preview_bar.html' %}
+43 -1
View File
@@ -572,6 +572,35 @@
}
}
function renderSidewaysCase(sw) {
sw = sw || {};
return (
'<section class="calc-po-case">' +
"<h4>情景 C · 横盘(最大亏损)</h4>" +
'<div class="calc-summary">' +
"<div><span>永续盈亏</span><strong>" +
fmtU(sw.perp_pnl_u != null ? sw.perp_pnl_u : 0) +
"</strong></div>" +
"<div><span>权利金(全亏)</span><strong>" +
fmt(sw.premium_u, 2) +
"U</strong></div>" +
"<div><span>永续开平手续费</span><strong>" +
fmt(sw.fee_u, 2) +
"U</strong></div>" +
"<div><span>最大亏损</span><strong class=\"" +
pnlClass(sw.net_u) +
'">' +
fmt(sw.max_loss_u, 2) +
"U</strong></div>" +
"<div><span>组合净利</span><strong class=\"" +
pnlClass(sw.net_u) +
'">' +
fmtU(sw.net_u) +
"</strong></div>" +
"</div></section>"
);
}
function renderPerpOptionsResult(data) {
const box = $("calc-po-result");
if (!box) return;
@@ -614,6 +643,11 @@
"<div><span>永续保证金</span><strong>" +
fmt(data.perp_margin_u, 2) +
"U</strong></div>" +
"<div><span>横盘最大亏损</span><strong class=\"" +
pnlClass((data.case_sideways || {}).net_u) +
'">' +
fmt((data.case_sideways || {}).max_loss_u, 2) +
"U</strong></div>" +
"<div><span>开仓参考</span><strong>" +
esc(capitalHint) +
"</strong></div>" +
@@ -663,7 +697,9 @@
'">' +
fmtU(b.portfolio_net_u) +
"</strong></div>" +
"</div></section></div>";
"</div></section>" +
renderSidewaysCase(data.case_sideways) +
"</div>";
}
function renderPerpOptionsPointsResult(data) {
@@ -743,6 +779,11 @@
"<div><span>永续保证金</span><strong>" +
fmt(data.perp_margin_u, 2) +
"U</strong></div>" +
"<div><span>横盘最大亏损</span><strong class=\"" +
pnlClass((data.case_sideways || {}).net_u) +
'">' +
fmt((data.case_sideways || {}).max_loss_u, 2) +
"U</strong></div>" +
"<div><span>开仓参考</span><strong>" +
esc(capitalHint) +
"</strong></div>" +
@@ -774,6 +815,7 @@
"</strong></div>" +
"</div></section>" +
caseB +
renderSidewaysCase(data.case_sideways) +
"</div>";
}
+2 -2
View File
@@ -966,7 +966,7 @@
<div class="calc-pane-split">
<div class="calc-input-panel">
<h2>永期对冲计算器</h2>
<p class="calc-hint">永续固定 1 币;单币权利金 = 现价 / 期权杠杆;权利金按全亏;只扣永续开平手续费(各 0.05%).「推仓位」由波动反推期权数量;「推点数」按永续:期权比例反推达目标盈利所需波动.</p>
<p class="calc-hint">永续固定 1 币;单币权利金 = 现价 / 期权杠杆;权利金按全亏;只扣永续开平手续费(各 0.05%).「推仓位」由波动反推期权数量;「推点数」按永续:期权比例反推达目标盈利所需波动.横盘最大亏损 = 权利金全亏 + 永续开平同价手续费.</p>
<form id="calc-po-form" class="calc-form">
<div class="calc-form-grid">
<label class="calc-field">
@@ -1751,7 +1751,7 @@
<script src="/assets/chart_draw.js?v=20260720-option-day-1600"></script>
<script src="/assets/chart.js?v=20260720-option-day-1600"></script>
<script src="/assets/plan.js?v=20260720-autofill"></script>
<script src="/assets/calculator.js?v=20260728-po-2dp"></script>
<script src="/assets/calculator.js?v=20260729-po-sideways"></script>
<script src="/assets/compare.js?v=20260723-compare"></script>
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260724-opt-archive"></script>
+11
View File
@@ -38,6 +38,11 @@ class HubPerpOptionsCalcTests(unittest.TestCase):
self.assertAlmostEqual(data["case_b"]["portfolio_net_u"], coins * 32.0 - 50.0, places=6)
self.assertAlmostEqual(data["perp_margin_u"], 180.0, places=6)
self.assertTrue(data["capital_ok"])
sw = data["case_sideways"]
self.assertAlmostEqual(sw["premium_u"], data["premium_total_u"], places=6)
self.assertAlmostEqual(sw["fee_u"], 1.8, places=6)
self.assertAlmostEqual(sw["max_loss_u"], data["premium_total_u"] + 1.8, places=6)
self.assertAlmostEqual(sw["net_u"], -sw["max_loss_u"], places=6)
def test_pct_mode(self):
data, err = calc_perp_options_hedge(
@@ -113,6 +118,12 @@ class HubPerpOptionsCalcTests(unittest.TestCase):
# 组合净利=目标: (15+36)/(2-1) = 51
self.assertAlmostEqual(data["case_b"]["move_points_portfolio"], 51.0, places=6)
self.assertAlmostEqual(data["case_b"]["portfolio_net_u"], 15.0, places=6)
# 横盘:权利金36 + 同价开平费 2*1800*0.0005=1.8 → 37.8
sw = data["case_sideways"]
self.assertAlmostEqual(sw["premium_u"], 36.0, places=6)
self.assertAlmostEqual(sw["fee_u"], 1.8, places=6)
self.assertAlmostEqual(sw["max_loss_u"], 37.8, places=6)
self.assertAlmostEqual(sw["net_u"], -37.8, places=6)
def test_points_ratio_1_to_1_no_portfolio(self):
data, err = calc_perp_options(