Fold chain/account/PnL hints into rules; hide dust sell-coin.

Move the three noisy help lines into 规则说明, and show 手动卖回币 only when trading-coin balance meets the exchange min size.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 19:31:14 +08:00
parent 1d71414c56
commit ab7a2309ad
3 changed files with 110 additions and 18 deletions
+44 -13
View File
@@ -1097,7 +1097,7 @@
"</div></div>" + "</div></div>" +
"</div>" + "</div>" +
'<div class="hp-live-combo">' + '<div class="hp-live-combo">' +
'<span class="muted">组合净盈亏(扣开平手续费·永续盘口均价)</span>' + '<span class="muted" title="扣开平手续费·永续盘口均价(详见左侧规则说明)">组合净盈亏</span>' +
'<strong class="' + '<strong class="' +
comboCls + comboCls +
'">' + '">' +
@@ -1577,18 +1577,55 @@
function setSellCoinMsg(text, kind) { function setSellCoinMsg(text, kind) {
const el = $("hp-sell-coin-msg"); const el = $("hp-sell-coin-msg");
if (!el) return; if (!el) return;
const show = !!(text || "").trim();
el.hidden = !show;
el.textContent = text || ""; el.textContent = text || "";
el.classList.toggle("is-err", kind === "err"); el.classList.toggle("is-err", kind === "err");
el.classList.toggle("is-ok", kind === "ok"); el.classList.toggle("is-ok", kind === "ok");
el.classList.toggle("muted", !kind); el.classList.toggle("muted", !kind);
} }
function setSellCoinVisible(visible, meta) {
const btn = $("hp-sell-coin-btn");
if (!btn) return;
const on = !!visible;
btn.hidden = !on;
btn.disabled = !on;
if (on && meta) {
const uly = String(meta.underlying || state.underlying || "ETH").toUpperCase();
const avail = meta.available != null ? Number(meta.available) : null;
const minSz = meta.min_sz != null ? Number(meta.min_sz) : null;
let tip = "市价卖出交易账户全部可用 " + uly + " 换回 USDT";
if (avail != null && !Number.isNaN(avail)) tip += " · 可用 " + avail;
if (minSz != null && !Number.isNaN(minSz)) tip += " · 最小 " + minSz;
btn.title = tip;
}
if (!on) setSellCoinMsg("", null);
}
async function refreshSellCoinVisibility() {
const uly = String(state.underlying || "ETH").toUpperCase();
try {
const d = await apiJson(
"/api/hedge-plan/sell-coin-status?underlying=" + encodeURIComponent(uly)
);
if (!d || !d.ok) {
setSellCoinVisible(false);
return;
}
setSellCoinVisible(!!d.sellable, d);
} catch (_) {
setSellCoinVisible(false);
}
}
async function submitSellCoin() { async function submitSellCoin() {
const uly = String(state.underlying || "ETH").toUpperCase(); const uly = String(state.underlying || "ETH").toUpperCase();
const btn = $("hp-sell-coin-btn");
if (!btn || btn.hidden) return;
if (!window.confirm("确认市价卖出交易账户全部可用 " + uly + " 换回 USDT?")) return; if (!window.confirm("确认市价卖出交易账户全部可用 " + uly + " 换回 USDT?")) return;
setSellCoinMsg("卖回中…", null); setSellCoinMsg("卖回中…", null);
const btn = $("hp-sell-coin-btn"); btn.disabled = true;
if (btn) btn.disabled = true;
try { try {
const d = await apiJson("/api/hedge-plan/sell-coin", { const d = await apiJson("/api/hedge-plan/sell-coin", {
method: "POST", method: "POST",
@@ -1597,6 +1634,7 @@
}); });
if (!d.ok) { if (!d.ok) {
setSellCoinMsg(d.msg || "卖回失败", "err"); setSellCoinMsg(d.msg || "卖回失败", "err");
await refreshSellCoinVisibility();
return; return;
} }
const sold = const sold =
@@ -1606,10 +1644,10 @@
setSellCoinMsg(sold, "ok"); setSellCoinMsg(sold, "ok");
await loadChain(); await loadChain();
void loadLivePositions(); void loadLivePositions();
await refreshSellCoinVisibility();
} catch (e) { } catch (e) {
setSellCoinMsg(e.message || "卖回失败", "err"); setSellCoinMsg(e.message || "卖回失败", "err");
} finally { await refreshSellCoinVisibility();
if (btn) btn.disabled = false;
} }
} }
@@ -2059,6 +2097,7 @@
$("hp-profit-rr").value = "2"; $("hp-profit-rr").value = "2";
} }
} }
void refreshSellCoinVisibility();
} }
function currentExp(selectId) { function currentExp(selectId) {
@@ -2186,14 +2225,6 @@
} }
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
if (isOptionPrimary() && !state.poStrikeExpandAll && state.moneyFilter === "all") {
const hintTr = document.createElement("tr");
hintTr.innerHTML =
'<td colspan="' +
cols +
'" class="muted opt-strike-hint">默认显示约 5 档(平值 + 实值2 + 虚值2) · 勾选「展开全部」查看该到期更多行权价</td>';
tbody.appendChild(hintTr);
}
tbody.querySelectorAll(".hp-pick").forEach(function (btn) { tbody.querySelectorAll(".hp-pick").forEach(function (btn) {
btn.addEventListener("click", function () { btn.addEventListener("click", function () {
const inst = btn.getAttribute("data-inst"); const inst = btn.getAttribute("data-inst");
+59 -1
View File
@@ -1281,10 +1281,47 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
pass pass
return jsonify(result) return jsonify(result)
@app.route("/api/hedge-plan/sell-coin-status")
@lr
def api_hedge_sell_coin_status():
"""是否显示「手动卖回币」:交易户有币且 ≥ 现货最小卖出量(粉尘隐藏)."""
if not _hedge_enabled():
return jsonify({"ok": False, "sellable": False, "msg": "对冲模块未启用"})
ex = cfg.get("exchange_options") or cfg.get("exchange")
if ex is None:
return jsonify({"ok": False, "sellable": False, "msg": "交易所未配置"})
underlying = str(
request.args.get("underlying") or cfg.get("default_underly") or "ETH"
).strip().upper()
if underlying not in ("ETH", "BTC"):
return jsonify({"ok": False, "sellable": False, "msg": "标的仅支持 ETH/BTC"})
from lib.options.options_spot_bridge_lib import (
fetch_trading_coin_available,
is_sellable_spot_coin,
spot_min_sell_sz,
)
avail = fetch_trading_coin_available(ex, underlying)
try:
avail_f = float(avail) if avail is not None else 0.0
except (TypeError, ValueError):
avail_f = 0.0
min_sz = float(spot_min_sell_sz(ex, underlying))
sellable = is_sellable_spot_coin(ex, underlying, avail_f)
return jsonify(
{
"ok": True,
"underlying": underlying,
"available": avail_f,
"min_sz": min_sz,
"sellable": bool(sellable),
}
)
@app.route("/api/hedge-plan/sell-coin", methods=["POST"]) @app.route("/api/hedge-plan/sell-coin", methods=["POST"])
@lr @lr
def api_hedge_sell_coin(): def api_hedge_sell_coin():
"""手动卖回币:市价卖出交易账户全部可用标的币换 USDT.""" """手动卖回币:市价卖出交易账户全部可用标的币换 USDT(粉尘拒绝)."""
if not _hedge_enabled(): if not _hedge_enabled():
return jsonify({"ok": False, "msg": "对冲模块未启用"}), 400 return jsonify({"ok": False, "msg": "对冲模块未启用"}), 400
ex = cfg.get("exchange_options") or cfg.get("exchange") ex = cfg.get("exchange_options") or cfg.get("exchange")
@@ -1294,6 +1331,27 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
underlying = str(data.get("underlying") or cfg.get("default_underly") or "ETH").strip().upper() underlying = str(data.get("underlying") or cfg.get("default_underly") or "ETH").strip().upper()
if underlying not in ("ETH", "BTC"): if underlying not in ("ETH", "BTC"):
return jsonify({"ok": False, "msg": "标的仅支持 ETH/BTC"}), 400 return jsonify({"ok": False, "msg": "标的仅支持 ETH/BTC"}), 400
from lib.options.options_spot_bridge_lib import (
fetch_trading_coin_available,
is_sellable_spot_coin,
spot_min_sell_sz,
)
avail = fetch_trading_coin_available(ex, underlying)
try:
avail_f = float(avail) if avail is not None else 0.0
except (TypeError, ValueError):
avail_f = 0.0
if not is_sellable_spot_coin(ex, underlying, avail_f):
min_sz = spot_min_sell_sz(ex, underlying)
return jsonify(
{
"ok": False,
"msg": f"无足够 {underlying} 可卖(可用 {avail_f:g} · 最小 {min_sz:g})",
"available": avail_f,
"min_sz": min_sz,
}
), 400
conn = cfg["get_db"]() conn = cfg["get_db"]()
try: try:
from lib.options.options_db import init_options_tables from lib.options.options_db import init_options_tables
@@ -50,6 +50,9 @@
<p><strong>手动下单</strong>:开仓按<strong>卖一限价</strong>(非交易所市价)。先开期权(按张数买币并+5%滑点),再开永续。永续杠杆取 env:BTC=<code>BTC_LEVERAGE</code>/ETH=<code>ALT_LEVERAGE</code>。</p> <p><strong>手动下单</strong>:开仓按<strong>卖一限价</strong>(非交易所市价)。先开期权(按张数买币并+5%滑点),再开永续。永续杠杆取 env:BTC=<code>BTC_LEVERAGE</code>/ETH=<code>ALT_LEVERAGE</code>。</p>
<p><strong>出场</strong>:盈利金额(扣开平双边手续费后净利)或目标位点数二选一。平仓先期权(须买一流动性)再永续,再把币全部卖回 USDT。一账户同时仅一笔对冲仓。</p> <p><strong>出场</strong>:盈利金额(扣开平双边手续费后净利)或目标位点数二选一。平仓先期权(须买一流动性)再永续,再把币全部卖回 USDT。一账户同时仅一笔对冲仓。</p>
<p><strong>开仓校验</strong>:已选合约 · 张数≥1 · 出场条件已填 · 门禁「开仓:可」· 账户有足够交易 USDT。</p> <p><strong>开仓校验</strong>:已选合约 · 张数≥1 · 出场条件已填 · 门禁「开仓:可」· 账户有足够交易 USDT。</p>
<p><strong>期权链</strong>:默认显示约 <strong>5</strong> 档(平值 + 实值2 + 虚值2);勾选「展开全部」查看该到期更多行权价。</p>
<p><strong>账户</strong>:期权币本位 · 交易/资金计 USDT · 永续全仓 / 期权逐仓(余额见下方划转)。</p>
<p><strong>组合净盈亏</strong>:扣开平双边手续费;永续按平仓方向盘口均价估值。</p>
<p class="muted" id="hp-po-pair-hint">买 Call + 永续空 · 点「选择」弹窗下单。</p> <p class="muted" id="hp-po-pair-hint">买 Call + 永续空 · 点「选择」弹窗下单。</p>
</div> </div>
</details> </details>
@@ -100,7 +103,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div id="hp-opt-bal-line" class="muted hp-po-meta hp-opt-bal-line"></div> <div id="hp-opt-bal-line" class="muted hp-po-meta hp-opt-bal-line" hidden aria-hidden="true"></div>
<div class="hp-oo-transfer hp-oo-transfer--compact" id="hp-po-transfer"> <div class="hp-oo-transfer hp-oo-transfer--compact" id="hp-po-transfer">
<div class="hp-oo-transfer-bals muted"> <div class="hp-oo-transfer-bals muted">
<span>资金 <strong id="hp-po-funding-usdt">—</strong></span> <span>资金 <strong id="hp-po-funding-usdt">—</strong></span>
@@ -234,11 +237,11 @@
<div class="options-pos-head"> <div class="options-pos-head">
<h2>持仓</h2> <h2>持仓</h2>
<div class="options-pos-head-actions"> <div class="options-pos-head-actions">
<button type="button" class="btn-secondary" id="hp-sell-coin-btn" title="市价卖出交易账户全部可用标的币(当前选中 ETH/BTC)换回 USDT">手动卖回币</button> <button type="button" class="btn-secondary" id="hp-sell-coin-btn" hidden title="市价卖出交易账户全部可用标的币(当前选中 ETH/BTC)换回 USDT">手动卖回币</button>
<button type="button" class="btn-secondary" id="hp-pos-refresh" title="刷新持仓与历史">刷新</button> <button type="button" class="btn-secondary" id="hp-pos-refresh" title="刷新持仓与历史">刷新</button>
</div> </div>
</div> </div>
<div class="muted" id="hp-sell-coin-msg" style="font-size:.72rem;margin:0 0 6px"></div> <div class="muted" id="hp-sell-coin-msg" hidden style="font-size:.72rem;margin:0 0 6px"></div>
<div class="options-pos-tabs" role="tablist" aria-label="永期持仓面板"> <div class="options-pos-tabs" role="tablist" aria-label="永期持仓面板">
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab active" data-hp-pos-tab="live" role="tab" aria-selected="true">当前持仓</button> <button type="button" class="btn-secondary opt-pos-tab hp-pos-tab active" data-hp-pos-tab="live" role="tab" aria-selected="true">当前持仓</button>
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab" data-hp-pos-tab="plans" role="tab" aria-selected="false">对冲计划</button> <button type="button" class="btn-secondary opt-pos-tab hp-pos-tab" data-hp-pos-tab="plans" role="tab" aria-selected="false">对冲计划</button>
@@ -494,4 +497,4 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/static/hedge_plan.js?v=61"></script> <script src="/static/hedge_plan.js?v=62"></script>