diff --git a/docs/binance-orderbook-trade-development.md b/docs/binance-orderbook-trade-development.md index ff60690..cbe3d34 100644 --- a/docs/binance-orderbook-trade-development.md +++ b/docs/binance-orderbook-trade-development.md @@ -155,6 +155,8 @@ Do not auto-confirm destructive Binance dialogs. The script may open Binance's n Ladder replacement must stay scoped and direction-aware. Automatic replacement may cancel only visible basic open-order rows for the current symbol and the same plan direction (`开多`, `开空`, `平多`, or `平空`). It must not use current-symbol cancel-all for ladder replacement, must not touch conditional/protection orders, and must retry the ladder plan only after the replacement path is validated by current DOM rows. +User-facing failures must preserve the observed reason instead of collapsing multiple states into one generic message. In particular, an unread openable quantity, a confirmed zero available balance, and a Binance-calculated openable quantity of zero are separate outcomes. Use the panel term `价格精度` consistently instead of exposing the internal `缩放值` name. Cancellation feedback should describe the user-visible action and result, not implementation details such as row-by-row processing, observer roots, context loss, or temporary chart-state manipulation. Combined `A or B` failures are allowed only when the code cannot distinguish the causes; when each branch is already known, report that branch directly. + Continuous ladder trading is available only for close actions through `Option/Alt + click`; an ordinary click remains one round. A round is the complete existing ladder-close workflow, including any scoped same-direction replacement and cleanup. After a completed round, the runner must observe the same symbol, close mode, current precision, and native close button as ready before starting a full one-second cooldown. It must validate readiness again after the cooldown; losing readiness restarts the wait and a new full cooldown. Every new round must rebuild its plan from the latest panel profile and live trading context. A failed, stopped, or interrupted round ends the continuous session. Continuous-session feedback stays in the shared ladder status row and uses `连续阶梯平多` / `连续阶梯平空` as the stable action name. The action, phase, and counters are separated with ` · ` instead of concatenating `连续` after the ordinary ladder label. `2/3 轮` means two rounds completed out of three started, `本轮 1/3 笔` reports the active or latest partial plan, and `累计 7 笔` reports all confirmed submissions across the session. Confirmed cancellations are appended only when greater than zero. The active round must combine its live progress with the completed-round aggregate; ordinary single-round status text must never overwrite the continuous-session identity. Round outcomes must expose a detached progress snapshot so a terminal continuous summary cannot be overwritten by the latest single-round message. diff --git a/e2e/binance-orderbook/helpers/live-performance-probe.js b/e2e/binance-orderbook/helpers/live-performance-probe.js index 0bdef78..eb76974 100644 --- a/e2e/binance-orderbook/helpers/live-performance-probe.js +++ b/e2e/binance-orderbook/helpers/live-performance-probe.js @@ -119,7 +119,7 @@ export function waitForBinanceLivePerformanceCompletion(options = {}) { if (kind === 'no-orders') { if (hasEvent(snapshot, 'dialog-visible') || hasEvent(snapshot, 'dialog-action')) return false; - return state.cancelButtonText === '无挂单' || /当前币无挂单/.test(state.statusText); + return state.cancelButtonText === '无挂单' || /当前交易对无挂单/.test(state.statusText); } const dialogActions = snapshot.events.filter((event) => event.kind === 'dialog-action'); @@ -128,7 +128,7 @@ export function waitForBinanceLivePerformanceCompletion(options = {}) { if (dialogActions[0].detail?.primary !== expectsPrimary) return false; return kind === 'dialog-cancel' ? state.statusText === '撤单已取消' - : /撤单流程结束.*已恢复筛选状态/.test(state.statusText); + : state.statusText === '撤单已完成'; }; return new Promise((resolve, reject) => { diff --git a/e2e/binance-orderbook/specs/cancel-covering-matrix.pw.js b/e2e/binance-orderbook/specs/cancel-covering-matrix.pw.js index 47cb7ff..e4febd1 100644 --- a/e2e/binance-orderbook/specs/cancel-covering-matrix.pw.js +++ b/e2e/binance-orderbook/specs/cancel-covering-matrix.pw.js @@ -57,7 +57,7 @@ for (const entry of CANCEL_COVERING_SCENARIOS) { }).click(); await expect(page.getByText( vector.dialogOutcome === 'confirm' - ? '撤单流程结束,已恢复筛选状态' + ? '撤单已完成' : '撤单已取消', )).toBeVisible(); } diff --git a/e2e/binance-orderbook/specs/cancel-current-symbol.pw.js b/e2e/binance-orderbook/specs/cancel-current-symbol.pw.js index 873be15..82234e9 100644 --- a/e2e/binance-orderbook/specs/cancel-current-symbol.pw.js +++ b/e2e/binance-orderbook/specs/cancel-current-symbol.pw.js @@ -199,7 +199,7 @@ test('confirming with mixed-symbol orders clears only the current symbol', async await page.getByRole('button', { name: '撤单' }).click(); await expect(page.getByRole('dialog')).toBeVisible(); await page.getByRole('button', { name: '确认' }).click(); - await expect(page.getByText('撤单流程结束,已恢复筛选状态')).toBeVisible(); + await expect(page.getByText('撤单已完成')).toBeVisible(); const state = await readFixtureState(page); expect(state.orders).toEqual(otherSymbolOrders(scenario)); @@ -219,7 +219,7 @@ test('an originally enabled symbol filter remains enabled after confirmation', a await page.getByRole('button', { name: '撤单' }).click(); await expect(page.getByRole('dialog')).toBeVisible(); await page.getByRole('button', { name: '确认' }).click(); - await expect(page.getByText('撤单流程结束,已恢复筛选状态')).toBeVisible(); + await expect(page.getByText('撤单已完成')).toBeVisible(); const state = await readFixtureState(page); expect(state.orders).toEqual(otherSymbolOrders(scenario)); @@ -382,8 +382,8 @@ test('a delayed confirmation keeps visible progress and clears only the current await page.getByRole('button', { name: '撤单' }).click(); await page.getByRole('button', { name: '确认' }).click(); - await expect(page.getByText('已确认撤单,等待当前币挂单清空')).toBeVisible(); - await expect(page.getByText('撤单流程结束,已恢复筛选状态')).toBeVisible(); + await expect(page.getByText('撤单已确认,等待挂单清空')).toBeVisible(); + await expect(page.getByText('撤单已完成')).toBeVisible(); const state = await readFixtureState(page); expect(state.orders).toEqual(otherSymbolOrders(scenario)); @@ -425,7 +425,7 @@ test('a confirmed dialog that does not clear current orders reports incomplete c await page.getByRole('button', { name: '撤单' }).click(); await page.getByRole('button', { name: '确认' }).click(); - await expect(page.getByText('当前币挂单仍存在,撤单流程未完成')).toBeVisible({ + await expect(page.getByText('当前交易对挂单仍存在,撤单未完成')).toBeVisible({ timeout: 10_000, }); diff --git a/e2e/binance-orderbook/specs/live-performance-probe.pw.js b/e2e/binance-orderbook/specs/live-performance-probe.pw.js index 7247313..d14408d 100644 --- a/e2e/binance-orderbook/specs/live-performance-probe.pw.js +++ b/e2e/binance-orderbook/specs/live-performance-probe.pw.js @@ -33,7 +33,7 @@ test('live probe captures a no-order run and destroys every listener', async ({ const firstFeedback = snapshot.events.find((event) => event.kind === 'first-feedback'); expect(snapshot.finishedAtMonotonicMs - snapshot.startedAtMonotonicMs - firstFeedback.atMs) .toBeLessThan(50); - expect(snapshot.lastSemanticState.statusText).toBe('当前币无挂单'); + expect(snapshot.lastSemanticState.statusText).toBe('当前交易对无挂单'); const finishedEventCount = snapshot.events.length; await page.evaluate(() => { @@ -102,7 +102,7 @@ test('live completion waits for confirmed cancellation cleanup', async ({ page } expect(() => validateLivePerformanceProbeSnapshot(snapshot)).not.toThrow(); expect(snapshot.events.find((event) => event.kind === 'dialog-action')?.detail?.primary).toBe(true); - expect(snapshot.lastSemanticState.statusText).toBe('撤单流程结束,已恢复筛选状态'); + expect(snapshot.lastSemanticState.statusText).toBe('撤单已完成'); await destroyLivePerformanceProbe(page); }); diff --git a/scripts/binance-orderbook-trade.user.js b/scripts/binance-orderbook-trade.user.js index 3425361..c984d8f 100644 --- a/scripts/binance-orderbook-trade.user.js +++ b/scripts/binance-orderbook-trade.user.js @@ -3,7 +3,7 @@ // @namespace binance.orderbook.trade // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 2.7.160 +// @version 2.7.161 // @author jackhai9 // @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板 // @match https://www.binance.com/*/futures/* @@ -171,7 +171,7 @@ function formatPrecisionRefreshTooltip(tradeCount) { const count = Number(tradeCount); if (!Number.isInteger(count) || count <= 1) { - throw new Error(`Invalid precision trade count: ${tradeCount}`); + throw new Error(`价格精度成交样本数无效:${tradeCount}`); } return `优先根据最新 ${count} 条成交价;价格变化不足时自动扩大范围,重新计算推荐精度。`; } @@ -377,12 +377,12 @@ if (typeof value === "string" && /^-?(?:\d+\.?\d*|\.\d+)$/.test(value.trim())) { return Number(value); } - throw new Error(`invalid position amount: ${String(value)}`); + throw new Error(`持仓数量无效:${String(value)}`); } function resolveSymbolPositionStatus(payload, symbol) { - if (payload?.success !== true) throw new Error("position response was unsuccessful"); - if (!Array.isArray(payload.data)) throw new Error("position response data must be an array"); - if (!symbol) throw new Error("position response requires a symbol"); + if (payload?.success !== true) throw new Error("持仓接口返回失败"); + if (!Array.isArray(payload.data)) throw new Error("持仓接口数据格式异常"); + if (!symbol) throw new Error("持仓接口缺少交易对"); const positions = payload.data.filter((position) => position?.symbol === symbol); const hasPosition = positions.some((position) => parsePositionAmount(position.positionAmount) !== 0); return { @@ -392,9 +392,9 @@ } function observeAutoOpenLeveragePositionState(previousState, observation) { const { symbol, status } = observation; - if (!symbol) throw new Error("auto leverage observation requires a symbol"); + if (!symbol) throw new Error("自动杠杆检查缺少交易对"); if (!POSITION_STATUSES.has(status)) { - throw new Error(`invalid auto leverage position status: ${status}`); + throw new Error(`自动杠杆持仓状态无效:${status}`); } const isSameSymbol = previousState?.symbol === symbol; const previousKnownStatus = isSameSymbol ? previousState.lastKnownStatus : null; @@ -412,7 +412,7 @@ return result; } function parseDecimalString(value) { - const raw = String(value || "").replace(/,/g, "").trim(); + const raw = String(value ?? "").replace(/,/g, "").trim(); if (!/^\d+(\.\d+)?$/.test(raw)) return null; const [intPart, fracPart = ""] = raw.split("."); return { @@ -531,19 +531,19 @@ function getOrderbookPrecisionShortcutOptions(options, limit = 4) { const normalizedLimit = Number(limit); if (!Number.isInteger(normalizedLimit) || normalizedLimit < 1) { - throw new Error(`Invalid orderbook precision shortcut limit: ${limit}`); + throw new Error(`价格精度快捷项数量无效:${limit}`); } return Array.from(new Set(sortedPositiveDecimals(options))).slice(0, normalizedLimit); } function formatOrderbookPrecisionShortcutLabel(value) { const normalized = normalizeDecimalString(value); if (!normalized || !isPositiveDecimalString(normalized)) { - throw new Error(`Invalid orderbook precision shortcut value: ${value}`); + throw new Error(`价格精度快捷值无效:${value}`); } if (normalized.length <= 5) return normalized; const numeric = Number(normalized); if (!Number.isFinite(numeric)) { - throw new Error(`Orderbook precision shortcut value is not finite: ${value}`); + throw new Error(`价格精度快捷值不是有限数值:${value}`); } return numeric.toExponential().replace("e+", "e"); } @@ -620,16 +620,16 @@ minBucketShare = 0.25 }) { if (!Array.isArray(prices)) { - throw new Error("Precision trade prices must be an array"); + throw new Error("价格精度成交价样本必须为数组"); } if (!Number.isInteger(initialLimit) || initialLimit < 2) { - throw new Error(`Invalid initial precision trade limit: ${initialLimit}`); + throw new Error(`价格精度初始样本数无效:${initialLimit}`); } if (!Number.isInteger(expansionStep) || expansionStep < 1) { - throw new Error(`Invalid precision trade expansion step: ${expansionStep}`); + throw new Error(`价格精度样本扩展步长无效:${expansionStep}`); } if (!Number.isInteger(minSamples) || minSamples < 1) { - throw new Error(`Invalid minimum precision sample count: ${minSamples}`); + throw new Error(`价格精度最小样本数无效:${minSamples}`); } let usedCount = Math.min(initialLimit, prices.length); let samples = []; @@ -748,6 +748,19 @@ if (mode === "CLOSE") return closePercent; return null; } + function getUnavailableLadderQuantityMessage(mode, quantity, confirmedZeroOpenBalance = false) { + if (mode !== "OPEN" && mode !== "CLOSE") { + throw new Error(`未知阶梯数量模式:${mode}`); + } + const parsed = parseDecimalString(quantity); + if (parsed?.digits > 0n) return null; + if (mode === "OPEN") { + if (!parsed) return "未读取到可开数量"; + return confirmedZeroOpenBalance ? "可用余额不足" : "当前可开数量为 0"; + } + if (!parsed) return "未读取到可平数量"; + return "当前方向没有可平仓位"; + } function pow103(exp) { let result = 1n; for (let i = 0; i < exp; i += 1) result *= 10n; @@ -868,17 +881,17 @@ // src/binance-orderbook-trade/core/ladder-progress.js function assertLadderProgress(progress) { if (!progress || !Number.isInteger(progress.submittedOrders) || progress.submittedOrders < 0 || !Number.isInteger(progress.cancelledOrders) || progress.cancelledOrders < 0 || !Number.isInteger(progress.currentPlanSubmittedOrders) || progress.currentPlanSubmittedOrders < 0 || (progress.plannedOrders === null ? progress.currentPlanSubmittedOrders !== 0 : !(Number.isInteger(progress.plannedOrders) && progress.plannedOrders > 0 && progress.currentPlanSubmittedOrders <= progress.plannedOrders))) { - throw new Error("Invalid ladder progress"); + throw new Error("阶梯进度状态无效"); } } function assertLadderLabel(label) { if (typeof label !== "string" || label.trim() === "") { - throw new Error("Invalid ladder label"); + throw new Error("阶梯动作名称无效"); } } function assertLadderMessage(message) { if (typeof message !== "string" || message.trim() === "") { - throw new Error("Invalid ladder progress message"); + throw new Error("阶梯进度信息无效"); } } function formatLadderProgressCounts(progress) { @@ -916,7 +929,7 @@ function setLadderPlannedOrders(progress, plannedOrders) { assertLadderProgress(progress); if (!Number.isInteger(plannedOrders) || plannedOrders <= 0) { - throw new Error("Invalid ladder planned orders"); + throw new Error("阶梯计划笔数无效"); } progress.plannedOrders = plannedOrders; progress.currentPlanSubmittedOrders = 0; @@ -924,7 +937,7 @@ function recordLadderSubmittedOrder(progress) { assertLadderProgress(progress); if (progress.plannedOrders !== null && progress.currentPlanSubmittedOrders >= progress.plannedOrders) { - throw new Error("Ladder submitted orders exceed plan"); + throw new Error("阶梯已挂笔数超过计划"); } progress.submittedOrders += 1; if (progress.plannedOrders !== null) progress.currentPlanSubmittedOrders += 1; @@ -953,13 +966,13 @@ assertLadderLabel(label); assertLadderProgress(progress); if (!Number.isInteger(completedOrders) || completedOrders < 0 || !Number.isInteger(totalOrders) || totalOrders < 0) { - throw new Error("Invalid completed ladder total"); + throw new Error("阶梯完成笔数无效"); } if (completedOrders !== totalOrders) { - throw new Error("Completed ladder progress mismatch"); + throw new Error("阶梯完成进度与计划不一致"); } if (progress.plannedOrders !== totalOrders || progress.currentPlanSubmittedOrders !== completedOrders) { - throw new Error("Completed ladder progress mismatch"); + throw new Error("阶梯完成进度与计划不一致"); } const cancelledText = progress.cancelledOrders > 0 ? ` · 已撤 ${progress.cancelledOrders} 笔` : ""; return `${label}已完成 · 已挂 ${completedOrders}/${totalOrders} 笔${cancelledText}`; @@ -978,7 +991,7 @@ }); function assertContinuousLadderProgress(progress) { if (!progress || !Number.isInteger(progress.startedRounds) || progress.startedRounds < 0 || !Number.isInteger(progress.completedRounds) || progress.completedRounds < 0 || progress.completedRounds > progress.startedRounds || !Number.isInteger(progress.submittedOrders) || progress.submittedOrders < 0 || !Number.isInteger(progress.cancelledOrders) || progress.cancelledOrders < 0 || progress.startedRounds === 0 !== (progress.lastRound === null)) { - throw new Error("Invalid continuous ladder progress"); + throw new Error("连续阶梯进度状态无效"); } if (progress.lastRound !== null) snapshotLadderProgress(progress.lastRound); } @@ -994,13 +1007,13 @@ function recordContinuousLadderRound(progress, outcome) { assertContinuousLadderProgress(progress); if (!outcome || typeof outcome !== "object") { - throw new Error("Invalid continuous ladder round outcome"); + throw new Error("连续阶梯本轮结果无效"); } if (recordedRoundOutcomes.has(outcome)) { - throw new Error("Continuous ladder round was already recorded"); + throw new Error("连续阶梯本轮结果已记录"); } if (!["completed", "stopped", "failed", "interrupted"].includes(outcome.status)) { - throw new Error("Invalid continuous ladder round outcome"); + throw new Error("连续阶梯本轮结果无效"); } const roundProgress = snapshotLadderProgress(outcome.progress); progress.startedRounds += 1; @@ -1016,11 +1029,11 @@ } function buildContinuousLadderProgressParts(label, phase, progress) { if (typeof label !== "string" || label.trim() === "") { - throw new Error("Invalid continuous ladder label"); + throw new Error("连续阶梯动作名称无效"); } const phaseText = CONTINUOUS_LADDER_PHASE_TEXT[phase]; if (!Object.hasOwn(CONTINUOUS_LADDER_PHASE_TEXT, phase)) { - throw new Error("Invalid continuous ladder phase"); + throw new Error("连续阶梯阶段无效"); } assertContinuousLadderProgress(progress); const parts = [`连续${label}`]; @@ -1039,10 +1052,10 @@ } function formatActiveContinuousLadderProgress(label, detail, progress, roundProgress) { if (typeof label !== "string" || label.trim() === "") { - throw new Error("Invalid continuous ladder label"); + throw new Error("连续阶梯动作名称无效"); } if (detail !== null && (typeof detail !== "string" || detail.trim() === "")) { - throw new Error("Invalid active continuous ladder detail"); + throw new Error("连续阶梯当前进度信息无效"); } assertContinuousLadderProgress(progress); const round = snapshotLadderProgress(roundProgress); @@ -1061,7 +1074,7 @@ } function formatContinuousLadderProgress(label, phase, progress, reason = null) { if (reason !== null && (typeof reason !== "string" || reason.trim() === "")) { - throw new Error("Invalid continuous ladder reason"); + throw new Error("连续阶梯停止原因无效"); } const parts = buildContinuousLadderProgressParts(label, phase, progress); if (reason !== null) parts.push(reason); @@ -1069,10 +1082,10 @@ } function formatContinuousLadderWaitReason(phase, cooldownMs) { if (!Number.isFinite(cooldownMs) || cooldownMs < 0) { - throw new Error("Invalid continuous ladder cooldown"); + throw new Error("连续阶梯轮间等待时间无效"); } if (phase === "waiting_ready") return "等待按钮恢复"; - if (phase !== "cooldown") throw new Error("Invalid continuous ladder wait phase"); + if (phase !== "cooldown") throw new Error("连续阶梯等待阶段无效"); const duration = cooldownMs % 1e3 === 0 ? `${cooldownMs / 1e3}s` : `${cooldownMs}ms`; return `${duration} 后继续`; } @@ -1083,7 +1096,7 @@ } function assertReadinessState(state) { if (!["ready", "waiting", "stopped"].includes(state?.status)) { - throw new Error("Invalid continuous ladder readiness state"); + throw new Error("连续阶梯按钮就绪状态无效"); } return state; } @@ -1117,10 +1130,10 @@ onWaitStateChange = () => { } }) { - if (!(cooldownMs >= 0)) throw new Error("Invalid continuous ladder cooldown"); - if (!(readyCheckMs > 0)) throw new Error("Invalid continuous ladder readiness interval"); + if (!(cooldownMs >= 0)) throw new Error("连续阶梯轮间等待时间无效"); + if (!(readyCheckMs > 0)) throw new Error("连续阶梯按钮检查间隔无效"); if (typeof onWaitStateChange !== "function") { - throw new Error("Invalid continuous ladder wait-state callback"); + throw new Error("连续阶梯等待状态回调无效"); } let waitingAlreadyReported = false; while (true) { @@ -1149,13 +1162,13 @@ var STATUS_QUOTE_ASSETS = Object.freeze(["USDT", "USDC"]); function formatStatusBaseAsset(symbol) { if (typeof symbol !== "string") { - throw new Error(`Unsupported futures status symbol: ${symbol}`); + throw new Error(`不支持的合约状态交易对:${symbol}`); } const normalized = symbol.trim().toUpperCase(); const quoteAsset = STATUS_QUOTE_ASSETS.find((candidate) => normalized.endsWith(candidate)); const baseAsset = quoteAsset ? normalized.slice(0, -quoteAsset.length) : ""; if (!baseAsset) { - throw new Error(`Unsupported futures status symbol: ${symbol}`); + throw new Error(`不支持的合约状态交易对:${symbol}`); } return baseAsset; } @@ -1554,10 +1567,10 @@ } function createBoundedInputWriter({ writeValue, maxWriteAttempts }) { if (typeof writeValue !== "function") { - throw new Error("Bounded input writer dependency is invalid"); + throw new Error("输入框写入依赖异常"); } if (!Number.isInteger(maxWriteAttempts) || maxWriteAttempts < 1) { - throw new Error("Trade input write attempts must be a positive integer"); + throw new Error("输入框写入次数必须为正整数"); } const attemptsByInput = /* @__PURE__ */ new WeakMap(); return (input, value) => { @@ -1576,7 +1589,7 @@ compareValues }) { if (typeof compareValues !== "function") { - throw new Error("Trade input recovery comparison dependency is invalid"); + throw new Error("输入框恢复校验依赖异常"); } const isScriptOwnedOrEmpty = (value) => value === null || previousSubmittedValue != null && compareValues(previousSubmittedValue, value) === 0; return isScriptOwnedOrEmpty(preWriteValue) && isScriptOwnedOrEmpty(rollbackValue) && isScriptOwnedOrEmpty(submittedValue); @@ -1599,25 +1612,25 @@ readNowMs = Date.now }) { if (typeof resolveInputs !== "function" || typeof normalizeValue !== "function" || typeof compareValues !== "function" || typeof writeValue !== "function" || typeof isRecoveryWriteAllowed !== "function" || typeof readNowMs !== "function") { - throw new Error("Trade input synchronizer dependencies are invalid"); + throw new Error("交易输入框同步依赖异常"); } if (!Number.isInteger(requiredStableMismatchFrames) || requiredStableMismatchFrames < 1) { - throw new Error("Trade input rollback stability must be a positive integer"); + throw new Error("输入框回退稳定帧数必须为正整数"); } if (!Number.isInteger(requiredStableMatchFrames) || requiredStableMatchFrames < 1) { - throw new Error("Trade input accepted stability must be a positive integer"); + throw new Error("输入框写入稳定帧数必须为正整数"); } if (!Number.isFinite(requiredStableMismatchMs) || requiredStableMismatchMs < 0) { - throw new Error("Trade input rollback stability duration must be a non-negative number"); + throw new Error("输入框回退稳定时间不能为负数"); } if (!Number.isFinite(requiredStableMatchMs) || requiredStableMatchMs < 0) { - throw new Error("Trade input accepted stability duration must be a non-negative number"); + throw new Error("输入框写入稳定时间不能为负数"); } if (!Number.isInteger(maxWriteAttempts) || maxWriteAttempts < 1) { - throw new Error("Trade input write attempts must be a positive integer"); + throw new Error("输入框写入次数必须为正整数"); } if (typeof recoverProvisionalMatchRollback !== "boolean") { - throw new Error("Provisional trade input recovery flag must be boolean"); + throw new Error("输入框临时恢复标记必须为布尔值"); } const createSyncSlot = (field) => { let root = null; @@ -1845,10 +1858,10 @@ function waitForTradeFormFrameState(observationRoot, readState, timeoutMs, requiredStableFrames = 2) { const view = observationRoot?.ownerDocument?.defaultView; if (!view || typeof view.requestAnimationFrame !== "function" || typeof view.cancelAnimationFrame !== "function") { - throw new Error("Trade form frame scheduler is unavailable"); + throw new Error("交易表单帧调度器不可用"); } if (!Number.isInteger(requiredStableFrames) || requiredStableFrames < 1) { - throw new Error("requiredStableFrames must be a positive integer"); + throw new Error("稳定帧数必须为正整数"); } return new Promise((resolve) => { let settled = false; @@ -1886,13 +1899,13 @@ function waitForTradeActionButtonFrameState(observationRoot, findButton, isVisibleElement, timeoutMs, requiredStableFrames = 2) { const view = observationRoot?.ownerDocument?.defaultView || observationRoot?.defaultView; if (!view || typeof view.requestAnimationFrame !== "function" || typeof view.cancelAnimationFrame !== "function") { - throw new Error("Trade action button frame scheduler is unavailable"); + throw new Error("下单按钮帧调度器不可用"); } if (typeof findButton !== "function" || typeof isVisibleElement !== "function") { - throw new Error("Trade action button resolver is unavailable"); + throw new Error("下单按钮定位器不可用"); } if (!Number.isInteger(requiredStableFrames) || requiredStableFrames < 1) { - throw new Error("requiredStableFrames must be a positive integer"); + throw new Error("稳定帧数必须为正整数"); } return new Promise((resolve) => { let settled = false; @@ -2035,14 +2048,14 @@ } function repriceRemainingLadderOrders({ orders, completedCount, prices }) { if (!Array.isArray(orders) || !Number.isInteger(completedCount) || completedCount < 0 || completedCount > orders.length) { - throw new Error("Invalid completed ladder count"); + throw new Error("已完成阶梯订单数无效"); } const remainingCount = orders.length - completedCount; if (!Array.isArray(prices) || prices.length !== remainingCount) { - throw new Error(`Expected ${remainingCount} replacement prices`); + throw new Error(`重定价数量不一致:预期 ${remainingCount} 个价格`); } if (prices.some((price) => !isPositiveDecimalString(normalizeDecimalString(price)))) { - throw new Error("Invalid replacement ladder price"); + throw new Error("阶梯重定价价格无效"); } return orders.map((order, index) => index < completedCount ? { ...order } : { ...order, price: normalizeDecimalString(prices[index - completedCount]) }); } @@ -2076,10 +2089,10 @@ } function modeSymbolPrecisionOptionStorageKey(modeKeys, mode, symbol, precision) { if (mode !== "OPEN" && mode !== "CLOSE") { - throw new Error(`Unknown trade mode: ${mode}`); + throw new Error(`未知交易模式:${mode}`); } const baseKey = modeKeys[mode]; - if (!baseKey) throw new Error(`Missing storage key for trade mode: ${mode}`); + if (!baseKey) throw new Error(`交易模式缺少存储键:${mode}`); const normalizedSymbol = String(symbol || "").toUpperCase(); const normalizedPrecision = normalizePrecisionScopeValue(precision); return normalizedSymbol && normalizedPrecision ? `${baseKey}:${normalizedSymbol}:${normalizedPrecision}` : null; @@ -2095,7 +2108,7 @@ function migrateModeSymbolPrecisionNumberOption(storage, modeKeys, mode, symbol, precision, retiredValue, replacementValue, options) { const numericReplacement = Number(replacementValue); if (!options.includes(numericReplacement)) { - throw new Error(`Invalid replacement option: ${replacementValue}`); + throw new Error(`替换选项无效:${replacementValue}`); } const storageKey = modeSymbolPrecisionOptionStorageKey(modeKeys, mode, symbol, precision); if (!storageKey) return false; @@ -2125,12 +2138,12 @@ // src/binance-orderbook-trade/core/chart-orders-recovery.js var CHART_ORDERS_RECOVERY_STORAGE_KEY = "binance-orderbook-trade:chart-orders-recovery:v2"; function createChartOrdersRecoveryRecord(nowMs) { - if (!Number.isFinite(nowMs)) throw new Error("Chart orders recovery timestamp is invalid"); + if (!Number.isFinite(nowMs)) throw new Error("图表委托线恢复时间无效"); return JSON.stringify({ version: 2, originalChecked: true, createdAtMs: nowMs }); } function parseChartOrdersRecoveryRecord(rawValue, nowMs) { if (rawValue === null) return { status: "missing", record: null }; - if (!Number.isFinite(nowMs)) throw new Error("Chart orders recovery current time is invalid"); + if (!Number.isFinite(nowMs)) throw new Error("图表委托线恢复当前时间无效"); let record; try { record = JSON.parse(rawValue); @@ -2148,18 +2161,18 @@ var IGNORED_DRAWING_EVENT_TYPES = /* @__PURE__ */ new Set(["click", "move"]); function validateTradingViewApi(api) { if (!api || typeof api !== "object") { - throw new Error("TradingView API is unavailable"); + throw new Error("图表接口不可用"); } if (typeof api.saveChart !== "function") { - throw new Error("TradingView saveChart API is unavailable"); + throw new Error("图表保存接口不可用"); } if (typeof api.subscribe !== "function" || typeof api.unsubscribe !== "function") { - throw new Error("TradingView drawing-event subscription API is unavailable"); + throw new Error("图表事件接口不可用"); } } function restoreSaveChartMethod(api, wrapper, originalSaveChart, originalDescriptor) { if (api.saveChart !== wrapper) { - throw new Error("TradingView saveChart API changed during save coalescing"); + throw new Error("图表保存接口在操作期间发生变化"); } if (originalDescriptor) { Object.defineProperty(api, "saveChart", originalDescriptor); @@ -2167,7 +2180,7 @@ delete api.saveChart; } if (api.saveChart !== originalSaveChart) { - throw new Error("TradingView saveChart API was not restored"); + throw new Error("图表保存接口未能恢复"); } } async function coalesceTradingViewDrawingSaves(api, action, { @@ -2178,15 +2191,15 @@ clearTimeoutFn = clearTimeout } = {}) { validateTradingViewApi(api); - if (typeof action !== "function") throw new Error("Chart action is unavailable"); + if (typeof action !== "function") throw new Error("图表操作不可用"); if (!Number.isFinite(eventDiscoveryTimeoutMs) || eventDiscoveryTimeoutMs < 0) { - throw new Error("TradingView drawing-event discovery timeout is invalid"); + throw new Error("图表事件等待时间无效"); } if (!Number.isFinite(settleQuietMs) || settleQuietMs <= 0) { - throw new Error("TradingView drawing-save quiet window is invalid"); + throw new Error("图表保存稳定等待时间无效"); } if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { - throw new Error("TradingView save coalescing timeout is invalid"); + throw new Error("图表保存超时时间无效"); } const originalSaveChart = api.saveChart; const originalDescriptor = Object.getOwnPropertyDescriptor(api, "saveChart"); @@ -2237,7 +2250,7 @@ try { api.saveChart = saveChartWrapper; if (api.saveChart !== saveChartWrapper) { - throw new Error("TradingView saveChart API is not writable"); + throw new Error("图表保存接口无法临时接管"); } } catch (error) { api.unsubscribe("drawing_event", handleDrawingEvent); @@ -2260,12 +2273,12 @@ waitTimeout = setTimeoutFn(() => { if (saveRequestCount < drawingEventCount) { settleReject(new Error( - `Expected ${drawingEventCount} TradingView saveChart requests, received ${saveRequestCount}` + `图表保存请求数量不一致:预期 ${drawingEventCount},实际 ${saveRequestCount}` )); return; } settleReject(new Error( - `TradingView drawing-save burst did not settle within ${timeoutMs}ms` + `图表保存未在 ${timeoutMs} 毫秒内完成` )); }, timeoutMs); await burstSettled; @@ -2328,11 +2341,11 @@ const buttons = Array.from(dialog.querySelectorAll("button")).filter(isVisibleElement); if (!buttons.length) return null; if (buttons.length !== 2) { - throw new Error(`Expected two Binance cancel-all dialog buttons, found ${buttons.length}`); + throw new Error(`撤单确认弹窗按钮数量异常:${buttons.length}`); } const primaryButtons = buttons.filter((button) => button.matches(PRIMARY_BUTTON_SELECTOR)); if (primaryButtons.length !== 1) { - throw new Error(`Expected one Binance cancel-all primary button, found ${primaryButtons.length}`); + throw new Error(`撤单确认按钮数量异常:${primaryButtons.length}`); } const confirmButton = primaryButtons[0]; const cancelButton = buttons.find((button) => button !== confirmButton); @@ -2347,7 +2360,7 @@ if (!existing) actionPairs.push(contract); } if (actionPairs.length !== 1) { - throw new Error(`Expected one Binance cancel-all dialog action pair, found ${actionPairs.length}`); + throw new Error(`撤单确认弹窗操作区域数量异常:${actionPairs.length}`); } return contracts.reduce((innermost, contract) => innermost.dialog.contains(contract.dialog) ? contract : innermost); } @@ -2490,13 +2503,13 @@ const chartRoots = Array.from(document2.querySelectorAll(CHART_ROOT_SELECTOR)).filter(hasVisibleBox); if (!chartRoots.length) return null; if (chartRoots.length > 1) { - throw new Error(`Expected one visible Binance chart root, found ${chartRoots.length}`); + throw new Error(`可见图表区域数量异常:${chartRoots.length}`); } const chartRoot = chartRoots[0]; const tradingViewApis = Array.from(chartRoot.querySelectorAll("iframe")).map((frame) => frame.contentWindow?.tradingViewApi).filter(Boolean); if (!tradingViewApis.length) return null; if (tradingViewApis.length > 1) { - throw new Error(`Expected one Binance TradingView API, found ${tradingViewApis.length}`); + throw new Error(`图表接口数量异常:${tradingViewApis.length}`); } return { chartRoot, tradingViewApi: tradingViewApis[0] }; } @@ -2526,7 +2539,7 @@ }); if (!toolbars.length) return null; if (toolbars.length > 1) { - throw new Error(`Expected one Binance chart toolbar, found ${toolbars.length}`); + throw new Error(`图表工具栏数量异常:${toolbars.length}`); } const toolbar = toolbars[0]; const trigger = toolbar.children[toolbar.children.length - 2]; @@ -2534,12 +2547,10 @@ trigger.querySelectorAll(".bn-tooltips-ele[aria-describedby]") ); if (popoverReferences.length !== 1) { - throw new Error( - `Expected one Binance chart orders popover reference, found ${popoverReferences.length}` - ); + throw new Error(`图表“显示当前委托”菜单入口数量异常:${popoverReferences.length}`); } const popoverId = popoverReferences[0].getAttribute("aria-describedby"); - if (!popoverId) throw new Error("Binance chart orders popover id is unavailable"); + if (!popoverId) throw new Error("图表“显示当前委托”菜单标识缺失"); return { chartRoot, tradingViewApi, @@ -2550,19 +2561,19 @@ } function getBinanceChartOrdersTarget(document2) { const target = findBinanceChartOrdersTarget(document2); - if (!target) throw new Error("Binance chart orders target is unavailable"); + if (!target) throw new Error("未找到图表“显示当前委托”控件"); return target; } function assertSameBinanceChartOrdersTarget(capturedTarget, currentTarget) { if (!capturedTarget || !currentTarget) { - throw new Error("Binance chart orders target is unavailable"); + throw new Error("未找到图表“显示当前委托”控件"); } if (capturedTarget.chartRoot !== currentTarget.chartRoot || capturedTarget.tradingViewApi !== currentTarget.tradingViewApi || capturedTarget.toolbar !== currentTarget.toolbar || capturedTarget.trigger !== currentTarget.trigger || capturedTarget.popoverId !== currentTarget.popoverId) { - throw new Error("Binance chart orders target changed"); + throw new Error("图表“显示当前委托”控件已变化"); } } function findActiveBinanceChartOrdersPopover(document2, target, isVisibleElement) { - if (!target?.popoverId) throw new Error("Binance chart orders target is unavailable"); + if (!target?.popoverId) throw new Error("未找到图表“显示当前委托”控件"); const popover = document2.getElementById(target.popoverId); if (!popover || !popover.matches(ACTIVE_POPOVER_SELECTOR) || !isVisibleElement(popover)) { return null; @@ -2570,12 +2581,12 @@ const checkboxes = Array.from(popover.querySelectorAll('[role="checkbox"]')).filter(isVisibleElement).filter((checkbox2) => OPEN_ORDERS_LABEL_PATTERN.test(normalizeLabel(checkbox2.textContent))); if (!checkboxes.length) return null; if (checkboxes.length > 1) { - throw new Error(`Expected one Binance chart OpenOrders checkbox, found ${checkboxes.length}`); + throw new Error(`图表“显示当前委托”选项数量异常:${checkboxes.length}`); } const checkbox = checkboxes[0]; const checkedValue = checkbox.getAttribute("aria-checked"); if (checkedValue !== "true" && checkedValue !== "false") { - throw new Error(`Binance chart OpenOrders state is ${checkedValue}`); + throw new Error(`图表“显示当前委托”状态异常:${checkedValue}`); } return { popover, checkbox, checked: checkedValue === "true" }; } @@ -3332,7 +3343,7 @@ } async function waitForLadderSubmitResponseObservations(captureId, timeoutMs) { const capture = activeLadderSubmitCapture?.captureId === captureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error("下单响应捕获上下文丢失"); + if (!capture) throw new Error("下单结果跟踪状态异常,已停止"); const observations = capture.responseObservations.slice(); if (observations.length > 0 && timeoutMs > 0) { await Promise.race([ @@ -3344,12 +3355,12 @@ } function readLadderSubmitApiErrors(captureId) { const capture = activeLadderSubmitCapture?.captureId === captureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error("下单响应捕获上下文丢失"); + if (!capture) throw new Error("下单结果跟踪状态异常,已停止"); return capture.apiErrors.slice(); } function readLadderSubmitApiSuccesses(captureId) { const capture = activeLadderSubmitCapture?.captureId === captureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error("下单响应捕获上下文丢失"); + if (!capture) throw new Error("下单结果跟踪状态异常,已停止"); return capture.apiSuccesses.slice(); } (function installFetchInterceptor() { @@ -3381,7 +3392,7 @@ } async function adjustLeverageApi(symbol, leverage) { if (!cachedBncHeaders) { - throw new Error("bapi header 尚未缓存"); + throw new Error("币安请求头尚未就绪"); } const controller = new AbortController(); const timer = window.setTimeout(() => controller.abort(), 5e3); @@ -3396,7 +3407,7 @@ signal: controller.signal } ); - if (!resp.ok) throw new Error(`adjustLeverage HTTP ${resp.status}`); + if (!resp.ok) throw new Error(`杠杆调整接口异常:HTTP ${resp.status}`); const data = await resp.json(); if (!data.success) throw new Error(data.message || `code=${data.code}`); return data; @@ -3471,7 +3482,7 @@ const key = orderbookPrecisionSamplesKey(symbol); if (!key) return []; if (!Array.isArray(samples)) { - throw new Error("Invalid latest trade movement snapshot"); + throw new Error("最新成交价样本状态异常"); } const normalizedSamples = samples.map((sample) => normalizeDecimalString(sample)).filter((sample) => sample && isPositiveDecimalString(sample)); localStorage.setItem(key, JSON.stringify(normalizedSamples)); @@ -3673,7 +3684,7 @@ const selected = value === current; const recommended = value === recommendation; const disabledAttrs = disabled ? ' disabled aria-disabled="true"' : ""; - const title = disabled ? "缩放调整暂不可用" : selected && recommended ? `当前且推荐的价格精度 ${value}` : selected ? `当前价格精度 ${value}` : recommended ? `推荐价格精度 ${value}` : `切换到价格精度 ${value}`; + const title = disabled ? "价格精度暂不可调整" : selected && recommended ? `当前且推荐的价格精度 ${value}` : selected ? `当前价格精度 ${value}` : recommended ? `推荐价格精度 ${value}` : `切换到价格精度 ${value}`; const activeStyle = selected ? `border-color:var(--color-PrimaryYellow);background:var(--color-BadgeBg);color:${PRIMARY_EMPHASIS_COLOR};font-weight:${PRIMARY_EMPHASIS_FONT_WEIGHT};` : NEUTRAL_CONTROL_STYLE; const recommendationMarker = recommended ? '' : ""; return ``; @@ -3705,7 +3716,7 @@ } function showOrderbookPrecisionRefreshFeedback(symbol, state) { if (!["success", "retry"].includes(state)) { - throw new Error(`Invalid orderbook precision refresh feedback state: ${state}`); + throw new Error(`价格精度刷新反馈状态异常:${state}`); } if (orderbookPrecisionRefreshFeedbackTimer !== null) { window.clearTimeout(orderbookPrecisionRefreshFeedbackTimer); @@ -3796,14 +3807,14 @@ closed = await closeOrderbookPrecisionOptions(trigger.element, cleanupPrecision, true); } } - if (!closed) return { status: "无法关闭原生缩放下拉" }; + if (!closed) return { status: "无法关闭价格精度下拉" }; if (snapshot) return snapshot; if (Date.now() >= deadline) break; await delay(ORDERBOOK_PRECISION_READY_POLL_MS); } if (!isCurrentObservedSymbol(symbol)) return null; return { - status: lastPrecision ? `未找到当前缩放 ${lastPrecision} 的原生档位` : "订单簿尚未就绪" + status: lastPrecision ? `未找到当前价格精度 ${lastPrecision} 的选项` : "订单簿尚未就绪" }; } async function runLoadOrderbookPrecisionOptions() { @@ -3834,7 +3845,7 @@ if (!isCurrentObservedSymbol(symbol)) return false; const trigger = findOrderbookPrecisionTrigger(); if (!symbol || !trigger?.element) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: "未定位到缩放下拉" }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: "未找到价格精度下拉" }; scheduleRenderPanel(); return false; } @@ -3844,7 +3855,7 @@ if (!isCurrentObservedSymbol(symbol) || readCurrentOrderbookPrecisionValue() !== startPrecision) return false; const values = readVisibleOrderbookPrecisionOptionValues(trigger.element); if (!options.length || !values.includes(startPrecision)) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: "读取原生缩放档位失败" }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: "读取价格精度选项失败" }; scheduleRenderPanel(); return false; } @@ -3859,7 +3870,7 @@ nativeOptionsStatus: null }; if (!options.length || !shortcutOptions.includes(targetPrecision)) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: `未找到快捷缩放 ${targetPrecision}` }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: `未找到快捷精度 ${targetPrecision}` }; scheduleRenderPanel(); return false; } @@ -3871,7 +3882,7 @@ }); if (!selected) { if (isCurrentObservedSymbol(symbol)) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: `未找到 ${targetPrecision} 档切换结果` }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: `价格精度未切换到 ${targetPrecision}` }; scheduleRenderPanel(); } return false; @@ -3895,7 +3906,7 @@ function selectOrderbookPrecision(value) { const targetPrecision = normalizeDecimalString(value); if (!targetPrecision || !isPositiveDecimalString(targetPrecision)) { - throw new Error(`无效的订单簿缩放快捷值: ${value}`); + throw new Error(`无效的价格精度快捷值: ${value}`); } return runOrderbookPrecisionSelectionTask(() => runSelectOrderbookPrecision(targetPrecision)); } @@ -3918,7 +3929,7 @@ if (isCurrentObservedSymbol(symbol)) { orderbookPrecisionState = { ...orderbookPrecisionState, - nativeOptionsStatus: "读取原生缩放档位失败" + nativeOptionsStatus: "读取价格精度选项失败" }; scheduleRenderPanel(); } @@ -3937,7 +3948,7 @@ orderbookPrecisionState = { ...orderbookPrecisionState, symbol, - status: "未定位当前交易对" + status: "未识别当前交易对" }; scheduleRenderPanel(); return; @@ -4049,6 +4060,10 @@ const qty = spec.side === "LONG" ? longQty : shortQty; return { qty, qtySource }; }; + const classifyUnavailableQuantity = (quantity) => ({ + ...quantity, + confirmedZeroOpenBalance: isConfirmedZeroOpenBalance(quantity.qty) + }); const mutationRoot = getTradeMutationRoot(); const ready = await waitForTradeFormMutationState( mutationRoot, @@ -4056,13 +4071,14 @@ const quantity = readQuantity(); const { qty } = quantity; if (qty != null && isPositiveDecimalString(String(qty))) { - return quantity; + return { ...quantity, confirmedZeroOpenBalance: false }; } - return isConfirmedZeroOpenBalance(qty) ? quantity : null; + const classified = classifyUnavailableQuantity(quantity); + return classified.confirmedZeroOpenBalance ? classified : null; }, LADDER_OPEN_QTY_READY_TIMEOUT_MS ); - return ready || readQuantity(); + return ready || classifyUnavailableQuantity(readQuantity()); } function readCloseBaseQtyForLadder(spec) { const symbol = getCurrentSymbol(); @@ -4086,15 +4102,19 @@ if (expectedContext?.mode && spec.mode !== expectedContext.mode) { throw new Error("重挂前开仓/平仓模式已变化,已停止"); } + const modeLabel = spec.mode === "OPEN" ? "开仓" : "平仓"; const modeReady = await activateTradeMode(spec.mode); - if (!modeReady || getCurrentSymbol() !== startSymbol) throw new Error("切换开仓/平仓失败或交易对已变化"); + if (getCurrentSymbol() !== startSymbol) { + throw new Error(`切换至${modeLabel}时交易对已变化,已停止`); + } + if (!modeReady) throw new Error(`未能切换至${modeLabel}`); const startPrecision = readCurrentOrderbookPrecisionValue(); - if (!startPrecision) throw new Error("未识别订单簿缩放值"); + if (!startPrecision) throw new Error("未识别价格精度"); if (expectedContext?.precision && startPrecision !== expectedContext.precision) { - throw new Error("重挂前订单簿缩放值已变化,已停止"); + throw new Error("重挂前价格精度已变化,已停止"); } const postOnlyReady = await ensurePostOnlyOrderType(); - if (!postOnlyReady) throw new Error("请刷新页面让只做Maker (Post Only) 生效后重试,脚本不会用普通限价继续"); + if (!postOnlyReady) throw new Error("只做 Maker 未生效,请刷新页面后重试"); const levels = getLadderLevels(spec.mode, startSymbol, startPrecision); const ladderStep = getLadderStep(spec.mode, startSymbol, startPrecision); const prices = getBufferedMakerPrices(spec.priceSide, levels, ladderStep); @@ -4102,23 +4122,40 @@ throw new Error(`订单簿${spec.priceSide === "BID" ? "买盘" : "卖盘"}不足 ${levels} 档,档幅 ${ladderStep}`); } const rules = await ensureRules(startSymbol); - if (!rules || getCurrentSymbol() !== startSymbol || readCurrentOrderbookPrecisionValue() !== startPrecision) { - throw new Error("交易规则未就绪或交易上下文已变化"); + if (getCurrentSymbol() !== startSymbol) { + throw new Error("读取交易规则时交易对已变化,已停止"); + } + if (readCurrentOrderbookPrecisionValue() !== startPrecision) { + throw new Error("读取交易规则时价格精度已变化,已停止"); } + if (!rules) throw new Error("交易规则尚未就绪,请稍后重试"); const ruleContext = getQtyRuleContext(startSymbol, spec.mode, prices[0]); if (ruleContext.status !== "ready" || !ruleContext.stepSize || !ruleContext.baseMinQty) { - throw new Error("数量步进/最小量未就绪"); + throw new Error("下单数量规则尚未就绪,请稍后重试"); } const minRequiredQtyByLevel = spec.mode === "OPEN" ? prices.map((price) => getQtyRuleContext(startSymbol, spec.mode, price).effectiveMinQty || ruleContext.baseMinQty) : prices.map(() => ruleContext.baseMinQty); let minRequiredQty = minRequiredQtyByLevel.filter(Boolean).reduce((maxQty, qty) => maxDecimalString(maxQty, qty), ruleContext.baseMinQty); const base = spec.mode === "OPEN" ? await readOpenBaseQtyForLadder(spec, prices[0]) : readCloseBaseQtyForLadder(spec); - if (getCurrentSymbol() !== startSymbol || getActiveTradeMode() !== spec.mode || readCurrentOrderbookPrecisionValue() !== startPrecision || !isPostOnlyOrderTypeActive()) { - throw new Error("读取可用数量后交易上下文已变化,已停止"); + const quantityLabel = spec.mode === "OPEN" ? "可开数量" : "可平数量"; + if (getCurrentSymbol() !== startSymbol) { + throw new Error(`读取${quantityLabel}时交易对已变化,已停止`); + } + if (getActiveTradeMode() !== spec.mode) { + throw new Error(`读取${quantityLabel}时下单模式已变化,已停止`); + } + if (readCurrentOrderbookPrecisionValue() !== startPrecision) { + throw new Error(`读取${quantityLabel}时价格精度已变化,已停止`); } - const baseQty = normalizeDecimalString(base?.qty || ""); - if (!baseQty || !isPositiveDecimalString(baseQty)) { - throw new Error(`未读取到可用${spec.mode === "OPEN" ? "可开" : "可平"}数量`); + if (!isPostOnlyOrderTypeActive()) { + throw new Error(`读取${quantityLabel}时只做 Maker 已失效,请刷新页面后重试`); } + const baseQty = normalizeDecimalString(base?.qty ?? ""); + const unavailableQuantityMessage = getUnavailableLadderQuantityMessage( + spec.mode, + baseQty, + base?.confirmedZeroOpenBalance === true + ); + if (unavailableQuantityMessage) throw new Error(unavailableQuantityMessage); let percent = getLadderPercentForMode( spec.mode, spec.mode === "OPEN" ? getLadderOpenPercent(startSymbol, startPrecision) : null, @@ -4199,7 +4236,7 @@ const minimumText = minimumPercent ? `至少需要${percentLabel} ${minimumPercent}% 才能保持当前档位。` : ""; const maxText = maxAutoFitPercent ? `自动上限 ${maxAutoFitPercent}%。` : ""; const levelsText = levels ? `当前档位 ${levels} 档。` : ""; - const replacementText = mode === "OPEN" ? "脚本只会尝试替换当前币同向开仓基础单,不会自动全撤。" : "脚本不会自动撤单。"; + const replacementText = mode === "OPEN" ? "脚本只会尝试替换当前交易对的同向开仓基础单,不会自动全撤。" : "脚本不会自动撤单。"; error.statusTitle = `当前${percentLabel} ${percent}%,目标数量小于最小下单量 ${minRequiredQty},无法阶梯${actionLabel};${levelsText}${minimumText}${maxText}已尝试自动提高比例和自动降档;${replacementText}`; if (mode === "OPEN" && spec && symbol && precision && replacementTotalQty && isPositiveDecimalString(replacementTotalQty)) { error.openOrdersReplacementPlan = { @@ -4277,12 +4314,12 @@ return remainingCount; } function assertLadderExecutionContext(plan) { - if (!isCurrentObservedSymbol(plan.symbol)) throw new Error("执行中交易对变化,已停止"); - if (getActiveTradeMode() !== plan.spec.mode) throw new Error("执行中开仓/平仓模式变化,已停止"); + if (!isCurrentObservedSymbol(plan.symbol)) throw new Error("执行中交易对已变化,已停止"); + if (getActiveTradeMode() !== plan.spec.mode) throw new Error("执行中下单模式已变化,已停止"); if (readCurrentOrderbookPrecisionValue() !== plan.precision) { - throw new Error("执行中订单簿缩放值变化,已停止"); + throw new Error("执行中价格精度已变化,已停止"); } - if (!isPostOnlyOrderTypeActive()) throw new Error("执行中只做Maker (Post Only) 状态丢失,请刷新页面后重试"); + if (!isPostOnlyOrderTypeActive()) throw new Error("执行中只做 Maker 已失效,请刷新页面后重试"); } function assertSubmittedPriceMatchesExpectedPrice(expectedPrice, submittedPrice, expectedLabel = "点击价") { const expected = normalizeDecimalString(expectedPrice); @@ -4330,7 +4367,7 @@ maxWriteAttempts }) : setInputValueReact; const inputs = findTradeInputs(); - if (!inputs) throw new Error("未找到唯一的当前交易表单输入框"); + if (!inputs) throw new Error("未能确定唯一的当前交易表单输入框"); const observationRoot = inputs.root; const readTradeState = createTradeInputStateReader({ resolveInputs: findTradeInputs, @@ -4363,7 +4400,7 @@ priceLabel ); assertSubmittedQtyMatchesExpectedQty(expectedQty, findQtyInput()?.value || "", qtyLabel); - throw new Error("价格和数量状态未稳定,已停止提交"); + throw new Error("价格框或数量框未稳定,已停止提交"); } function isSubmitButtonBusy(button) { if (!button) return false; @@ -4433,7 +4470,7 @@ } function waitForOrderSubmitStartOrFailureFeedback(submitCaptureId, previousFeedbackSnapshot, timeoutMs) { const capture = activeLadderSubmitCapture?.captureId === submitCaptureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error("下单响应捕获上下文丢失"); + if (!capture) throw new Error("下单结果跟踪状态异常,已停止"); return new Promise((resolve) => { let settled = false; const finish = (result) => { @@ -4532,13 +4569,14 @@ throw new Error(`${failureActivity.message}(${diagnostic})`); } if (capturedApiSuccesses.length === 1) return; - const settleHint = sawBusy ? "按钮已恢复但未捕获当前下单 API 成功响应" : "未捕获当前下单 API 成功响应"; - throw new Error(`未收到明确${label}成功反馈(${settleHint}),已停止;请核对当前委托/历史成交`); + const settleHint = sawBusy ? "下单按钮已恢复,但仍未确认订单结果" : "仍未确认订单结果"; + throw new Error(`未确认${label}成功(${settleHint}),已停止;请在当前委托和历史成交中核对`); } async function executeLadderPlan(plan, progress, setExecutionStatus, abortSignal = null) { const priceInput = findPriceInput(); const qtyInput = findQtyInput(); - if (!priceInput || !qtyInput) throw new Error("未找到价格或数量输入框"); + if (!priceInput) throw new Error("未找到价格输入框"); + if (!qtyInput) throw new Error("未找到数量输入框"); let done = 0; let repriceAttempts = 0; let lastRepriceApiErrorCode = null; @@ -4549,7 +4587,7 @@ const order = plan.orders[done]; try { assertLadderExecutionContext(plan); - if (!await ensurePostOnlyOrderType()) throw new Error("执行中只做Maker (Post Only) 状态丢失,请刷新页面后重试"); + if (!await ensurePostOnlyOrderType()) throw new Error("执行中只做 Maker 已失效,请刷新页面后重试"); throwIfAborted(abortSignal); assertLadderExecutionContext(plan); assertLadderMakerPrice(plan, order.price); @@ -4559,7 +4597,8 @@ assertLadderMakerPrice(plan, order.price); const currentPriceInput = findPriceInput(); const currentQtyInput = findQtyInput(); - if (!currentPriceInput || !currentQtyInput) throw new Error("执行中价格或数量输入框丢失"); + if (!currentPriceInput) throw new Error("执行中价格输入框已消失,已停止"); + if (!currentQtyInput) throw new Error("执行中数量输入框已消失,已停止"); const synchronizedInputs = await syncTradeInputs(order.price, order.qty, { priceLabel: "计划价", qtyLabel: "计划量", @@ -4655,8 +4694,8 @@ } if (cancelCurrentSymbolOpenOrdersTask) { setStartStatus( - `${spec.label}尚未开始:撤本币挂单处理中`, - "尚未开始 · 撤本币挂单处理中" + `${spec.label}尚未开始:撤单处理中`, + "尚未开始 · 撤单处理中" ); return { status: "not_started" }; } @@ -5192,7 +5231,7 @@ let lastStatus = currentRoot ? "symbol_filter_not_confirmed" : "scope_not_found"; const observationRoot = getAccountOrdersObservationRoot() || document.body; const mutationSignal = createAccountOrdersMutationSignal(observationRoot); - if (!mutationSignal) throw new Error("Account-orders observer root is unavailable"); + if (!mutationSignal) throw new Error("当前委托状态无法观察,已停止"); try { while (true) { const observedVersion = mutationSignal.version; @@ -5427,7 +5466,7 @@ if (!accountSignal || !dialogSignal) { accountSignal?.dispose(); dialogSignal?.dispose(); - throw new Error("Row-cancellation observer root is unavailable"); + throw new Error("撤单结果无法观察,已停止"); } try { const observedAccountVersion = accountSignal.version; @@ -5456,7 +5495,7 @@ const deadline = Date.now() + LADDER_REPLACE_ROW_SETTLE_MS; const observationRoot = getAccountOrdersObservationRoot() || document.body; const mutationSignal = createAccountOrdersMutationSignal(observationRoot); - if (!mutationSignal) throw new Error("Account-orders observer root is unavailable"); + if (!mutationSignal) throw new Error("当前委托状态无法观察,已停止"); try { while (true) { throwIfAborted(abortSignal); @@ -5481,7 +5520,7 @@ const deadline = Date.now() + LADDER_REPLACE_OPEN_ORDERS_CLEAR_TIMEOUT_MS; const observationRoot = getAccountOrdersObservationRoot() || document.body; const mutationSignal = createAccountOrdersMutationSignal(observationRoot); - if (!mutationSignal) throw new Error("Account-orders observer root is unavailable"); + if (!mutationSignal) throw new Error("当前委托状态无法观察,已停止"); try { while (true) { throwIfAborted(abortSignal); @@ -5509,7 +5548,7 @@ let currentRoot = root; while (compareDecimalStrings(cancelQty, plan.totalQty) < 0) { throwIfAborted(abortSignal); - if (!isCurrentObservedSymbol(plan.symbol)) throw new Error("逐行撤单前交易对已变化"); + if (!isCurrentObservedSymbol(plan.symbol)) throw new Error("撤销待替换挂单前交易对已变化"); const remainingQty = subtractDecimalStrings(plan.totalQty, cancelQty); const refreshedRoot = getActiveOpenOrdersScope2(); if (refreshedRoot) currentRoot = refreshedRoot; @@ -5520,18 +5559,18 @@ { allowPartial: true } )[0]; if (!row) { - throw new Error("当前币可撤挂单数量不足,已停止重挂"); + throw new Error("同向可撤挂单数量不足,已停止重新挂单"); } currentRoot = row.root || currentRoot; const previousKeyCount = countOpenOrderRowsByKey(row.root, plan.symbol, row.key); if (!row.cancelButton?.isConnected || !isVisibleElement(row.cancelButton)) { if (previousKeyCount === 0) continue; - throw new Error("当前币挂单撤单入口已失效,已停止重挂"); + throw new Error("待替换挂单的撤单按钮已失效,已停止重新挂单"); } const dialogsBefore = new Set(getVisibleDialogs()); throwIfAborted(abortSignal); if (!clickDomTarget(row.cancelButton)) { - throw new Error("当前币挂单撤单入口点击失败,已停止重挂"); + throw new Error("待替换挂单的撤单按钮点击失败,已停止重新挂单"); } waitForTradeUiMutation({ timeoutMs: 800 }); const outcome = await waitForOpenOrderRowCancellationOutcome( @@ -5542,10 +5581,10 @@ abortSignal ); if (outcome.status === "symbol_changed") { - throw new Error("逐行撤单时交易对已变化"); + throw new Error("撤销待替换挂单时交易对已变化"); } if (outcome.status === "timeout") { - throw new Error("当前币挂单仍存在,已停止重挂"); + throw new Error("待替换挂单仍存在,已停止重新挂单"); } if (outcome.status === "dialog_open") { const { dialog } = outcome; @@ -5569,7 +5608,7 @@ previousKeyCount, abortSignal )) { - throw new Error("当前币挂单仍存在,已停止重挂"); + throw new Error("待替换挂单仍存在,已停止重新挂单"); } } throwIfAborted(abortSignal); @@ -5579,7 +5618,7 @@ previousKeyCount, abortSignal )) { - throw new Error("当前币挂单状态不稳定,已停止重挂"); + throw new Error("待替换挂单状态未稳定,已停止重新挂单"); } cancelQty = addDecimalStrings(cancelQty, row.qty); recordLadderCancelledOrder(progress); @@ -5674,7 +5713,7 @@ function createBinanceCancelAllDialogDecisionWatcher() { const lifecycleController = new AbortController(); const dialogSignal = createDialogMutationSignal(document); - if (!dialogSignal) throw new Error("Cancel-all dialog observer root is unavailable"); + if (!dialogSignal) throw new Error("撤单确认弹窗状态无法观察,已停止"); const watcher = { action: null, error: null, @@ -5818,7 +5857,7 @@ if (current && (expectedChecked === null || current.checked === expectedChecked)) { return current; } - throw new Error(expectedChecked === null ? "Binance chart OpenOrders popover did not open" : `Binance chart OpenOrders state did not become ${expectedChecked}`); + throw new Error(expectedChecked === null ? "图表“显示当前委托”菜单未打开" : `图表“显示当前委托”未切换为${expectedChecked ? "显示" : "隐藏"}`); } async function openBinanceChartOrdersPopover(target) { const currentTarget = getBinanceChartOrdersTarget2(); @@ -5867,12 +5906,12 @@ currentTarget, isVisibleElement )) { - throw new Error("Binance chart OpenOrders popover did not close"); + throw new Error("图表“显示当前委托”菜单未关闭"); } } async function toggleBinanceChartOrdersWithCoalescedSave(target, checkbox, expectedChecked, expectDrawingEvents) { if (typeof expectDrawingEvents !== "boolean") { - throw new Error("Chart orders drawing-event expectation is invalid"); + throw new Error("图表委托线保存参数异常"); } let popoverCloseOutcomePromise = null; const coalescingOutcome = await coalesceTradingViewDrawingSaves( @@ -5918,7 +5957,7 @@ } async function restoreBinanceChartOrdersAfterBulkCancel(target, state, expectDrawingEvents) { if (typeof expectDrawingEvents !== "boolean") { - throw new Error("Chart orders drawing-event expectation is invalid"); + throw new Error("图表委托线保存参数异常"); } assertSameBinanceChartOrdersTarget(target, getBinanceChartOrdersTarget2()); const current = await openBinanceChartOrdersPopover(target); @@ -5984,7 +6023,7 @@ return { ok: false, status: "symbol_changing", message }; } if (getOpenOrdersTabCount() === 0) { - setLadderStatus("当前币无挂单"); + setLadderStatus("当前交易对无挂单"); return { ok: true, status: "no_orders" }; } const previousAccountOrdersTabIdentity = getAccountOrdersTabIdentity2(findSelectedAccountOrdersTab2()); @@ -5999,56 +6038,61 @@ let successStatusMessage = null; try { const tabReady = await activateOpenOrdersTab(); - if (!tabReady || !isCurrentObservedSymbol(symbol)) { - const message = "当前委托页未就绪或交易对已变化"; + if (!isCurrentObservedSymbol(symbol)) { + const message = "打开当前委托时交易对已变化"; + setLadderStatus(message); + return { ok: false, status: "symbol_changed", message }; + } + if (!tabReady) { + const message = "未能打开当前委托"; setLadderStatus(message); return { ok: false, status: "tab_not_ready", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setLadderStatus(message); return { ok: false, status: "scope_not_found", message }; } const basicSubTabState = await activateOpenOrdersBasicSubTab(openOrdersScope); previousOpenOrdersSubTabIdentity = basicSubTabState.previousSubTabIdentity; if (!basicSubTabState.ready || !isCurrentObservedSymbol(symbol)) { - const message = "未定位到当前委托基础单"; + const message = "未找到当前委托基础单"; setLadderStatus(message); return { ok: false, status: "basic_tab_not_ready", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setLadderStatus(message); return { ok: false, status: "scope_not_found", message }; } const symbolFilter = await ensureOpenOrdersLimitedToCurrentSymbol(openOrdersScope, symbol); symbolFilterOriginalChecked = symbolFilter.originalChecked; if (!symbolFilter.ok || !isCurrentObservedSymbol(symbol)) { - const message = "未确认只显示当前币挂单"; + const message = "未确认仅显示当前交易对挂单"; setLadderStatus(message); return { ok: false, status: "symbol_filter_not_confirmed", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setLadderStatus(message); return { ok: false, status: "scope_not_found", message }; } const openOrdersEvidence = await waitForCurrentSymbolOpenOrders(symbol); if (!isCurrentObservedSymbol(symbol)) { - const message = "读取当前币挂单时交易对已变化"; + const message = "读取挂单时交易对已变化"; setLadderStatus(message); return { ok: false, status: "symbol_changed", message }; } if (!openOrdersEvidence.hasOrders) { - setLadderStatus("当前币无挂单"); + setLadderStatus("当前交易对无挂单"); return { ok: true, status: "no_orders" }; } let cancelAllButton = openOrdersEvidence.cancelAllButton; if (!cancelAllButton) { - const message = "未找到当前委托全撤按钮"; + const message = "未找到当前委托的全撤按钮"; setLadderStatus(message); return { ok: false, status: "cancel_button_not_found", message }; } @@ -6064,29 +6108,29 @@ await hideBinanceChartOrdersForBulkCancel(chartOrdersTarget, chartOrdersState); } catch (e) { emit("ERR", "撤单前隐藏图表当前委托失败", e); - const message = "未能确认图表当前委托已隐藏,未打开撤单确认框"; + const message = "未能准备撤单页面,未打开确认弹窗"; setLadderStatus(message); return { ok: false, status: "chart_orders_not_hidden", message }; } if (!isCurrentObservedSymbol(symbol)) { - const message = "隐藏图表当前委托后交易对已变化"; + const message = "准备撤单时交易对已变化"; setLadderStatus(message); return { ok: false, status: "symbol_changed", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = "隐藏图表当前委托后,未定位到当前委托面板"; + const message = "准备撤单时未找到当前委托面板"; setLadderStatus(message); return { ok: false, status: "scope_not_found", message }; } if (!isOpenOrdersScopeConfirmedForSymbol(openOrdersScope, symbol)) { - const message = "隐藏图表当前委托后,未确认只显示当前币挂单"; + const message = "准备撤单时未确认仅显示当前交易对挂单"; setLadderStatus(message); return { ok: false, status: "symbol_filter_not_confirmed", message }; } cancelAllButton = findCurrentSymbolCancelAllButton(openOrdersScope); if (!cancelAllButton) { - const message = "隐藏图表当前委托后,未找到当前委托全撤按钮"; + const message = "准备撤单时未找到全撤按钮"; setLadderStatus(message); return { ok: false, status: "cancel_button_not_found", message }; } @@ -6132,10 +6176,10 @@ return { ok: false, status: "cancelled", message }; } waitForTradeUiMutation({ timeoutMs: 800 }); - setLadderStatus("已确认撤单,等待当前币挂单清空"); + setLadderStatus("撤单已确认,等待挂单清空"); openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setLadderStatus(message); return { ok: false, status: "scope_not_found", message }; } @@ -6148,21 +6192,21 @@ return { ok: false, status: "symbol_changed", message: message2 }; } if (clearResult.status === "scope_not_found") { - const message2 = "等待撤单完成时未定位到当前委托面板"; + const message2 = "等待撤单完成时未找到当前委托面板"; setLadderStatus(message2); return { ok: false, status: "scope_not_found", message: message2 }; } if (clearResult.status === "symbol_filter_not_confirmed") { - const message2 = "等待撤单完成时未确认只显示当前币挂单"; + const message2 = "等待撤单完成时未确认仅显示当前交易对挂单"; setLadderStatus(message2); return { ok: false, status: "symbol_filter_not_confirmed", message: message2 }; } - const message = waitUntilCleared ? "当前币挂单仍存在,已停止重挂" : "当前币挂单仍存在,撤单流程未完成"; + const message = waitUntilCleared ? "当前交易对挂单仍存在,已停止重新挂单" : "当前交易对挂单仍存在,撤单未完成"; setLadderStatus(message); return { ok: false, status: "not_cleared", message }; } chartOrdersDefinitivelyCleared = clearResult.definitivelyCleared === true; - successStatusMessage = waitUntilCleared ? "当前币挂单已撤,继续重挂" : "撤单流程结束,已恢复筛选状态"; + successStatusMessage = waitUntilCleared ? "原挂单已撤,继续阶梯挂单" : "撤单已完成"; return { ok: true, status: "cleared" }; } finally { let temporaryUiRestoreSucceeded = true; @@ -6256,7 +6300,7 @@ }; const symbol = getCurrentSymbol(); if (!isCurrentObservedSymbol(symbol) || symbol !== plan?.symbol) { - const message = "逐行撤单前交易对已变化"; + const message = "撤销待替换挂单前交易对已变化"; setPlanStepStatus(message); return { ok: false, status: "symbol_changed", message }; } @@ -6269,15 +6313,20 @@ setPlanStepStatus("查找当前委托"); const tabReady = await activateOpenOrdersTab(abortSignal); throwIfAborted(abortSignal); - if (!tabReady || !isCurrentObservedSymbol(symbol)) { - const message = "当前委托页未就绪或交易对已变化"; + if (!isCurrentObservedSymbol(symbol)) { + const message = "打开当前委托时交易对已变化"; + setPlanStepStatus(message); + return { ok: false, status: "symbol_changed", message }; + } + if (!tabReady) { + const message = "未能打开当前委托"; setPlanStepStatus(message); return { ok: false, status: "tab_not_ready", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(abortSignal); throwIfAborted(abortSignal); if (!openOrdersScope) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setPlanStepStatus(message); return { ok: false, status: "scope_not_found", message }; } @@ -6290,14 +6339,14 @@ ); throwIfAborted(abortSignal); if (!basicSubTabState.ready) { - const message = "未定位到当前委托基础单"; + const message = "未找到当前委托基础单"; setPlanStepStatus(message); return { ok: false, status: "basic_tab_not_ready", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(abortSignal); throwIfAborted(abortSignal); if (!openOrdersScope) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setPlanStepStatus(message); return { ok: false, status: "scope_not_found", message }; } @@ -6311,14 +6360,14 @@ ); throwIfAborted(abortSignal); if (!symbolFilter.ok) { - const message = "未确认只显示当前币挂单"; + const message = "未确认仅显示当前交易对挂单"; setPlanStepStatus(message); return { ok: false, status: "symbol_filter_not_confirmed", message }; } openOrdersScope = await waitForActiveOpenOrdersScope(abortSignal); throwIfAborted(abortSignal); if (!openOrdersScope) { - const message = "未定位到当前委托面板"; + const message = "未找到当前委托面板"; setPlanStepStatus(message); return { ok: false, status: "scope_not_found", message }; } @@ -6331,17 +6380,17 @@ throwIfAborted(abortSignal); if (!rows.length) { const directionLabel = getPlanDirectionLabel(plan); - const message = `未定位到当前币的${directionLabel || ""}可撤基础单`; + const message = `未找到${directionLabel || ""}方向的可撤基础单`; setPlanStepStatus(message); return { ok: false, status: "rows_not_found", message }; } const rowsToCancel = selectOpenOrderRowsToCancelForPlan(plan, rows); if (!rowsToCancel.length) { - const message = "未选中当前币待撤挂单"; + const message = "未选中待替换挂单"; setPlanStepStatus(message); return { ok: false, status: "rows_not_selected", message }; } - setPlanStepStatus(`撤销 ${rowsToCancel.length} 笔当前币挂单`); + setPlanStepStatus(`撤销 ${rowsToCancel.length} 笔同向挂单`); await cancelOpenOrderRowsForPlan( openOrdersScope, plan, @@ -6350,12 +6399,12 @@ abortSignal ); throwIfAborted(abortSignal); - setPlanStepStatus("当前币挂单已替换,继续重挂"); + setPlanStepStatus("原挂单已撤,继续阶梯挂单"); return { ok: true, status: "rows_cleared" }; } catch (e) { if (isLadderStoppedError(e)) throw e; if (e?.name === "DialogNotClosedError") restoreTemporaryUiState = false; - const message = e?.message || "当前币挂单逐行撤销失败,已停止重挂"; + const message = e?.message || "待替换挂单撤销失败,已停止重新挂单"; setPlanStepStatus(message); const status = e?.name === "DialogNotClosedError" ? "dialog_not_closed" : "row_cancel_failed"; return { ok: false, status, message }; @@ -6404,9 +6453,9 @@ } function formatOpenOrdersReplacementDetail(plan) { if (plan?.spec?.mode === "OPEN") { - return "同向开仓挂单可能占用可开数量,准备替换"; + return "同向开仓挂单可能占用可开数量,准备撤销后重新挂单"; } - return "当前挂单占用可平数量,准备替换"; + return "同向平仓挂单占用可平数量,准备撤销后重新挂单"; } function formatOpenOrdersReplacementStatus(plan) { return `${plan.spec.label}:${formatOpenOrdersReplacementDetail(plan)}`; @@ -6451,7 +6500,7 @@ abortSignal ); throwIfAborted(abortSignal); - if (!result?.ok) throw new Error(result.message || "当前币挂单未替换,已停止重挂"); + if (!result?.ok) throw new Error(result.message || "原挂单未完成替换,已停止重新挂单"); } } throw new Error("阶梯重挂流程异常"); @@ -6576,7 +6625,7 @@ function isConfirmedZeroOpenBalance(qty) { const available = readTradeAvailableBalance(getTradeMutationRoot(), { isVisibleElement }); const normalizedQty = normalizeDecimalString(String(qty ?? "")); - const normalizedBalance = normalizeDecimalString(available?.amount || ""); + const normalizedBalance = normalizeDecimalString(available?.amount ?? ""); return normalizedQty !== null && normalizedBalance !== null && compareDecimalStrings(normalizedQty, "0") === 0 && compareDecimalStrings(normalizedBalance, "0") === 0; } function loadCloseSide(symbol = getCurrentSymbol()) { @@ -6714,7 +6763,7 @@ return Boolean(cachedBncHeaders && isCurrentObservedSymbol(symbol)); } async function fetchCurrentSymbolPositionState(symbol) { - if (!cachedBncHeaders) throw new Error("bapi header 尚未缓存"); + if (!cachedBncHeaders) throw new Error("币安请求头尚未就绪"); const controller = new AbortController(); const timer = window.setTimeout(() => controller.abort(), 5e3); try { @@ -6725,7 +6774,7 @@ credentials: "include", signal: controller.signal }); - if (!resp.ok) throw new Error(`user-position HTTP ${resp.status}`); + if (!resp.ok) throw new Error(`持仓接口异常:HTTP ${resp.status}`); const payload = await resp.json(); return { ...resolveSymbolPositionStatus(payload, symbol), @@ -7312,7 +7361,7 @@ let finalText = ""; let constraintText = ""; if (!precisionReady) { - finalText = "等待订单簿缩放值"; + finalText = "等待价格精度"; } else if (!modeReady) { finalText = "等待开仓/平仓状态"; } else if (rulesPending || !effectiveMinQty) { @@ -7375,7 +7424,7 @@ hintTitle = `平仓模式:双向持仓时单击订单簿价格后将${CFG.SAFE_MODE ? "填数量" : action}`; } else { hintText = "暂无可平仓位"; - hintTitle = "平仓模式:当前币种暂无可平仓位"; + hintTitle = "平仓模式:当前交易对暂无可平仓位"; } if (hintEl.textContent !== hintText) hintEl.textContent = hintText; if (hintEl.title !== hintTitle) hintEl.title = hintTitle; @@ -7957,7 +8006,7 @@ function resolveTargetQty(tradeMode, priceOverride) { const symbol = getCurrentSymbol(); const precision = readCurrentOrderbookPrecisionValue(); - if (!precision) throw new Error("未识别订单簿缩放值"); + if (!precision) throw new Error("未识别价格精度"); const qtyRuleContext = getQtyRuleContext(symbol, tradeMode, priceOverride); if (qtyRuleContext.status !== "ready" || !qtyRuleContext.effectiveMinQty) { if (symbol && !rulesCache[symbol]) ensureRules(symbol); @@ -8052,7 +8101,7 @@ throw new Error("开仓/平仓模式已变化,已停止提交"); } if (readCurrentOrderbookPrecisionValue() !== qtyPlan.precision) { - throw new Error("订单簿缩放值已变化,已停止提交"); + throw new Error("价格精度已变化,已停止提交"); } const currentAction = resolveTradeAction(); if (!currentAction || currentAction.mode !== action.mode || currentAction.side !== action.side || !currentAction.button || currentAction.button.disabled || currentAction.button.getAttribute("aria-disabled") === "true") { diff --git a/src/binance-orderbook-trade/contracts/panel-copy.js b/src/binance-orderbook-trade/contracts/panel-copy.js index 91a8176..8d74a0e 100644 --- a/src/binance-orderbook-trade/contracts/panel-copy.js +++ b/src/binance-orderbook-trade/contracts/panel-copy.js @@ -47,7 +47,7 @@ export const PANEL_COPY = Object.freeze({ export function formatPrecisionRefreshTooltip(tradeCount) { const count = Number(tradeCount); if (!Number.isInteger(count) || count <= 1) { - throw new Error(`Invalid precision trade count: ${tradeCount}`); + throw new Error(`价格精度成交样本数无效:${tradeCount}`); } return `优先根据最新 ${count} 条成交价;价格变化不足时自动扩大范围,重新计算推荐精度。`; } diff --git a/src/binance-orderbook-trade/core/auto-open-leverage.js b/src/binance-orderbook-trade/core/auto-open-leverage.js index 83897b0..6fb0998 100644 --- a/src/binance-orderbook-trade/core/auto-open-leverage.js +++ b/src/binance-orderbook-trade/core/auto-open-leverage.js @@ -5,13 +5,13 @@ function parsePositionAmount(value) { if (typeof value === 'string' && /^-?(?:\d+\.?\d*|\.\d+)$/.test(value.trim())) { return Number(value); } - throw new Error(`invalid position amount: ${String(value)}`); + throw new Error(`持仓数量无效:${String(value)}`); } export function resolveSymbolPositionStatus(payload, symbol) { - if (payload?.success !== true) throw new Error('position response was unsuccessful'); - if (!Array.isArray(payload.data)) throw new Error('position response data must be an array'); - if (!symbol) throw new Error('position response requires a symbol'); + if (payload?.success !== true) throw new Error('持仓接口返回失败'); + if (!Array.isArray(payload.data)) throw new Error('持仓接口数据格式异常'); + if (!symbol) throw new Error('持仓接口缺少交易对'); const positions = payload.data.filter((position) => position?.symbol === symbol); const hasPosition = positions.some((position) => parsePositionAmount(position.positionAmount) !== 0); @@ -27,9 +27,9 @@ export function resolveSymbolPositionStatus(payload, symbol) { */ export function observeAutoOpenLeveragePositionState(previousState, observation) { const { symbol, status } = observation; - if (!symbol) throw new Error('auto leverage observation requires a symbol'); + if (!symbol) throw new Error('自动杠杆检查缺少交易对'); if (!POSITION_STATUSES.has(status)) { - throw new Error(`invalid auto leverage position status: ${status}`); + throw new Error(`自动杠杆持仓状态无效:${status}`); } const isSameSymbol = previousState?.symbol === symbol; diff --git a/src/binance-orderbook-trade/core/chart-orders-recovery.js b/src/binance-orderbook-trade/core/chart-orders-recovery.js index aff0528..ac4c738 100644 --- a/src/binance-orderbook-trade/core/chart-orders-recovery.js +++ b/src/binance-orderbook-trade/core/chart-orders-recovery.js @@ -2,13 +2,13 @@ export const CHART_ORDERS_RECOVERY_STORAGE_KEY = 'binance-orderbook-trade:chart-orders-recovery:v2'; export function createChartOrdersRecoveryRecord(nowMs) { - if (!Number.isFinite(nowMs)) throw new Error('Chart orders recovery timestamp is invalid'); + if (!Number.isFinite(nowMs)) throw new Error('图表委托线恢复时间无效'); return JSON.stringify({ version: 2, originalChecked: true, createdAtMs: nowMs }); } export function parseChartOrdersRecoveryRecord(rawValue, nowMs) { if (rawValue === null) return { status: 'missing', record: null }; - if (!Number.isFinite(nowMs)) throw new Error('Chart orders recovery current time is invalid'); + if (!Number.isFinite(nowMs)) throw new Error('图表委托线恢复当前时间无效'); let record; try { diff --git a/src/binance-orderbook-trade/core/chart-save-coalescer.js b/src/binance-orderbook-trade/core/chart-save-coalescer.js index 561e730..48df871 100644 --- a/src/binance-orderbook-trade/core/chart-save-coalescer.js +++ b/src/binance-orderbook-trade/core/chart-save-coalescer.js @@ -2,19 +2,19 @@ const IGNORED_DRAWING_EVENT_TYPES = new Set(['click', 'move']); function validateTradingViewApi(api) { if (!api || typeof api !== 'object') { - throw new Error('TradingView API is unavailable'); + throw new Error('图表接口不可用'); } if (typeof api.saveChart !== 'function') { - throw new Error('TradingView saveChart API is unavailable'); + throw new Error('图表保存接口不可用'); } if (typeof api.subscribe !== 'function' || typeof api.unsubscribe !== 'function') { - throw new Error('TradingView drawing-event subscription API is unavailable'); + throw new Error('图表事件接口不可用'); } } function restoreSaveChartMethod(api, wrapper, originalSaveChart, originalDescriptor) { if (api.saveChart !== wrapper) { - throw new Error('TradingView saveChart API changed during save coalescing'); + throw new Error('图表保存接口在操作期间发生变化'); } if (originalDescriptor) { Object.defineProperty(api, 'saveChart', originalDescriptor); @@ -22,7 +22,7 @@ function restoreSaveChartMethod(api, wrapper, originalSaveChart, originalDescrip delete api.saveChart; } if (api.saveChart !== originalSaveChart) { - throw new Error('TradingView saveChart API was not restored'); + throw new Error('图表保存接口未能恢复'); } } @@ -43,15 +43,15 @@ export async function coalesceTradingViewDrawingSaves( } = {}, ) { validateTradingViewApi(api); - if (typeof action !== 'function') throw new Error('Chart action is unavailable'); + if (typeof action !== 'function') throw new Error('图表操作不可用'); if (!Number.isFinite(eventDiscoveryTimeoutMs) || eventDiscoveryTimeoutMs < 0) { - throw new Error('TradingView drawing-event discovery timeout is invalid'); + throw new Error('图表事件等待时间无效'); } if (!Number.isFinite(settleQuietMs) || settleQuietMs <= 0) { - throw new Error('TradingView drawing-save quiet window is invalid'); + throw new Error('图表保存稳定等待时间无效'); } if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { - throw new Error('TradingView save coalescing timeout is invalid'); + throw new Error('图表保存超时时间无效'); } const originalSaveChart = api.saveChart; @@ -109,7 +109,7 @@ export async function coalesceTradingViewDrawingSaves( try { api.saveChart = saveChartWrapper; if (api.saveChart !== saveChartWrapper) { - throw new Error('TradingView saveChart API is not writable'); + throw new Error('图表保存接口无法临时接管'); } } catch (error) { api.unsubscribe('drawing_event', handleDrawingEvent); @@ -136,12 +136,12 @@ export async function coalesceTradingViewDrawingSaves( waitTimeout = setTimeoutFn(() => { if (saveRequestCount < drawingEventCount) { settleReject(new Error( - `Expected ${drawingEventCount} TradingView saveChart requests, received ${saveRequestCount}`, + `图表保存请求数量不一致:预期 ${drawingEventCount},实际 ${saveRequestCount}`, )); return; } settleReject(new Error( - `TradingView drawing-save burst did not settle within ${timeoutMs}ms`, + `图表保存未在 ${timeoutMs} 毫秒内完成`, )); }, timeoutMs); await burstSettled; diff --git a/src/binance-orderbook-trade/core/continuous-ladder.js b/src/binance-orderbook-trade/core/continuous-ladder.js index f6914bd..7d0795b 100644 --- a/src/binance-orderbook-trade/core/continuous-ladder.js +++ b/src/binance-orderbook-trade/core/continuous-ladder.js @@ -30,7 +30,7 @@ function assertContinuousLadderProgress(progress) { || progress.cancelledOrders < 0 || (progress.startedRounds === 0) !== (progress.lastRound === null) ) { - throw new Error('Invalid continuous ladder progress'); + throw new Error('连续阶梯进度状态无效'); } if (progress.lastRound !== null) snapshotLadderProgress(progress.lastRound); } @@ -48,13 +48,13 @@ export function createContinuousLadderProgress() { export function recordContinuousLadderRound(progress, outcome) { assertContinuousLadderProgress(progress); if (!outcome || typeof outcome !== 'object') { - throw new Error('Invalid continuous ladder round outcome'); + throw new Error('连续阶梯本轮结果无效'); } if (recordedRoundOutcomes.has(outcome)) { - throw new Error('Continuous ladder round was already recorded'); + throw new Error('连续阶梯本轮结果已记录'); } if (!['completed', 'stopped', 'failed', 'interrupted'].includes(outcome.status)) { - throw new Error('Invalid continuous ladder round outcome'); + throw new Error('连续阶梯本轮结果无效'); } const roundProgress = snapshotLadderProgress(outcome.progress); progress.startedRounds += 1; @@ -71,11 +71,11 @@ export function recordContinuousLadderRound(progress, outcome) { function buildContinuousLadderProgressParts(label, phase, progress) { if (typeof label !== 'string' || label.trim() === '') { - throw new Error('Invalid continuous ladder label'); + throw new Error('连续阶梯动作名称无效'); } const phaseText = CONTINUOUS_LADDER_PHASE_TEXT[phase]; if (!Object.hasOwn(CONTINUOUS_LADDER_PHASE_TEXT, phase)) { - throw new Error('Invalid continuous ladder phase'); + throw new Error('连续阶梯阶段无效'); } assertContinuousLadderProgress(progress); @@ -103,10 +103,10 @@ export function formatActiveContinuousLadderProgress( roundProgress, ) { if (typeof label !== 'string' || label.trim() === '') { - throw new Error('Invalid continuous ladder label'); + throw new Error('连续阶梯动作名称无效'); } if (detail !== null && (typeof detail !== 'string' || detail.trim() === '')) { - throw new Error('Invalid active continuous ladder detail'); + throw new Error('连续阶梯当前进度信息无效'); } assertContinuousLadderProgress(progress); const round = snapshotLadderProgress(roundProgress); @@ -127,7 +127,7 @@ export function formatActiveContinuousLadderProgress( export function formatContinuousLadderProgress(label, phase, progress, reason = null) { if (reason !== null && (typeof reason !== 'string' || reason.trim() === '')) { - throw new Error('Invalid continuous ladder reason'); + throw new Error('连续阶梯停止原因无效'); } const parts = buildContinuousLadderProgressParts(label, phase, progress); if (reason !== null) parts.push(reason); @@ -136,10 +136,10 @@ export function formatContinuousLadderProgress(label, phase, progress, reason = export function formatContinuousLadderWaitReason(phase, cooldownMs) { if (!Number.isFinite(cooldownMs) || cooldownMs < 0) { - throw new Error('Invalid continuous ladder cooldown'); + throw new Error('连续阶梯轮间等待时间无效'); } if (phase === 'waiting_ready') return '等待按钮恢复'; - if (phase !== 'cooldown') throw new Error('Invalid continuous ladder wait phase'); + if (phase !== 'cooldown') throw new Error('连续阶梯等待阶段无效'); const duration = cooldownMs % 1000 === 0 ? `${cooldownMs / 1000}s` : `${cooldownMs}ms`; @@ -154,7 +154,7 @@ export function formatContinuousLadderWaitProgress(label, progress, phase, coold function assertReadinessState(state) { if (!['ready', 'waiting', 'stopped'].includes(state?.status)) { - throw new Error('Invalid continuous ladder readiness state'); + throw new Error('连续阶梯按钮就绪状态无效'); } return state; } @@ -193,10 +193,10 @@ export async function waitForContinuousLadderNextRound({ readyCheckMs = CONTINUOUS_LADDER_READY_CHECK_MS, onWaitStateChange = () => {}, }) { - if (!(cooldownMs >= 0)) throw new Error('Invalid continuous ladder cooldown'); - if (!(readyCheckMs > 0)) throw new Error('Invalid continuous ladder readiness interval'); + if (!(cooldownMs >= 0)) throw new Error('连续阶梯轮间等待时间无效'); + if (!(readyCheckMs > 0)) throw new Error('连续阶梯按钮检查间隔无效'); if (typeof onWaitStateChange !== 'function') { - throw new Error('Invalid continuous ladder wait-state callback'); + throw new Error('连续阶梯等待状态回调无效'); } let waitingAlreadyReported = false; diff --git a/src/binance-orderbook-trade/core/decimal.js b/src/binance-orderbook-trade/core/decimal.js index a4dd962..4cfeeb8 100644 --- a/src/binance-orderbook-trade/core/decimal.js +++ b/src/binance-orderbook-trade/core/decimal.js @@ -5,7 +5,7 @@ function pow10(exp) { } export function parseDecimalString(value) { - const raw = String(value || '').replace(/,/g, '').trim(); + const raw = String(value ?? '').replace(/,/g, '').trim(); if (!/^\d+(\.\d+)?$/.test(raw)) return null; const [intPart, fracPart = ''] = raw.split('.'); return { diff --git a/src/binance-orderbook-trade/core/ladder-plan.js b/src/binance-orderbook-trade/core/ladder-plan.js index 4cc0768..e4b67ac 100644 --- a/src/binance-orderbook-trade/core/ladder-plan.js +++ b/src/binance-orderbook-trade/core/ladder-plan.js @@ -52,6 +52,24 @@ export function getLadderPercentForMode(mode, openPercent, closePercent) { return null; } +export function getUnavailableLadderQuantityMessage( + mode, + quantity, + confirmedZeroOpenBalance = false, +) { + if (mode !== 'OPEN' && mode !== 'CLOSE') { + throw new Error(`未知阶梯数量模式:${mode}`); + } + const parsed = parseDecimalString(quantity); + if (parsed?.digits > 0n) return null; + if (mode === 'OPEN') { + if (!parsed) return '未读取到可开数量'; + return confirmedZeroOpenBalance ? '可用余额不足' : '当前可开数量为 0'; + } + if (!parsed) return '未读取到可平数量'; + return '当前方向没有可平仓位'; +} + function pow10(exp) { let result = 1n; for (let i = 0; i < exp; i += 1) result *= 10n; diff --git a/src/binance-orderbook-trade/core/ladder-progress.js b/src/binance-orderbook-trade/core/ladder-progress.js index 12f906b..dfd9e3d 100644 --- a/src/binance-orderbook-trade/core/ladder-progress.js +++ b/src/binance-orderbook-trade/core/ladder-progress.js @@ -17,19 +17,19 @@ function assertLadderProgress(progress) { ) ) ) { - throw new Error('Invalid ladder progress'); + throw new Error('阶梯进度状态无效'); } } function assertLadderLabel(label) { if (typeof label !== 'string' || label.trim() === '') { - throw new Error('Invalid ladder label'); + throw new Error('阶梯动作名称无效'); } } function assertLadderMessage(message) { if (typeof message !== 'string' || message.trim() === '') { - throw new Error('Invalid ladder progress message'); + throw new Error('阶梯进度信息无效'); } } @@ -75,7 +75,7 @@ export function snapshotLadderProgress(progress) { export function setLadderPlannedOrders(progress, plannedOrders) { assertLadderProgress(progress); if (!Number.isInteger(plannedOrders) || plannedOrders <= 0) { - throw new Error('Invalid ladder planned orders'); + throw new Error('阶梯计划笔数无效'); } progress.plannedOrders = plannedOrders; progress.currentPlanSubmittedOrders = 0; @@ -87,7 +87,7 @@ export function recordLadderSubmittedOrder(progress) { progress.plannedOrders !== null && progress.currentPlanSubmittedOrders >= progress.plannedOrders ) { - throw new Error('Ladder submitted orders exceed plan'); + throw new Error('阶梯已挂笔数超过计划'); } progress.submittedOrders += 1; if (progress.plannedOrders !== null) progress.currentPlanSubmittedOrders += 1; @@ -126,16 +126,16 @@ export function formatCompletedLadderProgress(label, completedOrders, totalOrder || !Number.isInteger(totalOrders) || totalOrders < 0 ) { - throw new Error('Invalid completed ladder total'); + throw new Error('阶梯完成笔数无效'); } if (completedOrders !== totalOrders) { - throw new Error('Completed ladder progress mismatch'); + throw new Error('阶梯完成进度与计划不一致'); } if ( progress.plannedOrders !== totalOrders || progress.currentPlanSubmittedOrders !== completedOrders ) { - throw new Error('Completed ladder progress mismatch'); + throw new Error('阶梯完成进度与计划不一致'); } const cancelledText = progress.cancelledOrders > 0 ? ` · 已撤 ${progress.cancelledOrders} 笔` diff --git a/src/binance-orderbook-trade/core/orderbook.js b/src/binance-orderbook-trade/core/orderbook.js index e6a3058..99b32b6 100644 --- a/src/binance-orderbook-trade/core/orderbook.js +++ b/src/binance-orderbook-trade/core/orderbook.js @@ -58,14 +58,14 @@ export function planBufferedMakerPrices({ export function repriceRemainingLadderOrders({ orders, completedCount, prices }) { if (!Array.isArray(orders) || !Number.isInteger(completedCount) || completedCount < 0 || completedCount > orders.length) { - throw new Error('Invalid completed ladder count'); + throw new Error('已完成阶梯订单数无效'); } const remainingCount = orders.length - completedCount; if (!Array.isArray(prices) || prices.length !== remainingCount) { - throw new Error(`Expected ${remainingCount} replacement prices`); + throw new Error(`重定价数量不一致:预期 ${remainingCount} 个价格`); } if (prices.some((price) => !isPositiveDecimalString(normalizeDecimalString(price)))) { - throw new Error('Invalid replacement ladder price'); + throw new Error('阶梯重定价价格无效'); } return orders.map((order, index) => ( index < completedCount diff --git a/src/binance-orderbook-trade/core/panel-options.js b/src/binance-orderbook-trade/core/panel-options.js index 012f304..afe85a4 100644 --- a/src/binance-orderbook-trade/core/panel-options.js +++ b/src/binance-orderbook-trade/core/panel-options.js @@ -34,10 +34,10 @@ function normalizePrecisionScopeValue(precision) { export function modeSymbolPrecisionOptionStorageKey(modeKeys, mode, symbol, precision) { if (mode !== 'OPEN' && mode !== 'CLOSE') { - throw new Error(`Unknown trade mode: ${mode}`); + throw new Error(`未知交易模式:${mode}`); } const baseKey = modeKeys[mode]; - if (!baseKey) throw new Error(`Missing storage key for trade mode: ${mode}`); + if (!baseKey) throw new Error(`交易模式缺少存储键:${mode}`); const normalizedSymbol = String(symbol || '').toUpperCase(); const normalizedPrecision = normalizePrecisionScopeValue(precision); return normalizedSymbol && normalizedPrecision @@ -74,7 +74,7 @@ export function migrateModeSymbolPrecisionNumberOption( ) { const numericReplacement = Number(replacementValue); if (!options.includes(numericReplacement)) { - throw new Error(`Invalid replacement option: ${replacementValue}`); + throw new Error(`替换选项无效:${replacementValue}`); } const storageKey = modeSymbolPrecisionOptionStorageKey(modeKeys, mode, symbol, precision); if (!storageKey) return false; diff --git a/src/binance-orderbook-trade/core/precision.js b/src/binance-orderbook-trade/core/precision.js index 489c41e..8b5490b 100644 --- a/src/binance-orderbook-trade/core/precision.js +++ b/src/binance-orderbook-trade/core/precision.js @@ -13,7 +13,7 @@ import { */ export function getOrderbookPrecisionDecadeTarget(options, current, direction) { if (direction !== 'DECREASE' && direction !== 'INCREASE') { - throw new Error(`Unsupported orderbook precision direction: ${direction}`); + throw new Error(`不支持的价格精度方向:${direction}`); } const normalizedCurrent = normalizeDecimalString(current); if (!normalizedCurrent || !isPositiveDecimalString(normalizedCurrent)) return null; @@ -31,7 +31,7 @@ export function getOrderbookPrecisionDecadeTarget(options, current, direction) { export function getOrderbookPrecisionShortcutOptions(options, limit = 4) { const normalizedLimit = Number(limit); if (!Number.isInteger(normalizedLimit) || normalizedLimit < 1) { - throw new Error(`Invalid orderbook precision shortcut limit: ${limit}`); + throw new Error(`价格精度快捷项数量无效:${limit}`); } return Array.from(new Set(sortedPositiveDecimals(options))).slice(0, normalizedLimit); } @@ -39,12 +39,12 @@ export function getOrderbookPrecisionShortcutOptions(options, limit = 4) { export function formatOrderbookPrecisionShortcutLabel(value) { const normalized = normalizeDecimalString(value); if (!normalized || !isPositiveDecimalString(normalized)) { - throw new Error(`Invalid orderbook precision shortcut value: ${value}`); + throw new Error(`价格精度快捷值无效:${value}`); } if (normalized.length <= 5) return normalized; const numeric = Number(normalized); if (!Number.isFinite(numeric)) { - throw new Error(`Orderbook precision shortcut value is not finite: ${value}`); + throw new Error(`价格精度快捷值不是有限数值:${value}`); } return numeric.toExponential().replace('e+', 'e'); } @@ -135,16 +135,16 @@ export function recommendOrderbookPrecisionWithExpandingWindow({ minBucketShare = 0.25, }) { if (!Array.isArray(prices)) { - throw new Error('Precision trade prices must be an array'); + throw new Error('价格精度成交价样本必须为数组'); } if (!Number.isInteger(initialLimit) || initialLimit < 2) { - throw new Error(`Invalid initial precision trade limit: ${initialLimit}`); + throw new Error(`价格精度初始样本数无效:${initialLimit}`); } if (!Number.isInteger(expansionStep) || expansionStep < 1) { - throw new Error(`Invalid precision trade expansion step: ${expansionStep}`); + throw new Error(`价格精度样本扩展步长无效:${expansionStep}`); } if (!Number.isInteger(minSamples) || minSamples < 1) { - throw new Error(`Invalid minimum precision sample count: ${minSamples}`); + throw new Error(`价格精度最小样本数无效:${minSamples}`); } let usedCount = Math.min(initialLimit, prices.length); diff --git a/src/binance-orderbook-trade/core/status-symbol.js b/src/binance-orderbook-trade/core/status-symbol.js index ca3aab9..5f979d1 100644 --- a/src/binance-orderbook-trade/core/status-symbol.js +++ b/src/binance-orderbook-trade/core/status-symbol.js @@ -2,13 +2,13 @@ const STATUS_QUOTE_ASSETS = Object.freeze(['USDT', 'USDC']); export function formatStatusBaseAsset(symbol) { if (typeof symbol !== 'string') { - throw new Error(`Unsupported futures status symbol: ${symbol}`); + throw new Error(`不支持的合约状态交易对:${symbol}`); } const normalized = symbol.trim().toUpperCase(); const quoteAsset = STATUS_QUOTE_ASSETS.find((candidate) => normalized.endsWith(candidate)); const baseAsset = quoteAsset ? normalized.slice(0, -quoteAsset.length) : ''; if (!baseAsset) { - throw new Error(`Unsupported futures status symbol: ${symbol}`); + throw new Error(`不支持的合约状态交易对:${symbol}`); } return baseAsset; } diff --git a/src/binance-orderbook-trade/dom/cancel-all-dialog.js b/src/binance-orderbook-trade/dom/cancel-all-dialog.js index becc682..bb7a9df 100644 --- a/src/binance-orderbook-trade/dom/cancel-all-dialog.js +++ b/src/binance-orderbook-trade/dom/cancel-all-dialog.js @@ -32,11 +32,11 @@ function getDialogContract(dialog, isVisibleElement) { const buttons = Array.from(dialog.querySelectorAll('button')).filter(isVisibleElement); if (!buttons.length) return null; if (buttons.length !== 2) { - throw new Error(`Expected two Binance cancel-all dialog buttons, found ${buttons.length}`); + throw new Error(`撤单确认弹窗按钮数量异常:${buttons.length}`); } const primaryButtons = buttons.filter((button) => button.matches(PRIMARY_BUTTON_SELECTOR)); if (primaryButtons.length !== 1) { - throw new Error(`Expected one Binance cancel-all primary button, found ${primaryButtons.length}`); + throw new Error(`撤单确认按钮数量异常:${primaryButtons.length}`); } const confirmButton = primaryButtons[0]; const cancelButton = buttons.find((button) => button !== confirmButton); @@ -58,7 +58,7 @@ export function findBinanceCancelAllDialog(document, isVisibleElement) { if (!existing) actionPairs.push(contract); } if (actionPairs.length !== 1) { - throw new Error(`Expected one Binance cancel-all dialog action pair, found ${actionPairs.length}`); + throw new Error(`撤单确认弹窗操作区域数量异常:${actionPairs.length}`); } return contracts.reduce((innermost, contract) => ( diff --git a/src/binance-orderbook-trade/dom/chart-orders.js b/src/binance-orderbook-trade/dom/chart-orders.js index 4c6c2ec..c8afe1e 100644 --- a/src/binance-orderbook-trade/dom/chart-orders.js +++ b/src/binance-orderbook-trade/dom/chart-orders.js @@ -36,7 +36,7 @@ export function findBinanceChartOrdersTarget(document) { }); if (!toolbars.length) return null; if (toolbars.length > 1) { - throw new Error(`Expected one Binance chart toolbar, found ${toolbars.length}`); + throw new Error(`图表工具栏数量异常:${toolbars.length}`); } const toolbar = toolbars[0]; @@ -45,12 +45,10 @@ export function findBinanceChartOrdersTarget(document) { trigger.querySelectorAll('.bn-tooltips-ele[aria-describedby]'), ); if (popoverReferences.length !== 1) { - throw new Error( - `Expected one Binance chart orders popover reference, found ${popoverReferences.length}`, - ); + throw new Error(`图表“显示当前委托”菜单入口数量异常:${popoverReferences.length}`); } const popoverId = popoverReferences[0].getAttribute('aria-describedby'); - if (!popoverId) throw new Error('Binance chart orders popover id is unavailable'); + if (!popoverId) throw new Error('图表“显示当前委托”菜单标识缺失'); return { chartRoot, @@ -63,13 +61,13 @@ export function findBinanceChartOrdersTarget(document) { export function getBinanceChartOrdersTarget(document) { const target = findBinanceChartOrdersTarget(document); - if (!target) throw new Error('Binance chart orders target is unavailable'); + if (!target) throw new Error('未找到图表“显示当前委托”控件'); return target; } export function assertSameBinanceChartOrdersTarget(capturedTarget, currentTarget) { if (!capturedTarget || !currentTarget) { - throw new Error('Binance chart orders target is unavailable'); + throw new Error('未找到图表“显示当前委托”控件'); } if ( capturedTarget.chartRoot !== currentTarget.chartRoot @@ -78,7 +76,7 @@ export function assertSameBinanceChartOrdersTarget(capturedTarget, currentTarget || capturedTarget.trigger !== currentTarget.trigger || capturedTarget.popoverId !== currentTarget.popoverId ) { - throw new Error('Binance chart orders target changed'); + throw new Error('图表“显示当前委托”控件已变化'); } } @@ -87,7 +85,7 @@ export function findActiveBinanceChartOrdersPopover( target, isVisibleElement, ) { - if (!target?.popoverId) throw new Error('Binance chart orders target is unavailable'); + if (!target?.popoverId) throw new Error('未找到图表“显示当前委托”控件'); const popover = document.getElementById(target.popoverId); if ( !popover @@ -102,13 +100,13 @@ export function findActiveBinanceChartOrdersPopover( .filter((checkbox) => OPEN_ORDERS_LABEL_PATTERN.test(normalizeLabel(checkbox.textContent))); if (!checkboxes.length) return null; if (checkboxes.length > 1) { - throw new Error(`Expected one Binance chart OpenOrders checkbox, found ${checkboxes.length}`); + throw new Error(`图表“显示当前委托”选项数量异常:${checkboxes.length}`); } const checkbox = checkboxes[0]; const checkedValue = checkbox.getAttribute('aria-checked'); if (checkedValue !== 'true' && checkedValue !== 'false') { - throw new Error(`Binance chart OpenOrders state is ${checkedValue}`); + throw new Error(`图表“显示当前委托”状态异常:${checkedValue}`); } return { popover, checkbox, checked: checkedValue === 'true' }; } diff --git a/src/binance-orderbook-trade/dom/trade-form.js b/src/binance-orderbook-trade/dom/trade-form.js index 90728a6..55a9f0d 100644 --- a/src/binance-orderbook-trade/dom/trade-form.js +++ b/src/binance-orderbook-trade/dom/trade-form.js @@ -127,10 +127,10 @@ export function findActiveTradeInputs(ownerDocument, { export function createBoundedInputWriter({ writeValue, maxWriteAttempts }) { if (typeof writeValue !== 'function') { - throw new Error('Bounded input writer dependency is invalid'); + throw new Error('输入框写入依赖异常'); } if (!Number.isInteger(maxWriteAttempts) || maxWriteAttempts < 1) { - throw new Error('Trade input write attempts must be a positive integer'); + throw new Error('输入框写入次数必须为正整数'); } const attemptsByInput = new WeakMap(); @@ -151,7 +151,7 @@ export function isScriptOwnedTradeInputRecoveryState({ compareValues, }) { if (typeof compareValues !== 'function') { - throw new Error('Trade input recovery comparison dependency is invalid'); + throw new Error('输入框恢复校验依赖异常'); } const isScriptOwnedOrEmpty = (value) => ( @@ -207,28 +207,28 @@ export function createTradeInputStateReader({ || typeof isRecoveryWriteAllowed !== 'function' || typeof readNowMs !== 'function' ) { - throw new Error('Trade input synchronizer dependencies are invalid'); + throw new Error('交易输入框同步依赖异常'); } if ( !Number.isInteger(requiredStableMismatchFrames) || requiredStableMismatchFrames < 1 ) { - throw new Error('Trade input rollback stability must be a positive integer'); + throw new Error('输入框回退稳定帧数必须为正整数'); } if (!Number.isInteger(requiredStableMatchFrames) || requiredStableMatchFrames < 1) { - throw new Error('Trade input accepted stability must be a positive integer'); + throw new Error('输入框写入稳定帧数必须为正整数'); } if (!Number.isFinite(requiredStableMismatchMs) || requiredStableMismatchMs < 0) { - throw new Error('Trade input rollback stability duration must be a non-negative number'); + throw new Error('输入框回退稳定时间不能为负数'); } if (!Number.isFinite(requiredStableMatchMs) || requiredStableMatchMs < 0) { - throw new Error('Trade input accepted stability duration must be a non-negative number'); + throw new Error('输入框写入稳定时间不能为负数'); } if (!Number.isInteger(maxWriteAttempts) || maxWriteAttempts < 1) { - throw new Error('Trade input write attempts must be a positive integer'); + throw new Error('输入框写入次数必须为正整数'); } if (typeof recoverProvisionalMatchRollback !== 'boolean') { - throw new Error('Provisional trade input recovery flag must be boolean'); + throw new Error('输入框临时恢复标记必须为布尔值'); } const createSyncSlot = (field) => { @@ -524,10 +524,10 @@ export function waitForTradeFormFrameState( || typeof view.requestAnimationFrame !== 'function' || typeof view.cancelAnimationFrame !== 'function' ) { - throw new Error('Trade form frame scheduler is unavailable'); + throw new Error('交易表单帧调度器不可用'); } if (!Number.isInteger(requiredStableFrames) || requiredStableFrames < 1) { - throw new Error('requiredStableFrames must be a positive integer'); + throw new Error('稳定帧数必须为正整数'); } return new Promise((resolve) => { @@ -582,13 +582,13 @@ export function waitForTradeActionButtonFrameState( || typeof view.requestAnimationFrame !== 'function' || typeof view.cancelAnimationFrame !== 'function' ) { - throw new Error('Trade action button frame scheduler is unavailable'); + throw new Error('下单按钮帧调度器不可用'); } if (typeof findButton !== 'function' || typeof isVisibleElement !== 'function') { - throw new Error('Trade action button resolver is unavailable'); + throw new Error('下单按钮定位器不可用'); } if (!Number.isInteger(requiredStableFrames) || requiredStableFrames < 1) { - throw new Error('requiredStableFrames must be a positive integer'); + throw new Error('稳定帧数必须为正整数'); } return new Promise((resolve) => { diff --git a/src/binance-orderbook-trade/dom/tradingview-target.js b/src/binance-orderbook-trade/dom/tradingview-target.js index a0ce492..cc69cbe 100644 --- a/src/binance-orderbook-trade/dom/tradingview-target.js +++ b/src/binance-orderbook-trade/dom/tradingview-target.js @@ -11,7 +11,7 @@ export function findBinanceTradingViewTarget(document) { .filter(hasVisibleBox); if (!chartRoots.length) return null; if (chartRoots.length > 1) { - throw new Error(`Expected one visible Binance chart root, found ${chartRoots.length}`); + throw new Error(`可见图表区域数量异常:${chartRoots.length}`); } const chartRoot = chartRoots[0]; @@ -20,7 +20,7 @@ export function findBinanceTradingViewTarget(document) { .filter(Boolean); if (!tradingViewApis.length) return null; if (tradingViewApis.length > 1) { - throw new Error(`Expected one Binance TradingView API, found ${tradingViewApis.length}`); + throw new Error(`图表接口数量异常:${tradingViewApis.length}`); } return { chartRoot, tradingViewApi: tradingViewApis[0] }; @@ -28,6 +28,6 @@ export function findBinanceTradingViewTarget(document) { export function getBinanceTradingViewTarget(document) { const target = findBinanceTradingViewTarget(document); - if (!target) throw new Error('Binance TradingView target is unavailable'); + if (!target) throw new Error('未找到可用图表接口'); return target; } diff --git a/src/binance-orderbook-trade/index.user.js b/src/binance-orderbook-trade/index.user.js index 58285c7..2810ad5 100644 --- a/src/binance-orderbook-trade/index.user.js +++ b/src/binance-orderbook-trade/index.user.js @@ -3,7 +3,7 @@ // @namespace binance.orderbook.trade // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 2.7.160 +// @version 2.7.161 // @author jackhai9 // @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板 // @match https://www.binance.com/*/futures/* @@ -75,6 +75,7 @@ import { fitLadderPlanForMinimumQty, getLadderActionSpec as getLadderActionSpecData, getLadderPercentForMode, + getUnavailableLadderQuantityMessage, } from './core/ladder-plan.js'; import { keepInteractionFeedbackVisible } from './core/interaction-feedback.js'; import { @@ -1112,7 +1113,7 @@ import { const capture = activeLadderSubmitCapture?.captureId === captureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error('下单响应捕获上下文丢失'); + if (!capture) throw new Error('下单结果跟踪状态异常,已停止'); const observations = capture.responseObservations.slice(); if (observations.length > 0 && timeoutMs > 0) { await Promise.race([ @@ -1127,7 +1128,7 @@ import { const capture = activeLadderSubmitCapture?.captureId === captureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error('下单响应捕获上下文丢失'); + if (!capture) throw new Error('下单结果跟踪状态异常,已停止'); return capture.apiErrors.slice(); } @@ -1135,7 +1136,7 @@ import { const capture = activeLadderSubmitCapture?.captureId === captureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error('下单响应捕获上下文丢失'); + if (!capture) throw new Error('下单结果跟踪状态异常,已停止'); return capture.apiSuccesses.slice(); } @@ -1173,7 +1174,7 @@ import { async function adjustLeverageApi(symbol, leverage) { if (!cachedBncHeaders) { - throw new Error('bapi header 尚未缓存'); + throw new Error('币安请求头尚未就绪'); } const controller = new AbortController(); const timer = window.setTimeout(() => controller.abort(), 5000); @@ -1188,7 +1189,7 @@ import { signal: controller.signal, } ); - if (!resp.ok) throw new Error(`adjustLeverage HTTP ${resp.status}`); + if (!resp.ok) throw new Error(`杠杆调整接口异常:HTTP ${resp.status}`); const data = await resp.json(); if (!data.success) throw new Error(data.message || `code=${data.code}`); return data; @@ -1286,7 +1287,7 @@ import { const key = orderbookPrecisionSamplesKey(symbol); if (!key) return []; if (!Array.isArray(samples)) { - throw new Error('Invalid latest trade movement snapshot'); + throw new Error('最新成交价样本状态异常'); } const normalizedSamples = samples .map((sample) => normalizeDecimalString(sample)) @@ -1534,7 +1535,7 @@ import { const recommended = value === recommendation; const disabledAttrs = disabled ? ' disabled aria-disabled="true"' : ''; const title = disabled - ? '缩放调整暂不可用' + ? '价格精度暂不可调整' : selected && recommended ? `当前且推荐的价格精度 ${value}` : selected @@ -1588,7 +1589,7 @@ import { function showOrderbookPrecisionRefreshFeedback(symbol, state) { if (!['success', 'retry'].includes(state)) { - throw new Error(`Invalid orderbook precision refresh feedback state: ${state}`); + throw new Error(`价格精度刷新反馈状态异常:${state}`); } if (orderbookPrecisionRefreshFeedbackTimer !== null) { window.clearTimeout(orderbookPrecisionRefreshFeedbackTimer); @@ -1707,7 +1708,7 @@ import { closed = await closeOrderbookPrecisionOptions(trigger.element, cleanupPrecision, true); } } - if (!closed) return { status: '无法关闭原生缩放下拉' }; + if (!closed) return { status: '无法关闭价格精度下拉' }; if (snapshot) return snapshot; if (Date.now() >= deadline) break; await delay(ORDERBOOK_PRECISION_READY_POLL_MS); @@ -1715,7 +1716,7 @@ import { if (!isCurrentObservedSymbol(symbol)) return null; return { status: lastPrecision - ? `未找到当前缩放 ${lastPrecision} 的原生档位` + ? `未找到当前价格精度 ${lastPrecision} 的选项` : '订单簿尚未就绪', }; } @@ -1749,7 +1750,7 @@ import { if (!isCurrentObservedSymbol(symbol)) return false; const trigger = findOrderbookPrecisionTrigger(); if (!symbol || !trigger?.element) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: '未定位到缩放下拉' }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: '未找到价格精度下拉' }; scheduleRenderPanel(); return false; } @@ -1759,7 +1760,7 @@ import { if (!isCurrentObservedSymbol(symbol) || readCurrentOrderbookPrecisionValue() !== startPrecision) return false; const values = readVisibleOrderbookPrecisionOptionValues(trigger.element); if (!options.length || !values.includes(startPrecision)) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: '读取原生缩放档位失败' }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: '读取价格精度选项失败' }; scheduleRenderPanel(); return false; } @@ -1774,7 +1775,7 @@ import { nativeOptionsStatus: null, }; if (!options.length || !shortcutOptions.includes(targetPrecision)) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: `未找到快捷缩放 ${targetPrecision}` }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: `未找到快捷精度 ${targetPrecision}` }; scheduleRenderPanel(); return false; } @@ -1787,7 +1788,7 @@ import { }); if (!selected) { if (isCurrentObservedSymbol(symbol)) { - orderbookPrecisionState = { ...orderbookPrecisionState, status: `未找到 ${targetPrecision} 档切换结果` }; + orderbookPrecisionState = { ...orderbookPrecisionState, status: `价格精度未切换到 ${targetPrecision}` }; scheduleRenderPanel(); } return false; @@ -1813,7 +1814,7 @@ import { function selectOrderbookPrecision(value) { const targetPrecision = normalizeDecimalString(value); if (!targetPrecision || !isPositiveDecimalString(targetPrecision)) { - throw new Error(`无效的订单簿缩放快捷值: ${value}`); + throw new Error(`无效的价格精度快捷值: ${value}`); } return runOrderbookPrecisionSelectionTask(() => runSelectOrderbookPrecision(targetPrecision)); } @@ -1844,7 +1845,7 @@ import { if (isCurrentObservedSymbol(symbol)) { orderbookPrecisionState = { ...orderbookPrecisionState, - nativeOptionsStatus: '读取原生缩放档位失败', + nativeOptionsStatus: '读取价格精度选项失败', }; scheduleRenderPanel(); } @@ -1867,7 +1868,7 @@ import { orderbookPrecisionState = { ...orderbookPrecisionState, symbol, - status: '未定位当前交易对', + status: '未识别当前交易对', }; scheduleRenderPanel(); return; @@ -1988,6 +1989,10 @@ import { const qty = spec.side === 'LONG' ? longQty : shortQty; return { qty, qtySource }; }; + const classifyUnavailableQuantity = (quantity) => ({ + ...quantity, + confirmedZeroOpenBalance: isConfirmedZeroOpenBalance(quantity.qty), + }); const mutationRoot = getTradeMutationRoot(); const ready = await waitForTradeFormMutationState( mutationRoot, @@ -1995,14 +2000,15 @@ import { const quantity = readQuantity(); const { qty } = quantity; if (qty != null && isPositiveDecimalString(String(qty))) { - return quantity; + return { ...quantity, confirmedZeroOpenBalance: false }; } - return isConfirmedZeroOpenBalance(qty) ? quantity : null; + const classified = classifyUnavailableQuantity(quantity); + return classified.confirmedZeroOpenBalance ? classified : null; }, LADDER_OPEN_QTY_READY_TIMEOUT_MS, ); - return ready || readQuantity(); + return ready || classifyUnavailableQuantity(readQuantity()); } function readCloseBaseQtyForLadder(spec) { @@ -2030,16 +2036,20 @@ import { throw new Error('重挂前开仓/平仓模式已变化,已停止'); } + const modeLabel = spec.mode === 'OPEN' ? '开仓' : '平仓'; const modeReady = await activateTradeMode(spec.mode); - if (!modeReady || getCurrentSymbol() !== startSymbol) throw new Error('切换开仓/平仓失败或交易对已变化'); + if (getCurrentSymbol() !== startSymbol) { + throw new Error(`切换至${modeLabel}时交易对已变化,已停止`); + } + if (!modeReady) throw new Error(`未能切换至${modeLabel}`); const startPrecision = readCurrentOrderbookPrecisionValue(); - if (!startPrecision) throw new Error('未识别订单簿缩放值'); + if (!startPrecision) throw new Error('未识别价格精度'); if (expectedContext?.precision && startPrecision !== expectedContext.precision) { - throw new Error('重挂前订单簿缩放值已变化,已停止'); + throw new Error('重挂前价格精度已变化,已停止'); } const postOnlyReady = await ensurePostOnlyOrderType(); - if (!postOnlyReady) throw new Error('请刷新页面让只做Maker (Post Only) 生效后重试,脚本不会用普通限价继续'); + if (!postOnlyReady) throw new Error('只做 Maker 未生效,请刷新页面后重试'); const levels = getLadderLevels(spec.mode, startSymbol, startPrecision); const ladderStep = getLadderStep(spec.mode, startSymbol, startPrecision); @@ -2049,17 +2059,17 @@ import { } const rules = await ensureRules(startSymbol); - if ( - !rules || - getCurrentSymbol() !== startSymbol || - readCurrentOrderbookPrecisionValue() !== startPrecision - ) { - throw new Error('交易规则未就绪或交易上下文已变化'); + if (getCurrentSymbol() !== startSymbol) { + throw new Error('读取交易规则时交易对已变化,已停止'); + } + if (readCurrentOrderbookPrecisionValue() !== startPrecision) { + throw new Error('读取交易规则时价格精度已变化,已停止'); } + if (!rules) throw new Error('交易规则尚未就绪,请稍后重试'); const ruleContext = getQtyRuleContext(startSymbol, spec.mode, prices[0]); if (ruleContext.status !== 'ready' || !ruleContext.stepSize || !ruleContext.baseMinQty) { - throw new Error('数量步进/最小量未就绪'); + throw new Error('下单数量规则尚未就绪,请稍后重试'); } const minRequiredQtyByLevel = spec.mode === 'OPEN' ? prices.map((price) => getQtyRuleContext(startSymbol, spec.mode, price).effectiveMinQty || ruleContext.baseMinQty) @@ -2071,18 +2081,26 @@ import { const base = spec.mode === 'OPEN' ? await readOpenBaseQtyForLadder(spec, prices[0]) : readCloseBaseQtyForLadder(spec); - if ( - getCurrentSymbol() !== startSymbol || - getActiveTradeMode() !== spec.mode || - readCurrentOrderbookPrecisionValue() !== startPrecision || - !isPostOnlyOrderTypeActive() - ) { - throw new Error('读取可用数量后交易上下文已变化,已停止'); + const quantityLabel = spec.mode === 'OPEN' ? '可开数量' : '可平数量'; + if (getCurrentSymbol() !== startSymbol) { + throw new Error(`读取${quantityLabel}时交易对已变化,已停止`); + } + if (getActiveTradeMode() !== spec.mode) { + throw new Error(`读取${quantityLabel}时下单模式已变化,已停止`); } - const baseQty = normalizeDecimalString(base?.qty || ''); - if (!baseQty || !isPositiveDecimalString(baseQty)) { - throw new Error(`未读取到可用${spec.mode === 'OPEN' ? '可开' : '可平'}数量`); + if (readCurrentOrderbookPrecisionValue() !== startPrecision) { + throw new Error(`读取${quantityLabel}时价格精度已变化,已停止`); } + if (!isPostOnlyOrderTypeActive()) { + throw new Error(`读取${quantityLabel}时只做 Maker 已失效,请刷新页面后重试`); + } + const baseQty = normalizeDecimalString(base?.qty ?? ''); + const unavailableQuantityMessage = getUnavailableLadderQuantityMessage( + spec.mode, + baseQty, + base?.confirmedZeroOpenBalance === true, + ); + if (unavailableQuantityMessage) throw new Error(unavailableQuantityMessage); let percent = getLadderPercentForMode( spec.mode, @@ -2169,7 +2187,7 @@ import { const maxText = maxAutoFitPercent ? `自动上限 ${maxAutoFitPercent}%。` : ''; const levelsText = levels ? `当前档位 ${levels} 档。` : ''; const replacementText = mode === 'OPEN' - ? '脚本只会尝试替换当前币同向开仓基础单,不会自动全撤。' + ? '脚本只会尝试替换当前交易对的同向开仓基础单,不会自动全撤。' : '脚本不会自动撤单。'; error.statusTitle = `当前${percentLabel} ${percent}%,目标数量小于最小下单量 ${minRequiredQty},无法阶梯${actionLabel};${levelsText}${minimumText}${maxText}已尝试自动提高比例和自动降档;${replacementText}`; if ( @@ -2264,12 +2282,12 @@ import { } function assertLadderExecutionContext(plan) { - if (!isCurrentObservedSymbol(plan.symbol)) throw new Error('执行中交易对变化,已停止'); - if (getActiveTradeMode() !== plan.spec.mode) throw new Error('执行中开仓/平仓模式变化,已停止'); + if (!isCurrentObservedSymbol(plan.symbol)) throw new Error('执行中交易对已变化,已停止'); + if (getActiveTradeMode() !== plan.spec.mode) throw new Error('执行中下单模式已变化,已停止'); if (readCurrentOrderbookPrecisionValue() !== plan.precision) { - throw new Error('执行中订单簿缩放值变化,已停止'); + throw new Error('执行中价格精度已变化,已停止'); } - if (!isPostOnlyOrderTypeActive()) throw new Error('执行中只做Maker (Post Only) 状态丢失,请刷新页面后重试'); + if (!isPostOnlyOrderTypeActive()) throw new Error('执行中只做 Maker 已失效,请刷新页面后重试'); } function assertSubmittedPriceMatchesExpectedPrice(expectedPrice, submittedPrice, expectedLabel = '点击价') { @@ -2334,7 +2352,7 @@ import { }) : setInputValueReact; const inputs = findTradeInputs(); - if (!inputs) throw new Error('未找到唯一的当前交易表单输入框'); + if (!inputs) throw new Error('未能确定唯一的当前交易表单输入框'); const observationRoot = inputs.root; const readTradeState = createTradeInputStateReader({ @@ -2371,7 +2389,7 @@ import { priceLabel, ); assertSubmittedQtyMatchesExpectedQty(expectedQty, findQtyInput()?.value || '', qtyLabel); - throw new Error('价格和数量状态未稳定,已停止提交'); + throw new Error('价格框或数量框未稳定,已停止提交'); } function isSubmitButtonBusy(button) { @@ -2462,7 +2480,7 @@ import { const capture = activeLadderSubmitCapture?.captureId === submitCaptureId ? activeLadderSubmitCapture : null; - if (!capture) throw new Error('下单响应捕获上下文丢失'); + if (!capture) throw new Error('下单结果跟踪状态异常,已停止'); return new Promise((resolve) => { let settled = false; @@ -2582,14 +2600,15 @@ import { } if (capturedApiSuccesses.length === 1) return; - const settleHint = sawBusy ? '按钮已恢复但未捕获当前下单 API 成功响应' : '未捕获当前下单 API 成功响应'; - throw new Error(`未收到明确${label}成功反馈(${settleHint}),已停止;请核对当前委托/历史成交`); + const settleHint = sawBusy ? '下单按钮已恢复,但仍未确认订单结果' : '仍未确认订单结果'; + throw new Error(`未确认${label}成功(${settleHint}),已停止;请在当前委托和历史成交中核对`); } async function executeLadderPlan(plan, progress, setExecutionStatus, abortSignal = null) { const priceInput = findPriceInput(); const qtyInput = findQtyInput(); - if (!priceInput || !qtyInput) throw new Error('未找到价格或数量输入框'); + if (!priceInput) throw new Error('未找到价格输入框'); + if (!qtyInput) throw new Error('未找到数量输入框'); let done = 0; let repriceAttempts = 0; @@ -2601,7 +2620,7 @@ import { const order = plan.orders[done]; try { assertLadderExecutionContext(plan); - if (!(await ensurePostOnlyOrderType())) throw new Error('执行中只做Maker (Post Only) 状态丢失,请刷新页面后重试'); + if (!(await ensurePostOnlyOrderType())) throw new Error('执行中只做 Maker 已失效,请刷新页面后重试'); throwIfAborted(abortSignal); assertLadderExecutionContext(plan); assertLadderMakerPrice(plan, order.price); @@ -2613,7 +2632,8 @@ import { const currentPriceInput = findPriceInput(); const currentQtyInput = findQtyInput(); - if (!currentPriceInput || !currentQtyInput) throw new Error('执行中价格或数量输入框丢失'); + if (!currentPriceInput) throw new Error('执行中价格输入框已消失,已停止'); + if (!currentQtyInput) throw new Error('执行中数量输入框已消失,已停止'); const synchronizedInputs = await syncTradeInputs(order.price, order.qty, { priceLabel: '计划价', @@ -2715,8 +2735,8 @@ import { } if (cancelCurrentSymbolOpenOrdersTask) { setStartStatus( - `${spec.label}尚未开始:撤本币挂单处理中`, - '尚未开始 · 撤本币挂单处理中', + `${spec.label}尚未开始:撤单处理中`, + '尚未开始 · 撤单处理中', ); return { status: 'not_started' }; } @@ -3339,7 +3359,7 @@ import { let lastStatus = currentRoot ? 'symbol_filter_not_confirmed' : 'scope_not_found'; const observationRoot = getAccountOrdersObservationRoot() || document.body; const mutationSignal = createAccountOrdersMutationSignal(observationRoot); - if (!mutationSignal) throw new Error('Account-orders observer root is unavailable'); + if (!mutationSignal) throw new Error('当前委托状态无法观察,已停止'); try { while (true) { @@ -3621,7 +3641,7 @@ import { if (!accountSignal || !dialogSignal) { accountSignal?.dispose(); dialogSignal?.dispose(); - throw new Error('Row-cancellation observer root is unavailable'); + throw new Error('撤单结果无法观察,已停止'); } try { @@ -3657,7 +3677,7 @@ import { const deadline = Date.now() + LADDER_REPLACE_ROW_SETTLE_MS; const observationRoot = getAccountOrdersObservationRoot() || document.body; const mutationSignal = createAccountOrdersMutationSignal(observationRoot); - if (!mutationSignal) throw new Error('Account-orders observer root is unavailable'); + if (!mutationSignal) throw new Error('当前委托状态无法观察,已停止'); try { while (true) { @@ -3689,7 +3709,7 @@ import { const deadline = Date.now() + LADDER_REPLACE_OPEN_ORDERS_CLEAR_TIMEOUT_MS; const observationRoot = getAccountOrdersObservationRoot() || document.body; const mutationSignal = createAccountOrdersMutationSignal(observationRoot); - if (!mutationSignal) throw new Error('Account-orders observer root is unavailable'); + if (!mutationSignal) throw new Error('当前委托状态无法观察,已停止'); try { while (true) { @@ -3725,7 +3745,7 @@ import { let currentRoot = root; while (compareDecimalStrings(cancelQty, plan.totalQty) < 0) { throwIfAborted(abortSignal); - if (!isCurrentObservedSymbol(plan.symbol)) throw new Error('逐行撤单前交易对已变化'); + if (!isCurrentObservedSymbol(plan.symbol)) throw new Error('撤销待替换挂单前交易对已变化'); const remainingQty = subtractDecimalStrings(plan.totalQty, cancelQty); const refreshedRoot = getActiveOpenOrdersScope(); if (refreshedRoot) currentRoot = refreshedRoot; @@ -3736,18 +3756,18 @@ import { { allowPartial: true } )[0]; if (!row) { - throw new Error('当前币可撤挂单数量不足,已停止重挂'); + throw new Error('同向可撤挂单数量不足,已停止重新挂单'); } currentRoot = row.root || currentRoot; const previousKeyCount = countOpenOrderRowsByKey(row.root, plan.symbol, row.key); if (!row.cancelButton?.isConnected || !isVisibleElement(row.cancelButton)) { if (previousKeyCount === 0) continue; - throw new Error('当前币挂单撤单入口已失效,已停止重挂'); + throw new Error('待替换挂单的撤单按钮已失效,已停止重新挂单'); } const dialogsBefore = new Set(getVisibleDialogs()); throwIfAborted(abortSignal); if (!clickDomTarget(row.cancelButton)) { - throw new Error('当前币挂单撤单入口点击失败,已停止重挂'); + throw new Error('待替换挂单的撤单按钮点击失败,已停止重新挂单'); } waitForTradeUiMutation({ timeoutMs: 800 }); const outcome = await waitForOpenOrderRowCancellationOutcome( @@ -3758,10 +3778,10 @@ import { abortSignal, ); if (outcome.status === 'symbol_changed') { - throw new Error('逐行撤单时交易对已变化'); + throw new Error('撤销待替换挂单时交易对已变化'); } if (outcome.status === 'timeout') { - throw new Error('当前币挂单仍存在,已停止重挂'); + throw new Error('待替换挂单仍存在,已停止重新挂单'); } if (outcome.status === 'dialog_open') { const { dialog } = outcome; @@ -3785,7 +3805,7 @@ import { previousKeyCount, abortSignal, ))) { - throw new Error('当前币挂单仍存在,已停止重挂'); + throw new Error('待替换挂单仍存在,已停止重新挂单'); } } throwIfAborted(abortSignal); @@ -3795,7 +3815,7 @@ import { previousKeyCount, abortSignal, ))) { - throw new Error('当前币挂单状态不稳定,已停止重挂'); + throw new Error('待替换挂单状态未稳定,已停止重新挂单'); } cancelQty = addDecimalStrings(cancelQty, row.qty); recordLadderCancelledOrder(progress); @@ -3907,7 +3927,7 @@ import { function createBinanceCancelAllDialogDecisionWatcher() { const lifecycleController = new AbortController(); const dialogSignal = createDialogMutationSignal(document); - if (!dialogSignal) throw new Error('Cancel-all dialog observer root is unavailable'); + if (!dialogSignal) throw new Error('撤单确认弹窗状态无法观察,已停止'); const watcher = { action: null, error: null, @@ -4066,8 +4086,8 @@ import { return current; } throw new Error(expectedChecked === null - ? 'Binance chart OpenOrders popover did not open' - : `Binance chart OpenOrders state did not become ${expectedChecked}`); + ? '图表“显示当前委托”菜单未打开' + : `图表“显示当前委托”未切换为${expectedChecked ? '显示' : '隐藏'}`); } async function openBinanceChartOrdersPopover(target) { @@ -4120,7 +4140,7 @@ import { currentTarget, isVisibleElement, )) { - throw new Error('Binance chart OpenOrders popover did not close'); + throw new Error('图表“显示当前委托”菜单未关闭'); } } @@ -4131,7 +4151,7 @@ import { expectDrawingEvents, ) { if (typeof expectDrawingEvents !== 'boolean') { - throw new Error('Chart orders drawing-event expectation is invalid'); + throw new Error('图表委托线保存参数异常'); } let popoverCloseOutcomePromise = null; const coalescingOutcome = await coalesceTradingViewDrawingSaves( @@ -4188,7 +4208,7 @@ import { expectDrawingEvents, ) { if (typeof expectDrawingEvents !== 'boolean') { - throw new Error('Chart orders drawing-event expectation is invalid'); + throw new Error('图表委托线保存参数异常'); } assertSameBinanceChartOrdersTarget(target, getBinanceChartOrdersTarget()); const current = await openBinanceChartOrdersPopover(target); @@ -4265,7 +4285,7 @@ import { return { ok: false, status: 'symbol_changing', message }; } if (getOpenOrdersTabCount() === 0) { - setLadderStatus('当前币无挂单'); + setLadderStatus('当前交易对无挂单'); return { ok: true, status: 'no_orders' }; } @@ -4282,57 +4302,62 @@ import { try { const tabReady = await activateOpenOrdersTab(); - if (!tabReady || !isCurrentObservedSymbol(symbol)) { - const message = '当前委托页未就绪或交易对已变化'; + if (!isCurrentObservedSymbol(symbol)) { + const message = '打开当前委托时交易对已变化'; + setLadderStatus(message); + return { ok: false, status: 'symbol_changed', message }; + } + if (!tabReady) { + const message = '未能打开当前委托'; setLadderStatus(message); return { ok: false, status: 'tab_not_ready', message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setLadderStatus(message); return { ok: false, status: 'scope_not_found', message }; } const basicSubTabState = await activateOpenOrdersBasicSubTab(openOrdersScope); previousOpenOrdersSubTabIdentity = basicSubTabState.previousSubTabIdentity; if (!basicSubTabState.ready || !isCurrentObservedSymbol(symbol)) { - const message = '未定位到当前委托基础单'; + const message = '未找到当前委托基础单'; setLadderStatus(message); return { ok: false, status: 'basic_tab_not_ready', message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setLadderStatus(message); return { ok: false, status: 'scope_not_found', message }; } const symbolFilter = await ensureOpenOrdersLimitedToCurrentSymbol(openOrdersScope, symbol); symbolFilterOriginalChecked = symbolFilter.originalChecked; if (!symbolFilter.ok || !isCurrentObservedSymbol(symbol)) { - const message = '未确认只显示当前币挂单'; + const message = '未确认仅显示当前交易对挂单'; setLadderStatus(message); return { ok: false, status: 'symbol_filter_not_confirmed', message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setLadderStatus(message); return { ok: false, status: 'scope_not_found', message }; } const openOrdersEvidence = await waitForCurrentSymbolOpenOrders(symbol); if (!isCurrentObservedSymbol(symbol)) { - const message = '读取当前币挂单时交易对已变化'; + const message = '读取挂单时交易对已变化'; setLadderStatus(message); return { ok: false, status: 'symbol_changed', message }; } if (!openOrdersEvidence.hasOrders) { - setLadderStatus('当前币无挂单'); + setLadderStatus('当前交易对无挂单'); return { ok: true, status: 'no_orders' }; } let cancelAllButton = openOrdersEvidence.cancelAllButton; if (!cancelAllButton) { - const message = '未找到当前委托全撤按钮'; + const message = '未找到当前委托的全撤按钮'; setLadderStatus(message); return { ok: false, status: 'cancel_button_not_found', message }; } @@ -4351,31 +4376,31 @@ import { await hideBinanceChartOrdersForBulkCancel(chartOrdersTarget, chartOrdersState); } catch (e) { emit('ERR', '撤单前隐藏图表当前委托失败', e); - const message = '未能确认图表当前委托已隐藏,未打开撤单确认框'; + const message = '未能准备撤单页面,未打开确认弹窗'; setLadderStatus(message); return { ok: false, status: 'chart_orders_not_hidden', message }; } if (!isCurrentObservedSymbol(symbol)) { - const message = '隐藏图表当前委托后交易对已变化'; + const message = '准备撤单时交易对已变化'; setLadderStatus(message); return { ok: false, status: 'symbol_changed', message }; } openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = '隐藏图表当前委托后,未定位到当前委托面板'; + const message = '准备撤单时未找到当前委托面板'; setLadderStatus(message); return { ok: false, status: 'scope_not_found', message }; } if (!isOpenOrdersScopeConfirmedForSymbol(openOrdersScope, symbol)) { - const message = '隐藏图表当前委托后,未确认只显示当前币挂单'; + const message = '准备撤单时未确认仅显示当前交易对挂单'; setLadderStatus(message); return { ok: false, status: 'symbol_filter_not_confirmed', message }; } cancelAllButton = findCurrentSymbolCancelAllButton(openOrdersScope); if (!cancelAllButton) { - const message = '隐藏图表当前委托后,未找到当前委托全撤按钮'; + const message = '准备撤单时未找到全撤按钮'; setLadderStatus(message); return { ok: false, status: 'cancel_button_not_found', message }; } @@ -4422,11 +4447,11 @@ import { return { ok: false, status: 'cancelled', message }; } waitForTradeUiMutation({ timeoutMs: 800 }); - setLadderStatus('已确认撤单,等待当前币挂单清空'); + setLadderStatus('撤单已确认,等待挂单清空'); openOrdersScope = await waitForActiveOpenOrdersScope(); if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setLadderStatus(message); return { ok: false, status: 'scope_not_found', message }; } @@ -4439,25 +4464,25 @@ import { return { ok: false, status: 'symbol_changed', message }; } if (clearResult.status === 'scope_not_found') { - const message = '等待撤单完成时未定位到当前委托面板'; + const message = '等待撤单完成时未找到当前委托面板'; setLadderStatus(message); return { ok: false, status: 'scope_not_found', message }; } if (clearResult.status === 'symbol_filter_not_confirmed') { - const message = '等待撤单完成时未确认只显示当前币挂单'; + const message = '等待撤单完成时未确认仅显示当前交易对挂单'; setLadderStatus(message); return { ok: false, status: 'symbol_filter_not_confirmed', message }; } const message = waitUntilCleared - ? '当前币挂单仍存在,已停止重挂' - : '当前币挂单仍存在,撤单流程未完成'; + ? '当前交易对挂单仍存在,已停止重新挂单' + : '当前交易对挂单仍存在,撤单未完成'; setLadderStatus(message); return { ok: false, status: 'not_cleared', message }; } chartOrdersDefinitivelyCleared = clearResult.definitivelyCleared === true; successStatusMessage = waitUntilCleared - ? '当前币挂单已撤,继续重挂' - : '撤单流程结束,已恢复筛选状态'; + ? '原挂单已撤,继续阶梯挂单' + : '撤单已完成'; return { ok: true, status: 'cleared' }; } finally { let temporaryUiRestoreSucceeded = true; @@ -4573,7 +4598,7 @@ import { }; const symbol = getCurrentSymbol(); if (!isCurrentObservedSymbol(symbol) || symbol !== plan?.symbol) { - const message = '逐行撤单前交易对已变化'; + const message = '撤销待替换挂单前交易对已变化'; setPlanStepStatus(message); return { ok: false, status: 'symbol_changed', message }; } @@ -4588,8 +4613,13 @@ import { setPlanStepStatus('查找当前委托'); const tabReady = await activateOpenOrdersTab(abortSignal); throwIfAborted(abortSignal); - if (!tabReady || !isCurrentObservedSymbol(symbol)) { - const message = '当前委托页未就绪或交易对已变化'; + if (!isCurrentObservedSymbol(symbol)) { + const message = '打开当前委托时交易对已变化'; + setPlanStepStatus(message); + return { ok: false, status: 'symbol_changed', message }; + } + if (!tabReady) { + const message = '未能打开当前委托'; setPlanStepStatus(message); return { ok: false, status: 'tab_not_ready', message }; } @@ -4597,7 +4627,7 @@ import { openOrdersScope = await waitForActiveOpenOrdersScope(abortSignal); throwIfAborted(abortSignal); if (!openOrdersScope) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setPlanStepStatus(message); return { ok: false, status: 'scope_not_found', message }; } @@ -4611,7 +4641,7 @@ import { ); throwIfAborted(abortSignal); if (!basicSubTabState.ready) { - const message = '未定位到当前委托基础单'; + const message = '未找到当前委托基础单'; setPlanStepStatus(message); return { ok: false, status: 'basic_tab_not_ready', message }; } @@ -4619,7 +4649,7 @@ import { openOrdersScope = await waitForActiveOpenOrdersScope(abortSignal); throwIfAborted(abortSignal); if (!openOrdersScope) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setPlanStepStatus(message); return { ok: false, status: 'scope_not_found', message }; } @@ -4634,7 +4664,7 @@ import { ); throwIfAborted(abortSignal); if (!symbolFilter.ok) { - const message = '未确认只显示当前币挂单'; + const message = '未确认仅显示当前交易对挂单'; setPlanStepStatus(message); return { ok: false, status: 'symbol_filter_not_confirmed', message }; } @@ -4642,7 +4672,7 @@ import { openOrdersScope = await waitForActiveOpenOrdersScope(abortSignal); throwIfAborted(abortSignal); if (!openOrdersScope) { - const message = '未定位到当前委托面板'; + const message = '未找到当前委托面板'; setPlanStepStatus(message); return { ok: false, status: 'scope_not_found', message }; } @@ -4656,19 +4686,19 @@ import { throwIfAborted(abortSignal); if (!rows.length) { const directionLabel = getPlanDirectionLabel(plan); - const message = `未定位到当前币的${directionLabel || ''}可撤基础单`; + const message = `未找到${directionLabel || ''}方向的可撤基础单`; setPlanStepStatus(message); return { ok: false, status: 'rows_not_found', message }; } const rowsToCancel = selectOpenOrderRowsToCancelForPlan(plan, rows); if (!rowsToCancel.length) { - const message = '未选中当前币待撤挂单'; + const message = '未选中待替换挂单'; setPlanStepStatus(message); return { ok: false, status: 'rows_not_selected', message }; } - setPlanStepStatus(`撤销 ${rowsToCancel.length} 笔当前币挂单`); + setPlanStepStatus(`撤销 ${rowsToCancel.length} 笔同向挂单`); await cancelOpenOrderRowsForPlan( openOrdersScope, plan, @@ -4677,12 +4707,12 @@ import { abortSignal, ); throwIfAborted(abortSignal); - setPlanStepStatus('当前币挂单已替换,继续重挂'); + setPlanStepStatus('原挂单已撤,继续阶梯挂单'); return { ok: true, status: 'rows_cleared' }; } catch (e) { if (isLadderStoppedError(e)) throw e; if (e?.name === 'DialogNotClosedError') restoreTemporaryUiState = false; - const message = e?.message || '当前币挂单逐行撤销失败,已停止重挂'; + const message = e?.message || '待替换挂单撤销失败,已停止重新挂单'; setPlanStepStatus(message); const status = e?.name === 'DialogNotClosedError' ? 'dialog_not_closed' : 'row_cancel_failed'; return { ok: false, status, message }; @@ -4748,9 +4778,9 @@ import { function formatOpenOrdersReplacementDetail(plan) { if (plan?.spec?.mode === 'OPEN') { - return '同向开仓挂单可能占用可开数量,准备替换'; + return '同向开仓挂单可能占用可开数量,准备撤销后重新挂单'; } - return '当前挂单占用可平数量,准备替换'; + return '同向平仓挂单占用可平数量,准备撤销后重新挂单'; } function formatOpenOrdersReplacementStatus(plan) { @@ -4803,7 +4833,7 @@ import { abortSignal, ); throwIfAborted(abortSignal); - if (!result?.ok) throw new Error(result.message || '当前币挂单未替换,已停止重挂'); + if (!result?.ok) throw new Error(result.message || '原挂单未完成替换,已停止重新挂单'); } } throw new Error('阶梯重挂流程异常'); @@ -4940,7 +4970,7 @@ import { function isConfirmedZeroOpenBalance(qty) { const available = readTradeAvailableBalance(getTradeMutationRoot(), { isVisibleElement }); const normalizedQty = normalizeDecimalString(String(qty ?? '')); - const normalizedBalance = normalizeDecimalString(available?.amount || ''); + const normalizedBalance = normalizeDecimalString(available?.amount ?? ''); return ( normalizedQty !== null && normalizedBalance !== null @@ -5117,7 +5147,7 @@ import { } async function fetchCurrentSymbolPositionState(symbol) { - if (!cachedBncHeaders) throw new Error('bapi header 尚未缓存'); + if (!cachedBncHeaders) throw new Error('币安请求头尚未就绪'); const controller = new AbortController(); const timer = window.setTimeout(() => controller.abort(), 5000); try { @@ -5128,7 +5158,7 @@ import { credentials: 'include', signal: controller.signal, }); - if (!resp.ok) throw new Error(`user-position HTTP ${resp.status}`); + if (!resp.ok) throw new Error(`持仓接口异常:HTTP ${resp.status}`); const payload = await resp.json(); return { ...resolveSymbolPositionStatus(payload, symbol), @@ -5812,7 +5842,7 @@ import { let finalText = ''; let constraintText = ''; if (!precisionReady) { - finalText = '等待订单簿缩放值'; + finalText = '等待价格精度'; } else if (!modeReady) { finalText = '等待开仓/平仓状态'; } else if (rulesPending || !effectiveMinQty) { @@ -5877,7 +5907,7 @@ import { hintTitle = `平仓模式:双向持仓时单击订单簿价格后将${CFG.SAFE_MODE ? '填数量' : action}`; } else { hintText = '暂无可平仓位'; - hintTitle = '平仓模式:当前币种暂无可平仓位'; + hintTitle = '平仓模式:当前交易对暂无可平仓位'; } if (hintEl.textContent !== hintText) hintEl.textContent = hintText; if (hintEl.title !== hintTitle) hintEl.title = hintTitle; @@ -6548,7 +6578,7 @@ import { function resolveTargetQty(tradeMode, priceOverride) { const symbol = getCurrentSymbol(); const precision = readCurrentOrderbookPrecisionValue(); - if (!precision) throw new Error('未识别订单簿缩放值'); + if (!precision) throw new Error('未识别价格精度'); const qtyRuleContext = getQtyRuleContext(symbol, tradeMode, priceOverride); if (qtyRuleContext.status !== 'ready' || !qtyRuleContext.effectiveMinQty) { // 规则未就绪时触发加载,下次点击即可命中缓存 @@ -6653,7 +6683,7 @@ import { throw new Error('开仓/平仓模式已变化,已停止提交'); } if (readCurrentOrderbookPrecisionValue() !== qtyPlan.precision) { - throw new Error('订单簿缩放值已变化,已停止提交'); + throw new Error('价格精度已变化,已停止提交'); } const currentAction = resolveTradeAction(); if ( diff --git a/test/dom/binance-orderbook-trade/chart-orders.test.js b/test/dom/binance-orderbook-trade/chart-orders.test.js index 2c355b9..eaa20b6 100644 --- a/test/dom/binance-orderbook-trade/chart-orders.test.js +++ b/test/dom/binance-orderbook-trade/chart-orders.test.js @@ -103,7 +103,7 @@ test('find waits for a visible complete and unambiguous chart contract', () => { incomplete.dom.window.document.querySelector('[aria-describedby]').removeAttribute('aria-describedby'); assert.throws( () => getBinanceChartOrdersTarget(incomplete.dom.window.document), - /Expected one Binance chart orders popover reference, found 0/, + /图表“显示当前委托”菜单入口数量异常:0/, ); const first = loadChartTarget(); @@ -112,7 +112,7 @@ test('find waits for a visible complete and unambiguous chart contract', () => { first.dom.window.document.body.append(duplicateRoot.firstElementChild); assert.throws( () => getBinanceChartOrdersTarget(first.dom.window.document), - /Expected one visible Binance chart root, found 2/, + /可见图表区域数量异常:2/, ); }); @@ -161,7 +161,7 @@ test('invalid or duplicate Open Orders checkboxes fail explicitly', () => { getBinanceChartOrdersTarget(invalid.dom.window.document), isVisibleElement, ), - /OpenOrders state is mixed/, + /图表“显示当前委托”状态异常:mixed/, ); const duplicate = loadChartTarget(); @@ -175,7 +175,7 @@ test('invalid or duplicate Open Orders checkboxes fail explicitly', () => { getBinanceChartOrdersTarget(duplicate.dom.window.document), isVisibleElement, ), - /Expected one Binance chart OpenOrders checkbox, found 2/, + /图表“显示当前委托”选项数量异常:2/, ); }); @@ -187,27 +187,27 @@ test('rejects a replaced chart root toolbar trigger or runtime', () => { assert.throws( () => assertSameBinanceChartOrdersTarget(oldTarget, newTarget), - /Binance chart orders target changed/, + /图表“显示当前委托”控件已变化/, ); assert.throws( () => assertSameBinanceChartOrdersTarget(oldTarget, { ...oldTarget, toolbar: newTarget.toolbar, }), - /Binance chart orders target changed/, + /图表“显示当前委托”控件已变化/, ); assert.throws( () => assertSameBinanceChartOrdersTarget(oldTarget, { ...oldTarget, trigger: newTarget.trigger, }), - /Binance chart orders target changed/, + /图表“显示当前委托”控件已变化/, ); assert.throws( () => assertSameBinanceChartOrdersTarget(oldTarget, { ...oldTarget, tradingViewApi: newTarget.tradingViewApi, }), - /Binance chart orders target changed/, + /图表“显示当前委托”控件已变化/, ); }); diff --git a/test/unit/binance-live-performance-probe.test.js b/test/unit/binance-live-performance-probe.test.js index 5364c1e..92e5ca5 100644 --- a/test/unit/binance-live-performance-probe.test.js +++ b/test/unit/binance-live-performance-probe.test.js @@ -94,6 +94,6 @@ test('live completion Runtime.evaluate expression owns the page-ready contract', assert.match(expression, /"kind":"dialog-confirm"/); assert.match(expression, /finishAfterPerformanceTail/); assert.match(expression, /dialog-action/); - assert.match(expression, /已恢复筛选状态/); + assert.match(expression, /撤单已完成/); assert.doesNotMatch(expression, /DEFAULT_GLOBAL_NAME/); }); diff --git a/test/unit/binance-orderbook-trade/auto-open-leverage.test.js b/test/unit/binance-orderbook-trade/auto-open-leverage.test.js index 416d81f..8080e9f 100644 --- a/test/unit/binance-orderbook-trade/auto-open-leverage.test.js +++ b/test/unit/binance-orderbook-trade/auto-open-leverage.test.js @@ -46,14 +46,14 @@ test('all current-symbol position directions must be zero before reset', () => { test('rejects unsuccessful or malformed current-symbol position responses', () => { assert.throws( () => resolveSymbolPositionStatus({ success: false, data: [] }, 'HYPEUSDT'), - /position response was unsuccessful/, + /持仓接口返回失败/, ); assert.throws( () => resolveSymbolPositionStatus({ success: true, data: [{ symbol: 'HYPEUSDT', positionAmount: 'unknown' }], }, 'HYPEUSDT'), - /invalid position amount/, + /持仓数量无效/, ); }); diff --git a/test/unit/binance-orderbook-trade/cancel-all-dialog.test.js b/test/unit/binance-orderbook-trade/cancel-all-dialog.test.js index 9630538..2665e9e 100644 --- a/test/unit/binance-orderbook-trade/cancel-all-dialog.test.js +++ b/test/unit/binance-orderbook-trade/cancel-all-dialog.test.js @@ -88,7 +88,7 @@ test('ignores unrelated dialogs and rejects invalid cancel-all button contracts' })); assert.throws( () => findBinanceCancelAllDialog(extraButtonDom.window.document, isVisibleElement), - /Expected two Binance cancel-all dialog buttons, found 3/, + /撤单确认弹窗按钮数量异常:3/, ); }); @@ -96,7 +96,7 @@ test('rejects multiple semantic cancel-all dialogs with distinct action pairs', const dom = loadFixtureDom(`${createDialogMarkup()}${createDialogMarkup()}`); assert.throws( () => findBinanceCancelAllDialog(dom.window.document, isVisibleElement), - /Expected one Binance cancel-all dialog action pair, found 2/, + /撤单确认弹窗操作区域数量异常:2/, ); }); diff --git a/test/unit/binance-orderbook-trade/chart-orders-recovery.test.js b/test/unit/binance-orderbook-trade/chart-orders-recovery.test.js index 845c74a..cb4d067 100644 --- a/test/unit/binance-orderbook-trade/chart-orders-recovery.test.js +++ b/test/unit/binance-orderbook-trade/chart-orders-recovery.test.js @@ -58,10 +58,10 @@ test('valid recovery records do not expire before the hidden chart state is rest }); test('recovery records reject extra fields and invalid timestamps', () => { - assert.throws(() => createChartOrdersRecoveryRecord(Number.NaN), /timestamp is invalid/); + assert.throws(() => createChartOrdersRecoveryRecord(Number.NaN), /图表委托线恢复时间无效/); assert.throws( () => parseChartOrdersRecoveryRecord('{}', Number.NaN), - /current time is invalid/, + /图表委托线恢复当前时间无效/, ); assert.deepEqual(parseChartOrdersRecoveryRecord(JSON.stringify({ version: 2, diff --git a/test/unit/binance-orderbook-trade/chart-save-coalescer.test.js b/test/unit/binance-orderbook-trade/chart-save-coalescer.test.js index 5bec45d..4453e72 100644 --- a/test/unit/binance-orderbook-trade/chart-save-coalescer.test.js +++ b/test/unit/binance-orderbook-trade/chart-save-coalescer.test.js @@ -214,7 +214,7 @@ test('times out when drawing events do not produce matching save requests', asyn () => api.emit('drawing_event', 'order-1', 'remove'), { timeoutMs: 10 }, ), - /Expected 1 TradingView saveChart requests, received 0/, + /图表保存请求数量不一致:预期 1,实际 0/, ); assert.equal(api.saveChart, originalSaveChart); diff --git a/test/unit/binance-orderbook-trade/continuous-ladder.test.js b/test/unit/binance-orderbook-trade/continuous-ladder.test.js index e648907..9b1d8bc 100644 --- a/test/unit/binance-orderbook-trade/continuous-ladder.test.js +++ b/test/unit/binance-orderbook-trade/continuous-ladder.test.js @@ -139,11 +139,11 @@ test('continuous ladder progress rejects an invalid or duplicate round outcome', assert.throws( () => recordContinuousLadderRound(progress, outcome), - /Continuous ladder round was already recorded/, + /连续阶梯本轮结果已记录/, ); assert.throws( () => formatContinuousLadderProgress('阶梯平空', 'unknown', progress), - /Invalid continuous ladder phase/, + /连续阶梯阶段无效/, ); }); @@ -216,7 +216,7 @@ test('continuous ladder wait reasons distinguish readiness from the actual coold ); assert.throws( () => formatContinuousLadderWaitReason('unknown', CONTINUOUS_LADDER_COOLDOWN_MS), - /Invalid continuous ladder wait phase/, + /连续阶梯等待阶段无效/, ); }); @@ -295,6 +295,6 @@ test('continuous ladder rejects an invalid readiness contract', async () => { readReadiness: () => ({ status: 'unknown' }), delay: async () => {}, }), - /Invalid continuous ladder readiness state/, + /连续阶梯按钮就绪状态无效/, ); }); diff --git a/test/unit/binance-orderbook-trade/decimal.test.js b/test/unit/binance-orderbook-trade/decimal.test.js index 6f68b6b..f55167c 100644 --- a/test/unit/binance-orderbook-trade/decimal.test.js +++ b/test/unit/binance-orderbook-trade/decimal.test.js @@ -16,6 +16,7 @@ import { test('normalizes unsigned decimal strings without losing precision', () => { assert.equal(normalizeDecimalString('001.2300'), '1.23'); assert.equal(normalizeDecimalString('0.000'), '0'); + assert.equal(normalizeDecimalString(0), '0'); assert.equal(normalizeDecimalString('1,234.5000'), '1234.5'); assert.equal(normalizeDecimalString('-1'), null); assert.equal(normalizeDecimalString('abc'), null); diff --git a/test/unit/binance-orderbook-trade/ladder-options.test.js b/test/unit/binance-orderbook-trade/ladder-options.test.js index f9eb3e8..5f770db 100644 --- a/test/unit/binance-orderbook-trade/ladder-options.test.js +++ b/test/unit/binance-orderbook-trade/ladder-options.test.js @@ -102,7 +102,7 @@ test('retired option migration rejects an unsupported replacement', () => { 0.3, [1, 5, 10], ), - /Invalid replacement option: 0\.3/, + /替换选项无效:0\.3/, ); }); @@ -177,7 +177,7 @@ test('quantity multiplier keys share the symbol-mode-precision identity', () => test('ladder option persistence rejects unknown mode and skips incomplete context', () => { const storage = createStorage(); - assert.throws(() => modeSymbolPrecisionOptionStorageKey(MODE_KEYS, 'UNKNOWN', 'BTCUSDT', '0.01'), /Unknown trade mode/); + assert.throws(() => modeSymbolPrecisionOptionStorageKey(MODE_KEYS, 'UNKNOWN', 'BTCUSDT', '0.01'), /未知交易模式/); assert.equal(modeSymbolPrecisionOptionStorageKey(MODE_KEYS, 'OPEN', '', '0.01'), null); assert.equal(modeSymbolPrecisionOptionStorageKey(MODE_KEYS, 'OPEN', 'BTCUSDT', ''), null); assert.equal(loadModeSymbolPrecisionNumberOption(storage, MODE_KEYS, 'OPEN', '', '0.01', [3, 5, 7, 9], 5), null); diff --git a/test/unit/binance-orderbook-trade/ladder-progress.test.js b/test/unit/binance-orderbook-trade/ladder-progress.test.js index eead34a..f8117e6 100644 --- a/test/unit/binance-orderbook-trade/ladder-progress.test.js +++ b/test/unit/binance-orderbook-trade/ladder-progress.test.js @@ -138,11 +138,11 @@ test('failed and interrupted ladder statuses retain confirmed progress', () => { test('ladder progress rejects invalid counters instead of masking them', () => { assert.throws( () => formatStoppedLadderProgress('阶梯开多', { submittedOrders: -1, cancelledOrders: 0 }), - /Invalid ladder progress/, + /阶梯进度状态无效/, ); assert.throws( () => recordLadderCancelledOrder({ submittedOrders: 0, cancelledOrders: 1.5 }), - /Invalid ladder progress/, + /阶梯进度状态无效/, ); assert.throws( () => formatCompletedLadderProgress('阶梯开多', 1, 2, { @@ -151,12 +151,12 @@ test('ladder progress rejects invalid counters instead of masking them', () => { plannedOrders: 2, currentPlanSubmittedOrders: 1, }), - /Completed ladder progress mismatch/, + /阶梯完成进度与计划不一致/, ); assert.throws( () => setLadderPlannedOrders(createLadderProgress(), 0), - /Invalid ladder planned orders/, + /阶梯计划笔数无效/, ); const completedProgress = createLadderProgress(); @@ -164,7 +164,7 @@ test('ladder progress rejects invalid counters instead of masking them', () => { recordLadderSubmittedOrder(completedProgress); assert.throws( () => recordLadderSubmittedOrder(completedProgress), - /Ladder submitted orders exceed plan/, + /阶梯已挂笔数超过计划/, ); }); diff --git a/test/unit/binance-orderbook-trade/ladder.test.js b/test/unit/binance-orderbook-trade/ladder.test.js index f5d163e..c615a2f 100644 --- a/test/unit/binance-orderbook-trade/ladder.test.js +++ b/test/unit/binance-orderbook-trade/ladder.test.js @@ -6,6 +6,7 @@ import { fitLadderPlanForMinimumQty, getLadderActionSpec, getLadderPercentForMode, + getUnavailableLadderQuantityMessage, } from '../../../src/binance-orderbook-trade/core/ladder-plan.js'; test('maps open and close ladder actions to order direction specs', () => { @@ -46,6 +47,23 @@ test('rejects unknown ladder actions and resolves percent source by mode', () => assert.equal(getLadderPercentForMode('UNKNOWN', 30, 50), null); }); +test('unavailable ladder quantity messages preserve the observed failure reason', () => { + assert.equal(getUnavailableLadderQuantityMessage('OPEN', null), '未读取到可开数量'); + assert.equal(getUnavailableLadderQuantityMessage('OPEN', '0'), '当前可开数量为 0'); + assert.equal( + getUnavailableLadderQuantityMessage('OPEN', 0, true), + '可用余额不足', + ); + assert.equal(getUnavailableLadderQuantityMessage('OPEN', '1.25'), null); + assert.equal(getUnavailableLadderQuantityMessage('CLOSE', null), '未读取到可平数量'); + assert.equal(getUnavailableLadderQuantityMessage('CLOSE', '0'), '当前方向没有可平仓位'); + assert.equal(getUnavailableLadderQuantityMessage('CLOSE', '1.25'), null); + assert.throws( + () => getUnavailableLadderQuantityMessage('UNKNOWN', '0'), + /未知阶梯数量模式/, + ); +}); + test('auto-fits ladder percent before reducing requested levels', () => { const fit = fitLadderPlanForMinimumQty({ baseQty: '10', diff --git a/test/unit/binance-orderbook-trade/orderbook.test.js b/test/unit/binance-orderbook-trade/orderbook.test.js index ac37c9b..08dad58 100644 --- a/test/unit/binance-orderbook-trade/orderbook.test.js +++ b/test/unit/binance-orderbook-trade/orderbook.test.js @@ -81,10 +81,10 @@ test('remaining ladder repricing rejects incomplete or invalid progress', () => orders, completedCount: 1, prices: [], - }), /Expected 1 replacement prices/); + }), /重定价数量不一致:预期 1 个价格/); assert.throws(() => repriceRemainingLadderOrders({ orders, completedCount: 3, prices: [], - }), /Invalid completed ladder count/); + }), /已完成阶梯订单数无效/); }); diff --git a/test/unit/binance-orderbook-trade/panel-copy.test.js b/test/unit/binance-orderbook-trade/panel-copy.test.js index 58ac4fd..43942f4 100644 --- a/test/unit/binance-orderbook-trade/panel-copy.test.js +++ b/test/unit/binance-orderbook-trade/panel-copy.test.js @@ -58,5 +58,5 @@ test('precision refresh tooltip describes the latest trade snapshot without call '优先根据最新 10 条成交价;价格变化不足时自动扩大范围,重新计算推荐精度。', ); assert.doesNotMatch(formatPrecisionRefreshTooltip(10), /最小值/); - assert.throws(() => formatPrecisionRefreshTooltip(1), /Invalid precision trade count/); + assert.throws(() => formatPrecisionRefreshTooltip(1), /价格精度成交样本数无效/); }); diff --git a/test/unit/binance-orderbook-trade/precision.test.js b/test/unit/binance-orderbook-trade/precision.test.js index f59c345..8a83e25 100644 --- a/test/unit/binance-orderbook-trade/precision.test.js +++ b/test/unit/binance-orderbook-trade/precision.test.js @@ -45,7 +45,7 @@ test('selects only an exact native decade precision target', () => { test('rejects unsupported precision adjustment directions', () => { assert.throws( () => getOrderbookPrecisionDecadeTarget(['0.01', '0.1'], '0.01', 'NEXT'), - /Unsupported orderbook precision direction/, + /不支持的价格精度方向/, ); }); diff --git a/test/unit/binance-orderbook-trade/source-regressions.test.js b/test/unit/binance-orderbook-trade/source-regressions.test.js index c6fe8e1..3a5ca74 100644 --- a/test/unit/binance-orderbook-trade/source-regressions.test.js +++ b/test/unit/binance-orderbook-trade/source-regressions.test.js @@ -563,7 +563,7 @@ test('ladder minimum quantity failure explains safe manual options', () => { assert.match(errorBody, /openOrdersReplacementPlan/); assert.match(errorBody, /replacementTotalQty/); assert.doesNotMatch(errorBody, /allowPartialReplacement/); - assert.match(errorBody, /脚本只会尝试替换当前币同向开仓基础单,不会自动全撤/); + assert.match(errorBody, /脚本只会尝试替换当前交易对的同向开仓基础单,不会自动全撤/); assert.match(errorBody, /脚本不会自动撤单/); assert.doesNotMatch(errorBody, /将自动撤单/); @@ -660,7 +660,8 @@ test('Option or Alt click continuously repeats close ladders only after readines test('open ladder stops immediately only for a confirmed zero available balance', () => { const readOpenQtyBody = readFunctionBody('readOpenBaseQtyForLadder'); - assert.match(readOpenQtyBody, /isConfirmedZeroOpenBalance\(qty\)/); + assert.match(readOpenQtyBody, /isConfirmedZeroOpenBalance\(quantity\.qty\)/); + assert.match(readOpenQtyBody, /confirmedZeroOpenBalance/); assert.match(readOpenQtyBody, /return \{ qty, qtySource \}/); assert.match(readOpenQtyBody, /waitForTradeFormMutationState/); assert.doesNotMatch(readOpenQtyBody, /delay\(/); @@ -677,6 +678,27 @@ test('open ladder stops immediately only for a confirmed zero available balance' assert.match(readOpenableByTestIdsBody, /qtySource: 'testid'/); }); +test('user-facing trading failures preserve one precise reason and shared terminology', () => { + const buildBody = readFunctionBody('buildLadderPlan'); + const cancelBody = readFunctionBody('runCancelCurrentSymbolOpenOrders'); + const replaceBody = readFunctionBody('cancelCurrentSymbolOpenOrdersForPlan'); + + assert.match(buildBody, /getUnavailableLadderQuantityMessage\(/); + assert.match(buildBody, /normalizeDecimalString\(base\?\.qty \?\? ''\)/); + assert.match(buildBody, /base\?\.confirmedZeroOpenBalance === true/); + assert.match(source, /normalizeDecimalString\(available\?\.amount \?\? ''\)/); + assert.match(buildBody, /读取交易规则时交易对已变化/); + assert.match(buildBody, /读取交易规则时价格精度已变化/); + assert.match(buildBody, /交易规则尚未就绪,请稍后重试/); + assert.doesNotMatch(buildBody, /失败或|未就绪或|未读取到可用可/); + assert.doesNotMatch(source, /订单簿缩放值|缩放下拉|缩放调整|当前缩放|原生缩放|撤本币|状态丢失|上下文丢失/); + assert.doesNotMatch(source, /未找到价格或数量输入框|执行中价格或数量输入框丢失/); + assert.doesNotMatch(source, /未定位当前交易对|下单 API 成功响应|当前委托\/历史成交/); + assert.doesNotMatch(source, /observer root is unavailable|Binance chart OpenOrders/); + assert.doesNotMatch(cancelBody, /未定位到|当前币|逐行撤单/); + assert.doesNotMatch(replaceBody, /未定位到|当前币|逐行撤单/); +}); + test('ladder replacement cancels visible current-symbol same-direction rows up to planned quantity', () => { const readRowsBody = readFunctionBody('readCurrentSymbolOpenOrderRows'); assert.match(readRowsBody, /findOpenOrderRowElements\(root/); @@ -789,7 +811,7 @@ test('ladder replacement cancels visible current-symbol same-direction rows up t const cancelRowsBody = readFunctionBody('cancelCurrentSymbolOpenOrdersForPlan'); assert.match(cancelRowsBody, /if \(!isCurrentObservedSymbol\(symbol\) \|\| symbol !== plan\?\.symbol\)/); assert.match(cancelRowsBody, /activateOpenOrdersBasicSubTab\(\s*openOrdersScope,\s*abortSignal[\s\S]*openOrdersScope = await waitForActiveOpenOrdersScope\(abortSignal\)/); - assert.match(cancelRowsBody, /if \(!openOrdersScope\) \{\s*const message = '未定位到当前委托面板'/); + assert.match(cancelRowsBody, /if \(!openOrdersScope\) \{\s*const message = '未找到当前委托面板'/); assert.match(cancelRowsBody, /waitForCurrentSymbolOpenOrderRows\(\s*openOrdersScope,\s*symbol,\s*plan,\s*abortSignal/); assert.doesNotMatch(cancelRowsBody, /const openOrdersCount = getOpenOrdersTabCount\(\)/); assert.match(cancelRowsBody, /getPlanDirectionLabel\(plan\)/); @@ -883,7 +905,7 @@ test('ladder task statuses name the active action and observed outcome', () => { assert.match(startBody, /formatFailedLadderProgress\(spec\.label,\s*failureMessage,\s*progress\)/); assert.match(stopBody, /formatActiveContinuousLadderProgress\(\s*activeSpec\.label,\s*'停止中'/); assert.match(stopBody, /setLadderStatus\(`\$\{activeSpec\.label\}停止中`\)/); - assert.match(cancelPlanBody, /setPlanStepStatus\(`撤销 \$\{rowsToCancel\.length\} 笔当前币挂单`\)/); + assert.match(cancelPlanBody, /setPlanStepStatus\(`撤销 \$\{rowsToCancel\.length\} 笔同向挂单`\)/); assert.match(planStatusBody, /`\$\{plan\.spec\.label\}计划:\$\{formatLadderPlanDetail\(plan\)\}`/); assert.doesNotMatch(planStatusBody, /%\/|\/幅/); assert.match(replacementStatusBody, /`\$\{plan\.spec\.label\}:/); @@ -1160,7 +1182,7 @@ test('cancel current-symbol open orders are single-flight and follow the native assert.match(cancelRunBody, /const message = '撤单已取消';\s*successStatusMessage = message;/); assert.doesNotMatch(cancelRunBody, /,已恢复页面状态/); assert.match(cancelRunBody, /撤单确认弹窗已打开/); - assert.match(cancelRunBody, /已确认撤单,等待当前币挂单清空/); + assert.match(cancelRunBody, /撤单已确认,等待挂单清空/); assert.match(cancelRunBody, /未能恢复隐藏其他合约状态/); assert.match(cancelRunBody, /未能恢复图表当前委托显示/); const noOrdersReturnIndex = cancelRunBody.indexOf("status: 'no_orders'"); @@ -1171,7 +1193,7 @@ test('cancel current-symbol open orders are single-flight and follow the native assert.match(cancelRunBody, /cancelCurrentSymbolOpenOrdersBlocksLadderActions = true;[\s\S]*scheduleRenderPanel\(\);/); const startBody = readFunctionBody('startLadder'); - assert.match(startBody, /if \(cancelCurrentSymbolOpenOrdersTask\)[\s\S]*`\$\{spec\.label\}尚未开始:撤本币挂单处理中`/); + assert.match(startBody, /if \(cancelCurrentSymbolOpenOrdersTask\)[\s\S]*`\$\{spec\.label\}尚未开始:撤单处理中`/); const actionRowsBody = readFunctionBody('getLadderControlSections'); assert.match(actionRowsBody, /actionDisabled = ladderRunning[\s\S]*\|\| !!singleOrderTask[\s\S]*\|\| cancelCurrentSymbolOpenOrdersBlocksLadderActions/); assert.doesNotMatch(actionRowsBody, /!!cancelCurrentSymbolOpenOrdersTask/); @@ -1539,7 +1561,7 @@ test('panel numeric options wait for a complete mode-symbol-precision context', assert.match(renderBody, /const storedMultiplier = optionContext/); const refreshBody = readFunctionBody('refreshComputedInfo'); - assert.match(refreshBody, /finalText = '等待订单簿缩放值'/); + assert.match(refreshBody, /finalText = '等待价格精度'/); assert.match(refreshBody, /finalText = '等待开仓\/平仓状态'/); assert.match(refreshBody, /const numericContextReady = modeReady && precisionReady/); }); @@ -1563,7 +1585,7 @@ test('precision changes invalidate edits and immediately rerender the panel', () test('ladder plans fail closed when orderbook precision changes', () => { const buildBody = readFunctionBody('buildLadderPlan'); assert.match(buildBody, /const startPrecision = readCurrentOrderbookPrecisionValue\(\)/); - assert.match(buildBody, /if \(!startPrecision\) throw new Error\('未识别订单簿缩放值'\)/); + assert.match(buildBody, /if \(!startPrecision\) throw new Error\('未识别价格精度'\)/); assert.match(buildBody, /precision: startPrecision/); const contextBody = readFunctionBody('assertLadderExecutionContext'); @@ -1578,7 +1600,7 @@ test('ladder plans fail closed when orderbook precision changes', () => { test('single-order sizing and submission retain the captured orderbook precision', () => { const resolveBody = readFunctionBody('resolveTargetQty'); assert.match(resolveBody, /const precision = readCurrentOrderbookPrecisionValue\(\)/); - assert.match(resolveBody, /if \(!precision\) throw new Error\('未识别订单簿缩放值'\)/); + assert.match(resolveBody, /if \(!precision\) throw new Error\('未识别价格精度'\)/); assert.match(resolveBody, /loadMultiplier\(tradeMode, symbol, precision\)/); assert.match(resolveBody, /precision,/); assert.match(source, /readCurrentOrderbookPrecisionValue\(\) !== qtyPlan\.precision/); diff --git a/test/unit/binance-orderbook-trade/status-symbol.test.js b/test/unit/binance-orderbook-trade/status-symbol.test.js index 79f83a0..3540f19 100644 --- a/test/unit/binance-orderbook-trade/status-symbol.test.js +++ b/test/unit/binance-orderbook-trade/status-symbol.test.js @@ -10,7 +10,7 @@ test('status symbols omit supported futures quote assets', () => { }); test('status symbols reject unknown contracts instead of guessing a suffix', () => { - assert.throws(() => formatStatusBaseAsset('BTCUSD'), /Unsupported futures status symbol/); - assert.throws(() => formatStatusBaseAsset('USDT'), /Unsupported futures status symbol/); - assert.throws(() => formatStatusBaseAsset(''), /Unsupported futures status symbol/); + assert.throws(() => formatStatusBaseAsset('BTCUSD'), /不支持的合约状态交易对/); + assert.throws(() => formatStatusBaseAsset('USDT'), /不支持的合约状态交易对/); + assert.throws(() => formatStatusBaseAsset(''), /不支持的合约状态交易对/); }); diff --git a/test/unit/binance-orderbook-trade/trade-form.test.js b/test/unit/binance-orderbook-trade/trade-form.test.js index 7e75923..bc25a08 100644 --- a/test/unit/binance-orderbook-trade/trade-form.test.js +++ b/test/unit/binance-orderbook-trade/trade-form.test.js @@ -1029,7 +1029,7 @@ test('trade input synchronization rejects an invalid rollback stability contract writeValue: () => {}, requiredStableMismatchFrames: 0, }), - /Trade input rollback stability must be a positive integer/, + /输入框回退稳定帧数必须为正整数/, ); assert.throws( () => createTradeInputStateReader({ @@ -1041,7 +1041,7 @@ test('trade input synchronization rejects an invalid rollback stability contract writeValue: () => {}, maxWriteAttempts: 0, }), - /Trade input write attempts must be a positive integer/, + /输入框写入次数必须为正整数/, ); assert.throws( () => createTradeInputStateReader({ @@ -1053,7 +1053,7 @@ test('trade input synchronization rejects an invalid rollback stability contract writeValue: () => {}, requiredStableMatchFrames: 0, }), - /Trade input accepted stability must be a positive integer/, + /输入框写入稳定帧数必须为正整数/, ); assert.throws( () => createTradeInputStateReader({ @@ -1065,7 +1065,7 @@ test('trade input synchronization rejects an invalid rollback stability contract writeValue: () => {}, requiredStableMismatchMs: -1, }), - /Trade input rollback stability duration must be a non-negative number/, + /输入框回退稳定时间不能为负数/, ); assert.throws( () => createTradeInputStateReader({ @@ -1077,7 +1077,7 @@ test('trade input synchronization rejects an invalid rollback stability contract writeValue: () => {}, requiredStableMatchMs: Number.NaN, }), - /Trade input accepted stability duration must be a non-negative number/, + /输入框写入稳定时间不能为负数/, ); assert.throws( () => createTradeInputStateReader({ @@ -1089,7 +1089,7 @@ test('trade input synchronization rejects an invalid rollback stability contract writeValue: () => {}, recoverProvisionalMatchRollback: 'yes', }), - /Provisional trade input recovery flag must be boolean/, + /输入框临时恢复标记必须为布尔值/, ); }); diff --git a/test/unit/binance-orderbook-trade/tradingview-target.test.js b/test/unit/binance-orderbook-trade/tradingview-target.test.js index d576de6..ec5c63d 100644 --- a/test/unit/binance-orderbook-trade/tradingview-target.test.js +++ b/test/unit/binance-orderbook-trade/tradingview-target.test.js @@ -53,7 +53,7 @@ test('get rejects an unavailable TradingView target', () => { const dom = loadFixtureDom(createChartMarkup()); assert.throws( () => getBinanceTradingViewTarget(dom.window.document), - /Binance TradingView target is unavailable/, + /未找到可用图表接口/, ); }); @@ -61,7 +61,7 @@ test('target discovery rejects ambiguous chart roots or TradingView APIs', () => const duplicateRootDom = loadFixtureDom(`${createChartMarkup()}${createChartMarkup()}`); assert.throws( () => findBinanceTradingViewTarget(duplicateRootDom.window.document), - /Expected one visible Binance chart root, found 2/, + /可见图表区域数量异常:2/, ); const { dom } = loadChartTarget(); @@ -70,6 +70,6 @@ test('target discovery rejects ambiguous chart roots or TradingView APIs', () => secondFrame.contentWindow.tradingViewApi = { activeChart() {} }; assert.throws( () => findBinanceTradingViewTarget(dom.window.document), - /Expected one Binance TradingView API, found 2/, + /图表接口数量异常:2/, ); });