Auto-fill 期期 sheets from trading balance with same-sheets default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-19 00:56:35 +08:00
parent f21e4d1166
commit b3548240cc
7 changed files with 374 additions and 5 deletions
+150 -1
View File
@@ -28,6 +28,10 @@
legA: null,
legB: null,
market: null,
ooSheetsMode: "same_sheets",
tradingUsdc: null,
tradeBudgetUsdc: null,
budgetBuffer: 0.95,
};
function $(id) {
@@ -176,6 +180,16 @@
const label = (chain && chain.account_label) || acct.label || "期权账户";
const tag = $("hp-opt-acct-tag");
if (tag) tag.textContent = label;
if (acct.trading_usdc != null && acct.trading_usdc !== "") {
state.tradingUsdc = Number(acct.trading_usdc);
}
if (chain && chain.trade_budget_usdc != null && chain.trade_budget_usdc !== "") {
state.tradeBudgetUsdc = Number(chain.trade_budget_usdc);
}
if (chain && chain.budget_buffer != null && chain.budget_buffer !== "") {
const buf = Number(chain.budget_buffer);
if (!Number.isNaN(buf) && buf > 0) state.budgetBuffer = buf;
}
const line =
label +
" · 交易 USDC " +
@@ -186,6 +200,132 @@
if (el) el.textContent = line;
const oo = $("hp-oo-bal-line");
if (oo) oo.textContent = line;
autoFillOoSheets();
}
function resolveOoBudget() {
const buf = state.budgetBuffer > 0 ? state.budgetBuffer : 0.95;
const trading = state.tradingUsdc;
const cap = state.tradeBudgetUsdc;
let tradingCap = null;
let tradeCap = null;
if (trading != null && !Number.isNaN(Number(trading))) {
tradingCap = Math.max(0, Number(trading) * buf);
}
if (cap != null && !Number.isNaN(Number(cap))) {
tradeCap = Math.max(0, Number(cap));
}
if (tradingCap == null && tradeCap == null) {
return { ok: false, budget: 0, tradingCap: null, tradeCap: null, buf: buf, msg: "缺少交易户余额与单笔预算" };
}
let budget = 0;
if (tradingCap == null) budget = tradeCap;
else if (tradeCap == null) budget = tradingCap;
else budget = Math.min(tradingCap, tradeCap);
budget = Math.floor(budget * 1e6 + 1e-12) / 1e6;
return {
ok: budget > 0,
budget: budget,
tradingCap: tradingCap,
tradeCap: tradeCap,
buf: buf,
msg: budget > 0 ? "" : "可用预算为 0",
};
}
function unitCost(c) {
if (!c) return 0;
const ask = Number(c.ask || 0);
if (!(ask > 0)) return 0;
return ask * Number(c.ct_mult || 0.01);
}
function capByDepth(n, askSz) {
let out = Math.max(0, Math.floor(Number(n) || 0));
if (askSz == null || askSz === "") return out;
const d = Number(askSz);
if (Number.isNaN(d)) return out;
if (d <= 0) return 0;
return Math.min(out, Math.floor(d + 1e-12));
}
function suggestOoSheetsLocal(budget, mode) {
const costA = unitCost(state.legA);
const costB = unitCost(state.legB);
if (!(budget > 0)) {
return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "可用预算为 0" };
}
if (!(costA > 0) || !(costB > 0)) {
return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "缺少有效卖一价,无法建议张数" };
}
let nA = 0;
let nB = 0;
if (mode === "split_budget") {
const half = budget / 2;
nA = Math.floor(half / costA + 1e-12);
nB = Math.floor(half / costB + 1e-12);
} else {
const pair = costA + costB;
const n = pair > 0 ? Math.floor(budget / pair + 1e-12) : 0;
nA = n;
nB = n;
}
nA = capByDepth(nA, state.legA && state.legA.ask_sz);
nB = capByDepth(nB, state.legB && state.legB.ask_sz);
if (mode !== "split_budget") {
const n = Math.min(nA, nB);
nA = n;
nB = n;
}
const premium = costA * nA + costB * nB;
const ok = nA >= 1 && nB >= 1;
return {
sheetsA: nA,
sheetsB: nB,
premium: premium,
ok: ok,
msg: ok ? "" : "预算不够开 1+1(或卖一深度不足)",
};
}
function syncOoSizeModeUI() {
document.querySelectorAll(".hp-oo-size-mode").forEach(function (b) {
const on = b.getAttribute("data-oo-size") === state.ooSheetsMode;
b.classList.toggle("active", on);
});
}
function updateOoBudgetLine(extra) {
const line = $("hp-oo-budget-line");
if (!line) return;
const b = resolveOoBudget();
const modeLabel = state.ooSheetsMode === "split_budget" ? "均分预算" : "同张数";
const parts = [
"预算 min(交易×" + fmt(b.buf, 2) + ", 单笔) ≈ " + fmt(b.budget, 2) + " USDC",
"模式:" + modeLabel,
];
if (b.tradingCap != null) parts.push("交易×缓冲 " + fmt(b.tradingCap, 2));
if (b.tradeCap != null) parts.push("单笔上限 " + fmt(b.tradeCap, 2));
if (extra && extra.msg) parts.push(extra.msg);
else if (b.msg) parts.push(b.msg);
line.textContent = parts.join(" · ");
line.classList.toggle("hp-oo-budget-warn", !!(extra && extra.msg) || !b.ok);
}
function autoFillOoSheets() {
syncOoSizeModeUI();
if (!state.legA || !state.legB) {
updateOoBudgetLine(null);
return;
}
const b = resolveOoBudget();
const sug = suggestOoSheetsLocal(b.budget, state.ooSheetsMode);
const a = $("hp-oo-sheets-a");
const bb = $("hp-oo-sheets-b");
if (a && !a.disabled) a.value = String(sug.sheetsA);
if (bb && !bb.disabled) bb.value = String(sug.sheetsB);
updateOoBudgetLine(sug.ok ? null : sug);
updateOoPremiumLine();
}
async function loadGates() {
@@ -562,7 +702,7 @@
}
fill("腿A", state.legA, "hp-oo-leg-a-info", "hp-oo-sheets-a");
fill("腿B", state.legB, "hp-oo-leg-b-info", "hp-oo-sheets-b");
updateOoPremiumLine();
autoFillOoSheets();
}
function ooSheets(id) {
@@ -806,6 +946,13 @@
const el = $(id);
if (el) el.addEventListener("input", updateOoPremiumLine);
});
document.querySelectorAll(".hp-oo-size-mode").forEach(function (b) {
b.addEventListener("click", function () {
state.ooSheetsMode = b.getAttribute("data-oo-size") || "same_sheets";
syncOoSizeModeUI();
autoFillOoSheets();
});
});
if ($("hp-preview-btn"))
$("hp-preview-btn").addEventListener("click", function () {
state.mode = "perp_options";
@@ -1262,6 +1409,8 @@
syncTabUI();
syncUnderlyingUI();
syncMoneyUI();
syncOoSizeModeUI();
updateOoBudgetLine(null);
bind();
void refreshAll();
})();
+15
View File
@@ -3258,6 +3258,21 @@ html[data-theme="light"] .opt-be-dist-down {
flex-direction: column;
gap: 8px;
}
.hedge-plan-page-wrap .hp-oo-size-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin: 6px 0 2px;
}
.hedge-plan-page-wrap .hp-oo-size-mode.active {
border-color: var(--accent, #00d4ff);
color: var(--text, #fff);
background: rgba(0, 212, 255, 0.12);
}
.hedge-plan-page-wrap #hp-oo-budget-line.hp-oo-budget-warn {
color: #ff8a8a;
}
.hedge-plan-page-wrap .hp-oo-leg-row {
display: flex;
flex-wrap: wrap;