diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js
index db9700e..2f5d8a6 100644
--- a/lib/common/static/hedge_plan.js
+++ b/lib/common/static/hedge_plan.js
@@ -177,6 +177,7 @@
const acctLabel = d.account_label || "合约账户";
const tag = $("hp-perp-acct-tag");
if (tag) tag.textContent = acctLabel;
+ const amtPrec = d.amount_precision != null ? Number(d.amount_precision) : 4;
const q = $("hp-perp-quote");
if (q) {
q.innerHTML =
@@ -186,7 +187,7 @@
acctLabel +
"可用 " +
fmt(d.available_usdt, 2) +
- " U
标记 " +
+ " USDT
标记 " +
fmt(d.mark, 2) +
" · 最新 " +
fmt(d.last, 2) +
@@ -195,7 +196,15 @@
" · 买一 " +
fmt(d.bid, 2) +
" · 面值 " +
- fmt(d.contract_size, 4);
+ fmt(d.contract_size, 4) +
+ " · 张精度 " +
+ amtPrec +
+ " 位";
+ }
+ const contractsInput = $("hp-contracts");
+ if (contractsInput) {
+ const step = amtPrec <= 0 ? "1" : String(Math.pow(10, -amtPrec));
+ contractsInput.step = step;
}
const sz = $("hp-sizing-line");
if (sz) {
@@ -206,25 +215,73 @@
acctLabel +
"):保证金 " +
fmt(s.margin_capital, 2) +
- "U × " +
+ " USDT × " +
s.leverage +
"x → 名义 " +
fmt(s.notional_value, 2) +
- "U · 建议约 " +
- fmt(d.suggest_contracts, 4) +
- " 张";
+ " USDT · 建议 " +
+ fmt(d.suggest_contracts, amtPrec) +
+ " 合约张(已按交易所精度)";
} else {
sz.textContent = "非全仓或不具备保证金数据时仅手动填张数;永期开仓需全仓.";
}
}
const entry = $("hp-entry");
if (entry && d.entry_ref && !entry.value) entry.value = d.entry_ref;
- const contracts = $("hp-contracts");
- if (contracts && d.suggest_contracts != null && !contracts.value) {
- contracts.value = d.suggest_contracts;
+ if (contractsInput && d.suggest_contracts != null && !contractsInput.value) {
+ contractsInput.value = fmt(d.suggest_contracts, amtPrec);
}
const label = $("hp-opt-type-label");
if (label) label.textContent = d.suggested_opt_type === "C" ? "Call" : "Put";
+ updatePerpPnlHint();
+ }
+
+ function updatePerpPnlHint() {
+ const el = $("hp-perp-pnl-line");
+ if (!el) return;
+ const entry = Number(($("hp-entry") && $("hp-entry").value) || NaN);
+ const tp = Number(($("hp-tp") && $("hp-tp").value) || NaN);
+ const sl = Number(($("hp-sl") && $("hp-sl").value) || NaN);
+ const contracts = Number(($("hp-contracts") && $("hp-contracts").value) || NaN);
+ const cs = Number((state.market && state.market.contract_size) || 0.01);
+ const dir = (($("hp-direction") && $("hp-direction").value) || "long").toLowerCase();
+ if (!(entry > 0) || !(contracts > 0) || !(cs > 0)) {
+ el.textContent = "填写开仓价与张数后,输入止盈/止损可看永续盈亏金额(USDT)";
+ return;
+ }
+ function pnlAt(exitPx) {
+ const coins = contracts * cs;
+ if (dir === "short") return (entry - exitPx) * coins;
+ return (exitPx - entry) * coins;
+ }
+ const parts = [];
+ if (tp > 0) {
+ const p = pnlAt(tp);
+ parts.push(
+ "止盈预期 = 0 ? "hp-pnl-pos" : "hp-pnl-neg") +
+ "\">" +
+ (p >= 0 ? "+" : "") +
+ fmt(p, 2) +
+ " USDT"
+ );
+ } else {
+ parts.push("止盈预期 —");
+ }
+ if (sl > 0) {
+ const p = pnlAt(sl);
+ parts.push(
+ "止损预期 = 0 ? "hp-pnl-pos" : "hp-pnl-neg") +
+ "\">" +
+ (p >= 0 ? "+" : "") +
+ fmt(p, 2) +
+ " USDT"
+ );
+ } else {
+ parts.push("止损预期 —");
+ }
+ el.innerHTML = parts.join(" · ");
}
function fillExpSelect(sel, chain) {
@@ -632,6 +689,10 @@
});
});
}
+ ["hp-entry", "hp-tp", "hp-sl", "hp-contracts"].forEach(function (id) {
+ const el = $(id);
+ if (el) el.addEventListener("input", updatePerpPnlHint);
+ });
if ($("hp-refresh"))
$("hp-refresh").addEventListener("click", function () {
void refreshAll();
diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css
index 76acfb7..41b61ba 100644
--- a/lib/common/static/instance_theme.css
+++ b/lib/common/static/instance_theme.css
@@ -3212,6 +3212,28 @@ html[data-theme="light"] .opt-be-dist-down {
font-weight: 500;
margin-left: 4px;
}
+.hedge-plan-page-wrap .hp-unit {
+ font-size: 0.66rem;
+ color: #8892b0;
+ font-weight: 500;
+ margin-right: 2px;
+}
+.hedge-plan-page-wrap .hp-unit-hint {
+ font-size: 0.68rem;
+ margin: 2px 0 6px;
+ line-height: 1.4;
+}
+.hedge-plan-page-wrap #hp-perp-pnl-line {
+ font-size: 0.74rem;
+ margin: 4px 0 6px;
+ line-height: 1.45;
+}
+.hedge-plan-page-wrap .hp-pnl-pos {
+ color: #7ee787;
+}
+.hedge-plan-page-wrap .hp-pnl-neg {
+ color: #ff8a8a;
+}
.hedge-plan-page-wrap .form-row label,
.hedge-plan-page-wrap .form-row select,
.hedge-plan-page-wrap .form-row input,
diff --git a/lib/hedge_plan/hedge_plan_calc_lib.py b/lib/hedge_plan/hedge_plan_calc_lib.py
index 1507482..d200785 100644
--- a/lib/hedge_plan/hedge_plan_calc_lib.py
+++ b/lib/hedge_plan/hedge_plan_calc_lib.py
@@ -69,6 +69,23 @@ def suggest_contracts_from_notional(
return float(notional) / (float(entry) * float(contract_size))
+def floor_contracts_to_precision(contracts: float, decimals: int) -> float:
+ """按交易所张数精度向下取整,避免建议张数超过可用保证金."""
+ import math
+
+ raw = float(contracts or 0.0)
+ if raw <= 0:
+ return 0.0
+ try:
+ d = int(decimals)
+ except (TypeError, ValueError):
+ d = 0
+ if d <= 0:
+ return float(math.floor(raw + 1e-12))
+ scale = 10**d
+ return math.floor(raw * scale + 1e-12) / scale
+
+
def build_perp_options_preview(
*,
direction: str,
diff --git a/lib/hedge_plan/hedge_plan_register.py b/lib/hedge_plan/hedge_plan_register.py
index 548e3c6..f49cad9 100644
--- a/lib/hedge_plan/hedge_plan_register.py
+++ b/lib/hedge_plan/hedge_plan_register.py
@@ -10,10 +10,12 @@ from jinja2 import ChoiceLoader, FileSystemLoader
from lib.hedge_plan.hedge_plan_calc_lib import (
build_options_options_preview,
build_perp_options_preview,
+ floor_contracts_to_precision,
gate_status,
option_premium_total,
suggest_contracts_from_notional,
)
+from lib.hub.hub_calculator_market_lib import amount_decimals_from_exchange
from lib.trade.position_sizing_lib import (
compute_full_margin_sizing,
load_position_sizing_mode,
@@ -305,6 +307,11 @@ def _fetch_perp_market(cfg: dict[str, Any], base: str) -> tuple[dict[str, Any],
entry = float(mark or last or 0)
sizing = None
suggest_contracts = None
+ amount_precision = 4
+ try:
+ amount_precision = int(amount_decimals_from_exchange(ex, sym))
+ except Exception:
+ amount_precision = 4
if available is not None and entry > 0:
sizing, _serr = compute_full_margin_sizing(
symbol=sym,
@@ -316,11 +323,20 @@ def _fetch_perp_market(cfg: dict[str, Any], base: str) -> tuple[dict[str, Any],
funds_decimals=int(cfg.get("funds_decimals") or 2),
)
if sizing:
- suggest_contracts = suggest_contracts_from_notional(
+ raw_contracts = suggest_contracts_from_notional(
notional=float(sizing["notional_value"]),
entry=entry,
contract_size=cs,
)
+ # 优先走交易所 amount_to_precision;失败则按精度位数向下取整
+ suggest_contracts = None
+ try:
+ precise = float(ex.amount_to_precision(sym, raw_contracts))
+ if precise > raw_contracts + 1e-12:
+ precise = floor_contracts_to_precision(raw_contracts, amount_precision)
+ suggest_contracts = precise
+ except Exception:
+ suggest_contracts = floor_contracts_to_precision(raw_contracts, amount_precision)
return {
"exchange_symbol": sym,
@@ -331,7 +347,11 @@ def _fetch_perp_market(cfg: dict[str, Any], base: str) -> tuple[dict[str, Any],
"contract_size": cs,
"available_usdt": available,
"full_margin_sizing": sizing,
- "suggest_contracts": round(suggest_contracts, 6) if suggest_contracts is not None else None,
+ "suggest_contracts": suggest_contracts,
+ "amount_precision": amount_precision,
+ "unit_quote": "USDT",
+ "unit_contracts": "合约张",
+ "unit_note": "价格单位 USDT;张数=交易所永续合约张(与下单精度一致);名义≈张数×面值×价格",
"entry_ref": entry or None,
}, None
diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html
index 3bab432..732d1b7 100644
--- a/lib/hedge_plan/templates/hedge_plan_panel.html
+++ b/lib/hedge_plan/templates/hedge_plan_panel.html
@@ -39,12 +39,14 @@
单位说明:价格=USDT · 张数=交易所永续合约张(精度与 OKX 下单一致) · 盈亏=USDT
单位说明:权利金结算币=USDC · 张数=期权张(整张) · 卖一/买一=价格/张
@@ -192,4 +195,4 @@ - + diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index cf9921b..60fcc9a 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -7,7 +7,7 @@ - +