Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 644dcdf092 | |||
| d89aff3ad6 | |||
| 9f3395de2f | |||
| 28a329cb63 | |||
| 1c2c012dd7 |
+1
-1
@@ -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/20260728` | `c73e363` | 2026-07-28:中控永期对冲计算器(由波动推仓位 / 由比例推点数)、说明文档 |
|
||||||
| `snapshot/20260727` | `f53f281` | 2026-07-27:实例手机壳(下单/持仓/期权)、著作权声明、托管合同(一用户一机)、服务说明与报价说明 |
|
| `snapshot/20260727` | `f53f281` | 2026-07-27:实例手机壳(下单/持仓/期权)、著作权声明、托管合同(一用户一机)、服务说明与报价说明 |
|
||||||
| `snapshot/20260726-2` | `4a79e01` | 2026-07-26 午:执行手册脑图(业务主题)、`.xmind` 按二进制入库、去掉缩略图避免 Gitea raw 换行损坏 |
|
| `snapshot/20260726-2` | `4a79e01` | 2026-07-26 午:执行手册脑图(业务主题)、`.xmind` 按二进制入库、去掉缩略图避免 Gitea raw 换行损坏 |
|
||||||
|
|||||||
+38
-12
@@ -65,6 +65,19 @@ API:`POST /api/calculator/perp-options`
|
|||||||
组合净利 = 期权净利 + 永续亏损
|
组合净利 = 期权净利 + 永续亏损
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**C · 横盘(最大亏损)**
|
||||||
|
|
||||||
|
波动≈0、期权到期无内在价值:
|
||||||
|
|
||||||
|
```text
|
||||||
|
永续盈亏 ≈ 0
|
||||||
|
永续开平手续费 = 2 × 现价 × 1 × 0.05% (同价开平)
|
||||||
|
最大亏损 = 权利金总额 + 永续开平手续费
|
||||||
|
组合净利 = −最大亏损
|
||||||
|
```
|
||||||
|
|
||||||
|
忽略资金费 / Theta 过程中的中间态;口径与「权利金按全亏」一致。
|
||||||
|
|
||||||
### 手测示例
|
### 手测示例
|
||||||
|
|
||||||
现价 1800、波动 50 点、目标盈利 15、期权杠杆 100、永续杠杆 10:
|
现价 1800、波动 50 点、目标盈利 15、期权杠杆 100、永续杠杆 10:
|
||||||
@@ -80,16 +93,19 @@ API:`POST /api/calculator/perp-options`
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 模式二:由比例推波动点数(`calc_mode=points`)
|
## 模式二:由币数推波动点数(`calc_mode=points`)
|
||||||
|
|
||||||
已知永续:期权比例(如 **1:2**)、目标盈利、期权杠杆 → 反推两套情景要涨/跌多少点才能达到目标。
|
已知永续币数 / 期权币数(如 **1:2** 或 **2:4**)、目标盈利、期权杠杆 → 反推两套情景要涨/跌多少点才能达到目标。
|
||||||
|
|
||||||
|
**按绝对币数**,不再把输入归一到「永续 1 币」。填 2 与 4 → 永续 2 币 + 期权 4 币(权利金、保证金、手续费均按 2 倍于 1:2 放大;达同一目标盈利所需点数会变小)。
|
||||||
|
|
||||||
### 仓位
|
### 仓位
|
||||||
|
|
||||||
```text
|
```text
|
||||||
永续币数 = 1
|
永续币数 = 输入的永续币数
|
||||||
期权币数 = 1 × (期权比例 / 永续比例) # 1:2 → 2 币
|
期权币数 = 输入的期权币数
|
||||||
权利金总额 = 期权币数 × (现价 / 期权杠杆)
|
权利金总额 = 期权币数 × (现价 / 期权杠杆)
|
||||||
|
永续保证金 = 现价 × 永续币数 / 永续杠杆
|
||||||
```
|
```
|
||||||
|
|
||||||
### 情景 A · 永续方向对
|
### 情景 A · 永续方向对
|
||||||
@@ -97,10 +113,10 @@ API:`POST /api/calculator/perp-options`
|
|||||||
净利 = 目标盈利:
|
净利 = 目标盈利:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
move − 权利金 − fee(move) = 目标
|
qty×move − 权利金 − fee(move,qty) = 目标
|
||||||
fee(move) = (2×现价 + move) × 0.05%
|
fee = (2×现价 + move) × qty × 0.05%
|
||||||
|
|
||||||
move = (目标 + 权利金 + 2×现价×0.05%) / (1 − 0.05%)
|
move = (目标 + 权利金 + 2×现价×qty×0.05%) / (qty × (1 − 0.05%))
|
||||||
```
|
```
|
||||||
|
|
||||||
### 情景 B · 期权方向对(以组合净利为准)
|
### 情景 B · 期权方向对(以组合净利为准)
|
||||||
@@ -108,24 +124,34 @@ move = (目标 + 权利金 + 2×现价×0.05%) / (1 − 0.05%)
|
|||||||
组合净利 = 目标盈利:
|
组合净利 = 目标盈利:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
组合 = 期权币数×move − 权利金 − 1×move
|
组合 = 期权币数×move − 权利金 − 永续币数×move
|
||||||
= move×(期权币数 − 1) − 权利金
|
= move×(期权币数 − 永续币数) − 权利金
|
||||||
|
|
||||||
move = (目标 + 权利金) / (期权币数 − 1)
|
move = (目标 + 权利金) / (期权币数 − 永续币数)
|
||||||
```
|
```
|
||||||
|
|
||||||
要求期权币数 > 永续币数(比例须使期权侧更重,如 1:2);若为 1:1,组合恒为 −权利金,无法解出正目标。
|
要求期权币数 > 永续币数;若相等,组合恒为 −权利金,无法解出正目标。
|
||||||
|
|
||||||
结果区展示:所需波动点数(及折合%)、组合净利、其中期权净利、其中永续盈亏。
|
结果区展示:所需波动点数(及折合%)、组合净利、其中期权净利、其中永续盈亏。
|
||||||
|
|
||||||
### 手测示例
|
### 手测示例
|
||||||
|
|
||||||
现价 1800、目标 15、期权杠杆 100、比例 1:2 → 权利金总额 36U:
|
现价 1800、目标 15、期权杠杆 100、币数 1:2 → 权利金总额 36U:
|
||||||
|
|
||||||
| 情景 | 所需点数(约) |
|
| 情景 | 所需点数(约) |
|
||||||
|------|----------------|
|
|------|----------------|
|
||||||
| A 永续方向对(净利=15) | ≈52.83 |
|
| A 永续方向对(净利=15) | ≈52.83 |
|
||||||
| B 组合净利=15 | 51.00 |
|
| B 组合净利=15 | 51.00 |
|
||||||
|
| C 横盘最大亏损 | 37.80(权利金 36 + 同价开平费 1.8) |
|
||||||
|
|
||||||
|
币数 **2:4**(权利金 72U、保证金 360U):
|
||||||
|
|
||||||
|
| 情景 | 约值 |
|
||||||
|
|------|------|
|
||||||
|
| 仓位 | 永续 2 币 / 期权 4 币(400 张) |
|
||||||
|
| A 所需点数 | ≈45.32 |
|
||||||
|
| B 组合达目标 | 43.50 |
|
||||||
|
| C 横盘最大亏损 | 75.60 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -294,6 +294,7 @@
|
|||||||
async function fetchTabHtml(tab) {
|
async function fetchTabHtml(tab) {
|
||||||
const r = await fetch(embedPageUrl(tab), {
|
const r = await fetch(embedPageUrl(tab), {
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
|
cache: "no-store",
|
||||||
headers: { "X-Instance-Soft-Nav": "1" },
|
headers: { "X-Instance-Soft-Nav": "1" },
|
||||||
});
|
});
|
||||||
const ct = (r.headers.get("content-type") || "").toLowerCase();
|
const ct = (r.headers.get("content-type") || "").toLowerCase();
|
||||||
|
|||||||
@@ -22,8 +22,24 @@
|
|||||||
.card h2{font-size:1rem;margin-bottom:10px;color:#d4d9ff}
|
.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{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}
|
.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}
|
.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{color:#ff6b6b}
|
||||||
.order-preview-risk strong{color:#ff8f8f;font-weight:600}
|
.order-preview-risk strong{color:#ff8f8f;font-weight:600}
|
||||||
.order-preview-profit{color:#4cd97f}
|
.order-preview-profit{color:#4cd97f}
|
||||||
|
|||||||
@@ -1959,6 +1959,15 @@ html[data-theme="light"] .order-plan-preview {
|
|||||||
border-color: #b8c8d8 !important;
|
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 {
|
html[data-theme="light"] .order-preview-rr {
|
||||||
color: #4a6078 !important;
|
color: #4a6078 !important;
|
||||||
}
|
}
|
||||||
@@ -5680,6 +5689,36 @@ html[data-theme="light"] .options-review-wrap .or-reviewed-table tbody tr:hover
|
|||||||
max-width: 100%;
|
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 {
|
body.inst-phone .order-plan-preview {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|||||||
@@ -63,16 +63,51 @@ def _parse_base_common(
|
|||||||
}, None
|
}, None
|
||||||
|
|
||||||
|
|
||||||
def _move_for_perp_correct(*, spot: float, target: float, premium: float, fee_rate: float) -> float:
|
def _move_for_perp_correct(
|
||||||
"""净利 = move − premium − fee(move) = target → 解 move.
|
*,
|
||||||
|
spot: float,
|
||||||
|
target: float,
|
||||||
|
premium: float,
|
||||||
|
fee_rate: float,
|
||||||
|
perp_coins: float = 1.0,
|
||||||
|
) -> float:
|
||||||
|
"""净利 = qty*move − premium − fee(move,qty) = target → 解 move.
|
||||||
|
|
||||||
fee = (2*spot + move) * fee_rate
|
fee = (2*spot + move) * qty * fee_rate
|
||||||
move*(1-fee_rate) = target + premium + 2*spot*fee_rate
|
qty*move*(1-fee_rate) = target + premium + 2*spot*qty*fee_rate
|
||||||
"""
|
"""
|
||||||
denom = 1.0 - float(fee_rate)
|
qty = float(perp_coins)
|
||||||
|
if qty <= 0:
|
||||||
|
return 0.0
|
||||||
|
denom = qty * (1.0 - float(fee_rate))
|
||||||
if denom <= 0:
|
if denom <= 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
return (float(target) + float(premium) + 2.0 * float(spot) * float(fee_rate)) / denom
|
return (float(target) + float(premium) + 2.0 * float(spot) * qty * float(fee_rate)) / denom
|
||||||
|
|
||||||
|
|
||||||
|
def _case_sideways(
|
||||||
|
*,
|
||||||
|
spot: float,
|
||||||
|
premium_total: float,
|
||||||
|
perp_coins: float = 1.0,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""横盘/到期无方向:永续≈0,期权权利金全亏,另计永续开平同价手续费.
|
||||||
|
|
||||||
|
最大亏损(正数) = 权利金总额 + 开平手续费(exit=entry)
|
||||||
|
组合净利 = −最大亏损
|
||||||
|
"""
|
||||||
|
qty = float(perp_coins) if float(perp_coins) > 0 else PERP_COINS
|
||||||
|
fee_flat = estimate_roundtrip_fee_usdt(spot, spot, qty=qty, 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(
|
def calc_perp_options_hedge(
|
||||||
@@ -186,6 +221,7 @@ def calc_perp_options_hedge(
|
|||||||
"perp_pnl_u": round(perp_loss, 8),
|
"perp_pnl_u": round(perp_loss, 8),
|
||||||
"portfolio_net_u": round(portfolio_net, 8),
|
"portfolio_net_u": round(portfolio_net, 8),
|
||||||
},
|
},
|
||||||
|
"case_sideways": _case_sideways(spot=s, premium_total=premium_total),
|
||||||
}, None
|
}, None
|
||||||
|
|
||||||
|
|
||||||
@@ -201,12 +237,12 @@ def calc_perp_options_points(
|
|||||||
ratio_opt: float = 2.0,
|
ratio_opt: float = 2.0,
|
||||||
ct_mult: float = DEFAULT_CT_MULT,
|
ct_mult: float = DEFAULT_CT_MULT,
|
||||||
) -> Tuple[Optional[dict[str, Any]], Optional[str]]:
|
) -> Tuple[Optional[dict[str, Any]], Optional[str]]:
|
||||||
"""按永续:期权比例 + 目标盈利,反推两套情景所需波动点数.
|
"""按永续/期权币数 + 目标盈利,反推两套情景所需波动点数.
|
||||||
|
|
||||||
永续币数固定为 ratio 归一后的 1 币侧(perp_coins = PERP_COINS).
|
永续币数 = ratio_perp, 期权币数 = ratio_opt(按绝对币数,不再归一到 1 币).
|
||||||
期权币数 = PERP_COINS * (ratio_opt / ratio_perp),例 1:2 → 2 币.
|
例 2:4 → 永续 2 币 + 期权 4 币;1:2 → 永续 1 币 + 期权 2 币.
|
||||||
|
|
||||||
A 永续方向对: move − premium − fee(move) = 目标盈利
|
A 永续方向对: qty*move − premium − fee(move,qty) = 目标盈利
|
||||||
B 期权方向对:
|
B 期权方向对:
|
||||||
- 期权净利达目标: opt_coins*move − premium = 目标
|
- 期权净利达目标: opt_coins*move − premium = 目标
|
||||||
- 组合净利达目标: move*(opt_coins − perp_coins) − premium = 目标
|
- 组合净利达目标: move*(opt_coins − perp_coins) − premium = 目标
|
||||||
@@ -226,7 +262,7 @@ def calc_perp_options_points(
|
|||||||
rp = _f(ratio_perp)
|
rp = _f(ratio_perp)
|
||||||
ro = _f(ratio_opt)
|
ro = _f(ratio_opt)
|
||||||
if rp is None or ro is None or rp <= 0 or ro <= 0:
|
if rp is None or ro is None or rp <= 0 or ro <= 0:
|
||||||
return None, "永续:期权比例须大于 0"
|
return None, "永续/期权币数须大于 0"
|
||||||
|
|
||||||
s = common["spot"]
|
s = common["spot"]
|
||||||
capital = common["capital"]
|
capital = common["capital"]
|
||||||
@@ -235,28 +271,35 @@ def calc_perp_options_points(
|
|||||||
o_lev = common["o_lev"]
|
o_lev = common["o_lev"]
|
||||||
ct = common["ct"]
|
ct = common["ct"]
|
||||||
prem_per_coin = common["prem_per_coin"]
|
prem_per_coin = common["prem_per_coin"]
|
||||||
margin = common["margin"]
|
|
||||||
fee_rate = common["fee_rate"]
|
fee_rate = common["fee_rate"]
|
||||||
b = (base or "ETH").strip().upper()
|
b = (base or "ETH").strip().upper()
|
||||||
|
|
||||||
opt_coins = PERP_COINS * (ro / rp)
|
perp_coins = rp
|
||||||
|
opt_coins = ro
|
||||||
premium_total = opt_coins * prem_per_coin
|
premium_total = opt_coins * prem_per_coin
|
||||||
opt_sheets = opt_coins / ct
|
opt_sheets = opt_coins / ct
|
||||||
|
margin = (s * perp_coins) / p_lev
|
||||||
|
|
||||||
move_a = _move_for_perp_correct(spot=s, target=target, premium=premium_total, fee_rate=fee_rate)
|
move_a = _move_for_perp_correct(
|
||||||
|
spot=s,
|
||||||
|
target=target,
|
||||||
|
premium=premium_total,
|
||||||
|
fee_rate=fee_rate,
|
||||||
|
perp_coins=perp_coins,
|
||||||
|
)
|
||||||
if move_a <= 0:
|
if move_a <= 0:
|
||||||
return None, "无法解出永续方向对所需点数"
|
return None, "无法解出永续方向对所需点数"
|
||||||
|
|
||||||
fee_a = estimate_roundtrip_fee_usdt(s, s + move_a, qty=PERP_COINS, contract_size=1.0)
|
fee_a = estimate_roundtrip_fee_usdt(s, s + move_a, qty=perp_coins, contract_size=1.0)
|
||||||
net_a = move_a * PERP_COINS - premium_total - fee_a
|
net_a = move_a * perp_coins - premium_total - fee_a
|
||||||
|
|
||||||
# 期权净利 = 目标
|
# 期权净利 = 目标
|
||||||
move_b_opt = (target + premium_total) / opt_coins
|
move_b_opt = (target + premium_total) / opt_coins
|
||||||
opt_net_at_b_opt = opt_coins * move_b_opt - premium_total
|
opt_net_at_b_opt = opt_coins * move_b_opt - premium_total
|
||||||
portfolio_at_b_opt = opt_net_at_b_opt - move_b_opt * PERP_COINS
|
portfolio_at_b_opt = opt_net_at_b_opt - move_b_opt * perp_coins
|
||||||
|
|
||||||
# 组合净利 = 目标
|
# 组合净利 = 目标
|
||||||
edge = opt_coins - PERP_COINS
|
edge = opt_coins - perp_coins
|
||||||
if edge <= 0:
|
if edge <= 0:
|
||||||
move_b_port = None
|
move_b_port = None
|
||||||
port_err = "期权币数须大于永续币数,组合才能在方向对时赚到目标盈利"
|
port_err = "期权币数须大于永续币数,组合才能在方向对时赚到目标盈利"
|
||||||
@@ -265,7 +308,7 @@ def calc_perp_options_points(
|
|||||||
port_err = None
|
port_err = None
|
||||||
if move_b_port is not None:
|
if move_b_port is not None:
|
||||||
opt_net_at_b_port = opt_coins * move_b_port - premium_total
|
opt_net_at_b_port = opt_coins * move_b_port - premium_total
|
||||||
portfolio_at_b_port = opt_net_at_b_port - move_b_port * PERP_COINS
|
portfolio_at_b_port = opt_net_at_b_port - move_b_port * perp_coins
|
||||||
else:
|
else:
|
||||||
opt_net_at_b_port = None
|
opt_net_at_b_port = None
|
||||||
portfolio_at_b_port = None
|
portfolio_at_b_port = None
|
||||||
@@ -279,7 +322,7 @@ def calc_perp_options_points(
|
|||||||
"ratio_perp": round(rp, 8),
|
"ratio_perp": round(rp, 8),
|
||||||
"ratio_opt": round(ro, 8),
|
"ratio_opt": round(ro, 8),
|
||||||
"ratio_label": f"{_fmt_ratio(rp)}:{_fmt_ratio(ro)}",
|
"ratio_label": f"{_fmt_ratio(rp)}:{_fmt_ratio(ro)}",
|
||||||
"perp_coins": PERP_COINS,
|
"perp_coins": round(perp_coins, 8),
|
||||||
"opt_coins": round(opt_coins, 8),
|
"opt_coins": round(opt_coins, 8),
|
||||||
"opt_sheets": round(opt_sheets, 8),
|
"opt_sheets": round(opt_sheets, 8),
|
||||||
"perp_leverage": round(p_lev, 8),
|
"perp_leverage": round(p_lev, 8),
|
||||||
@@ -294,7 +337,7 @@ def calc_perp_options_points(
|
|||||||
"label": "永续方向对",
|
"label": "永续方向对",
|
||||||
"move_points": round(move_a, 8),
|
"move_points": round(move_a, 8),
|
||||||
"move_pct": round(move_a / s * 100.0, 8),
|
"move_pct": round(move_a / s * 100.0, 8),
|
||||||
"perp_pnl_u": round(move_a * PERP_COINS, 8),
|
"perp_pnl_u": round(move_a * perp_coins, 8),
|
||||||
"premium_u": round(premium_total, 8),
|
"premium_u": round(premium_total, 8),
|
||||||
"fee_u": round(fee_a, 8),
|
"fee_u": round(fee_a, 8),
|
||||||
"net_u": round(net_a, 8),
|
"net_u": round(net_a, 8),
|
||||||
@@ -316,6 +359,9 @@ def calc_perp_options_points(
|
|||||||
"portfolio_error": port_err,
|
"portfolio_error": port_err,
|
||||||
"premium_u": round(premium_total, 8),
|
"premium_u": round(premium_total, 8),
|
||||||
},
|
},
|
||||||
|
"case_sideways": _case_sideways(
|
||||||
|
spot=s, premium_total=premium_total, perp_coins=perp_coins
|
||||||
|
),
|
||||||
}, None
|
}, None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import os
|
|||||||
from typing import Callable
|
from typing import Callable
|
||||||
from urllib.parse import parse_qsl, urlencode, urlsplit
|
from urllib.parse import parse_qsl, urlencode, urlsplit
|
||||||
|
|
||||||
from flask import Flask, Response, jsonify, redirect, request, session
|
from flask import Flask, Response, jsonify, make_response, redirect, request, session
|
||||||
from jinja2 import ChoiceLoader, FileSystemLoader
|
from jinja2 import ChoiceLoader, FileSystemLoader
|
||||||
|
|
||||||
EMBED_TABS: tuple[str, ...] = (
|
EMBED_TABS: tuple[str, ...] = (
|
||||||
@@ -184,7 +184,10 @@ def register_embed_routes(
|
|||||||
if tab not in EMBED_TABS:
|
if tab not in EMBED_TABS:
|
||||||
tab = "trade"
|
tab = "trade"
|
||||||
session["hub_embed_shell"] = True
|
session["hub_embed_shell"] = True
|
||||||
return render_main_page_fn(tab, embed_mode="shell")
|
resp = make_response(render_main_page_fn(tab, embed_mode="shell"))
|
||||||
|
resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
|
||||||
|
resp.headers["Pragma"] = "no-cache"
|
||||||
|
return resp
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@app.route("/api/embed/page/<tab>")
|
@app.route("/api/embed/page/<tab>")
|
||||||
@@ -198,7 +201,10 @@ def register_embed_routes(
|
|||||||
html = render_main_page_fn(tab, embed_mode="fragment")
|
html = render_main_page_fn(tab, embed_mode="fragment")
|
||||||
if isinstance(html, Response):
|
if isinstance(html, Response):
|
||||||
html = html.get_data(as_text=True)
|
html = html.get_data(as_text=True)
|
||||||
return jsonify({"ok": True, "page": tab, "html": html})
|
resp = jsonify({"ok": True, "page": tab, "html": html})
|
||||||
|
resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
|
||||||
|
resp.headers["Pragma"] = "no-cache"
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def pwa_app_name(exchange_key: str) -> str:
|
def pwa_app_name(exchange_key: str) -> str:
|
||||||
|
|||||||
@@ -1279,6 +1279,12 @@ if(fullMarginEl){
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sltpModeEl = document.getElementById("sltp-mode");
|
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(){
|
function toggleSltpMode(){
|
||||||
const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
|
const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
|
||||||
const slEl = document.getElementById("order-sl");
|
const slEl = document.getElementById("order-sl");
|
||||||
@@ -1289,14 +1295,14 @@ function toggleSltpMode(){
|
|||||||
if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
|
if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
|
||||||
const pct = mode === "pct";
|
const pct = mode === "pct";
|
||||||
const fixed = mode === "fixed_rr";
|
const fixed = mode === "fixed_rr";
|
||||||
slEl.style.display = pct ? "none" : "";
|
setOmFieldVisible(slEl, !pct);
|
||||||
tpEl.style.display = (pct || fixed) ? "none" : "";
|
setOmFieldVisible(tpEl, !(pct || fixed));
|
||||||
if(fixedRrEl) fixedRrEl.style.display = fixed ? "" : "none";
|
setOmFieldVisible(fixedRrEl, fixed);
|
||||||
slEl.required = !pct;
|
slEl.required = !pct;
|
||||||
tpEl.required = !pct && !fixed;
|
tpEl.required = !pct && !fixed;
|
||||||
if(fixedRrEl) fixedRrEl.required = fixed;
|
if(fixedRrEl) fixedRrEl.required = fixed;
|
||||||
slPctEl.style.display = pct ? "" : "none";
|
setOmFieldVisible(slPctEl, pct);
|
||||||
tpPctEl.style.display = pct ? "" : "none";
|
setOmFieldVisible(tpPctEl, pct);
|
||||||
slPctEl.required = pct;
|
slPctEl.required = pct;
|
||||||
tpPctEl.required = pct;
|
tpPctEl.required = pct;
|
||||||
refreshOrderTpPreview();
|
refreshOrderTpPreview();
|
||||||
|
|||||||
@@ -97,50 +97,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include order_rule_tips_tpl %}
|
{% include order_rule_tips_tpl %}
|
||||||
<form id="add-order-form" action="/add_order" method="post" class="form-row" data-risk-percent="{{ risk_percent }}">
|
{% include 'order_monitor_open_form.html' %}
|
||||||
{% 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' %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 style="margin-bottom:8px">实时持仓</h2>
|
<h2 style="margin-bottom:8px">实时持仓</h2>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<script src="/static/autofill_guard.js?v=1"></script>
|
<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/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.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_page.css?v=12">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=108">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=109">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
<title>{{ pwa_app_name }}</title>
|
<title>{{ pwa_app_name }}</title>
|
||||||
@@ -168,7 +168,7 @@ window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
|||||||
</script>
|
</script>
|
||||||
<script src="/static/instance_settings_prefs.js?v=16"></script>
|
<script src="/static/instance_settings_prefs.js?v=16"></script>
|
||||||
<script src="/static/instance_live.js?v=6"></script>
|
<script src="/static/instance_live.js?v=6"></script>
|
||||||
<script src="/static/instance_embed.js?v=29"></script>
|
<script src="/static/instance_embed.js?v=30"></script>
|
||||||
<script src="/static/instance_mobile_nav.js?v=2"></script>
|
<script src="/static/instance_mobile_nav.js?v=2"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ pwa_app_name }}</title>
|
<title>{{ pwa_app_name }}</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=11">
|
<link rel="stylesheet" href="/static/instance_page.css?v=12">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=105">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=109">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -174,50 +174,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include order_rule_tips_tpl %}
|
{% include order_rule_tips_tpl %}
|
||||||
<form id="add-order-form" action="/add_order" method="post" class="form-row" data-risk-percent="{{ risk_percent }}">
|
{% include 'order_monitor_open_form.html' %}
|
||||||
{% 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' %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 style="margin-bottom:8px">实时持仓</h2>
|
<h2 style="margin-bottom:8px">实时持仓</h2>
|
||||||
@@ -1791,6 +1748,12 @@ if(fullMarginEl){
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sltpModeEl = document.getElementById("sltp-mode");
|
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(){
|
function toggleSltpMode(){
|
||||||
const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
|
const mode = sltpModeEl ? sltpModeEl.value : "fixed_rr";
|
||||||
const slEl = document.getElementById("order-sl");
|
const slEl = document.getElementById("order-sl");
|
||||||
@@ -1801,14 +1764,14 @@ function toggleSltpMode(){
|
|||||||
if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
|
if(!slEl || !tpEl || !slPctEl || !tpPctEl){ return; }
|
||||||
const pct = mode === "pct";
|
const pct = mode === "pct";
|
||||||
const fixed = mode === "fixed_rr";
|
const fixed = mode === "fixed_rr";
|
||||||
slEl.style.display = pct ? "none" : "";
|
setOmFieldVisible(slEl, !pct);
|
||||||
tpEl.style.display = (pct || fixed) ? "none" : "";
|
setOmFieldVisible(tpEl, !(pct || fixed));
|
||||||
if(fixedRrEl) fixedRrEl.style.display = fixed ? "" : "none";
|
setOmFieldVisible(fixedRrEl, fixed);
|
||||||
slEl.required = !pct;
|
slEl.required = !pct;
|
||||||
tpEl.required = !pct && !fixed;
|
tpEl.required = !pct && !fixed;
|
||||||
if(fixedRrEl) fixedRrEl.required = fixed;
|
if(fixedRrEl) fixedRrEl.required = fixed;
|
||||||
slPctEl.style.display = pct ? "" : "none";
|
setOmFieldVisible(slPctEl, pct);
|
||||||
tpPctEl.style.display = pct ? "" : "none";
|
setOmFieldVisible(tpPctEl, pct);
|
||||||
slPctEl.required = pct;
|
slPctEl.required = pct;
|
||||||
tpPctEl.required = pct;
|
tpPctEl.required = pct;
|
||||||
refreshOrderTpPreview();
|
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' %}
|
||||||
@@ -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) {
|
function renderPerpOptionsResult(data) {
|
||||||
const box = $("calc-po-result");
|
const box = $("calc-po-result");
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
@@ -614,6 +643,11 @@
|
|||||||
"<div><span>永续保证金</span><strong>" +
|
"<div><span>永续保证金</span><strong>" +
|
||||||
fmt(data.perp_margin_u, 2) +
|
fmt(data.perp_margin_u, 2) +
|
||||||
"U</strong></div>" +
|
"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>" +
|
"<div><span>开仓参考</span><strong>" +
|
||||||
esc(capitalHint) +
|
esc(capitalHint) +
|
||||||
"</strong></div>" +
|
"</strong></div>" +
|
||||||
@@ -663,7 +697,9 @@
|
|||||||
'">' +
|
'">' +
|
||||||
fmtU(b.portfolio_net_u) +
|
fmtU(b.portfolio_net_u) +
|
||||||
"</strong></div>" +
|
"</strong></div>" +
|
||||||
"</div></section></div>";
|
"</div></section>" +
|
||||||
|
renderSidewaysCase(data.case_sideways) +
|
||||||
|
"</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPerpOptionsPointsResult(data) {
|
function renderPerpOptionsPointsResult(data) {
|
||||||
@@ -743,6 +779,11 @@
|
|||||||
"<div><span>永续保证金</span><strong>" +
|
"<div><span>永续保证金</span><strong>" +
|
||||||
fmt(data.perp_margin_u, 2) +
|
fmt(data.perp_margin_u, 2) +
|
||||||
"U</strong></div>" +
|
"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>" +
|
"<div><span>开仓参考</span><strong>" +
|
||||||
esc(capitalHint) +
|
esc(capitalHint) +
|
||||||
"</strong></div>" +
|
"</strong></div>" +
|
||||||
@@ -774,6 +815,7 @@
|
|||||||
"</strong></div>" +
|
"</strong></div>" +
|
||||||
"</div></section>" +
|
"</div></section>" +
|
||||||
caseB +
|
caseB +
|
||||||
|
renderSidewaysCase(data.case_sideways) +
|
||||||
"</div>";
|
"</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -966,14 +966,14 @@
|
|||||||
<div class="calc-pane-split">
|
<div class="calc-pane-split">
|
||||||
<div class="calc-input-panel">
|
<div class="calc-input-panel">
|
||||||
<h2>永期对冲计算器</h2>
|
<h2>永期对冲计算器</h2>
|
||||||
<p class="calc-hint">永续固定 1 币;单币权利金 = 现价 / 期权杠杆;权利金按全亏;只扣永续开平手续费(各 0.05%).「推仓位」由波动反推期权数量;「推点数」按永续:期权比例反推达目标盈利所需波动.</p>
|
<p class="calc-hint">推点数模式:「永续币数/期权币数」按绝对仓位计算(填 2 与 4 → 永续 2 币 + 期权 4 币,不再归一成 1:2).单币权利金 = 现价 / 期权杠杆;权利金按全亏;只扣永续开平手续费(各 0.05%).「推仓位」仍固定永续 1 币、由波动反推期权数量.横盘最大亏损 = 权利金全亏 + 永续开平同价手续费.</p>
|
||||||
<form id="calc-po-form" class="calc-form">
|
<form id="calc-po-form" class="calc-form">
|
||||||
<div class="calc-form-grid">
|
<div class="calc-form-grid">
|
||||||
<label class="calc-field">
|
<label class="calc-field">
|
||||||
<span>测算模式</span>
|
<span>测算模式</span>
|
||||||
<select id="calc-po-calc-mode">
|
<select id="calc-po-calc-mode">
|
||||||
<option value="size" selected>由波动推期权仓位</option>
|
<option value="size" selected>由波动推期权仓位</option>
|
||||||
<option value="points">由比例推波动点数</option>
|
<option value="points">由币数推波动点数</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="calc-field">
|
<label class="calc-field">
|
||||||
@@ -1007,11 +1007,11 @@
|
|||||||
<input id="calc-po-move" type="number" min="0" step="any" value="50" />
|
<input id="calc-po-move" type="number" min="0" step="any" value="50" />
|
||||||
</label>
|
</label>
|
||||||
<label class="calc-field calc-po-points-only hidden">
|
<label class="calc-field calc-po-points-only hidden">
|
||||||
<span>永续比例</span>
|
<span>永续币数</span>
|
||||||
<input id="calc-po-ratio-perp" type="number" min="0.01" step="any" value="1" />
|
<input id="calc-po-ratio-perp" type="number" min="0.01" step="any" value="1" />
|
||||||
</label>
|
</label>
|
||||||
<label class="calc-field calc-po-points-only hidden">
|
<label class="calc-field calc-po-points-only hidden">
|
||||||
<span>期权比例</span>
|
<span>期权币数</span>
|
||||||
<input id="calc-po-ratio-opt" type="number" min="0.01" step="any" value="2" />
|
<input id="calc-po-ratio-opt" type="number" min="0.01" step="any" value="2" />
|
||||||
</label>
|
</label>
|
||||||
<label class="calc-field">
|
<label class="calc-field">
|
||||||
@@ -1751,7 +1751,7 @@
|
|||||||
<script src="/assets/chart_draw.js?v=20260720-option-day-1600"></script>
|
<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/chart.js?v=20260720-option-day-1600"></script>
|
||||||
<script src="/assets/plan.js?v=20260720-autofill"></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-abs-coins"></script>
|
||||||
<script src="/assets/compare.js?v=20260723-compare"></script>
|
<script src="/assets/compare.js?v=20260723-compare"></script>
|
||||||
<script src="/assets/trade_stats_calendar.js?v=3"></script>
|
<script src="/assets/trade_stats_calendar.js?v=3"></script>
|
||||||
<script src="/assets/archive.js?v=20260724-opt-archive"></script>
|
<script src="/assets/archive.js?v=20260724-opt-archive"></script>
|
||||||
|
|||||||
@@ -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["case_b"]["portfolio_net_u"], coins * 32.0 - 50.0, places=6)
|
||||||
self.assertAlmostEqual(data["perp_margin_u"], 180.0, places=6)
|
self.assertAlmostEqual(data["perp_margin_u"], 180.0, places=6)
|
||||||
self.assertTrue(data["capital_ok"])
|
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):
|
def test_pct_mode(self):
|
||||||
data, err = calc_perp_options_hedge(
|
data, err = calc_perp_options_hedge(
|
||||||
@@ -113,6 +118,40 @@ class HubPerpOptionsCalcTests(unittest.TestCase):
|
|||||||
# 组合净利=目标: (15+36)/(2-1) = 51
|
# 组合净利=目标: (15+36)/(2-1) = 51
|
||||||
self.assertAlmostEqual(data["case_b"]["move_points_portfolio"], 51.0, places=6)
|
self.assertAlmostEqual(data["case_b"]["move_points_portfolio"], 51.0, places=6)
|
||||||
self.assertAlmostEqual(data["case_b"]["portfolio_net_u"], 15.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_absolute_2_to_4(self):
|
||||||
|
# 2:4 按绝对币数,不再归一成 1:2
|
||||||
|
data, err = calc_perp_options_points(
|
||||||
|
base="ETH",
|
||||||
|
spot=1800,
|
||||||
|
capital_usdt=3000,
|
||||||
|
target_profit_u=15,
|
||||||
|
perp_leverage=10,
|
||||||
|
option_leverage=100,
|
||||||
|
ratio_perp=2,
|
||||||
|
ratio_opt=4,
|
||||||
|
ct_mult=0.01,
|
||||||
|
)
|
||||||
|
self.assertIsNone(err)
|
||||||
|
assert data is not None
|
||||||
|
self.assertEqual(data["perp_coins"], 2.0)
|
||||||
|
self.assertEqual(data["opt_coins"], 4.0)
|
||||||
|
self.assertEqual(data["premium_total_u"], 72.0)
|
||||||
|
self.assertAlmostEqual(data["perp_margin_u"], 360.0, places=6)
|
||||||
|
expect_a = (15 + 72 + 2 * 1800 * 2 * 0.0005) / (2 * 0.9995)
|
||||||
|
self.assertAlmostEqual(data["case_a"]["move_points"], expect_a, places=6)
|
||||||
|
self.assertAlmostEqual(data["case_a"]["net_u"], 15.0, places=5)
|
||||||
|
self.assertAlmostEqual(data["case_b"]["move_points_portfolio"], 43.5, places=6)
|
||||||
|
self.assertAlmostEqual(data["case_b"]["portfolio_net_u"], 15.0, places=6)
|
||||||
|
sw = data["case_sideways"]
|
||||||
|
self.assertAlmostEqual(sw["fee_u"], 3.6, places=6)
|
||||||
|
self.assertAlmostEqual(sw["max_loss_u"], 75.6, places=6)
|
||||||
|
|
||||||
def test_points_ratio_1_to_1_no_portfolio(self):
|
def test_points_ratio_1_to_1_no_portfolio(self):
|
||||||
data, err = calc_perp_options(
|
data, err = calc_perp_options(
|
||||||
|
|||||||
Reference in New Issue
Block a user