Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions e2e/binance-orderbook/specs/cancel-covering-matrix.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ for (const entry of CANCEL_COVERING_SCENARIOS) {
}).click();
await expect(page.getByText(
vector.dialogOutcome === 'confirm'
? `${scenario.currentSymbol} 撤单流程结束,已恢复筛选状态`
: `${scenario.currentSymbol} 已取消撤单,已恢复页面状态`,
? '撤单流程结束,已恢复筛选状态'
: '已取消撤单,已恢复页面状态',
)).toBeVisible();
}

Expand Down
24 changes: 12 additions & 12 deletions e2e/binance-orderbook/specs/cancel-current-symbol.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ test('cancelling the native dialog preserves current and other orders and restor
await expect(page.getByRole('dialog')).toBeVisible();
await expect.poll(async () => (await readFixtureState(page)).showOrders).toBe(false);
await page.getByRole('button', { name: '取消' }).click();
await expect(page.getByText('HYPEUSDT 已取消撤单,已恢复页面状态')).toBeVisible();
await expect(page.getByText('已取消撤单,已恢复页面状态')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(ORDER_SETS.both);
Expand All @@ -116,7 +116,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('HYPEUSDT 撤单流程结束,已恢复筛选状态')).toBeVisible();
await expect(page.getByText('撤单流程结束,已恢复筛选状态')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(otherSymbolOrders(scenario));
Expand All @@ -136,7 +136,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('HYPEUSDT 撤单流程结束,已恢复筛选状态')).toBeVisible();
await expect(page.getByText('撤单流程结束,已恢复筛选状态')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(otherSymbolOrders(scenario));
Expand Down Expand Up @@ -186,7 +186,7 @@ for (const closeMethod of ['Escape', 'backdrop']) {
} else {
await page.locator('.bn-modal-root').click({ position: { x: 4, y: 4 } });
}
await expect(page.getByText('HYPEUSDT 已取消撤单,已恢复页面状态')).toBeVisible();
await expect(page.getByText('已取消撤单,已恢复页面状态')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(ORDER_SETS.both);
Expand All @@ -211,7 +211,7 @@ test('a BFCache pagehide does not abort the active native dialog', async ({ page
})));
await expect(page.getByRole('dialog')).toBeVisible();
await page.getByRole('button', { name: '取消' }).click();
await expect(page.getByText('HYPEUSDT 已取消撤单,已恢复页面状态')).toBeVisible();
await expect(page.getByText('已取消撤单,已恢复页面状态')).toBeVisible();
await expectRestoredState(page, scenario);
expect(errors).toEqual([]);
});
Expand All @@ -229,7 +229,7 @@ test('a real pagehide aborts dialog tracking without mutating orders', async ({
await page.evaluate(() => window.dispatchEvent(new PageTransitionEvent('pagehide', {
persisted: false,
})));
await expect(page.getByText('HYPEUSDT 页面已离开,撤单确认跟踪已停止')).toBeVisible();
await expect(page.getByText('原交易对 HYPE 页面已离开,撤单确认跟踪已停止')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(ORDER_SETS.current);
Expand All @@ -249,7 +249,7 @@ test('a missing native dialog stops cleanly and restores temporary UI state', as
const { errors } = await openUserscriptScenario(page, scenario);

await page.getByRole('button', { name: '撤单' }).click();
await expect(page.getByText('HYPEUSDT 未识别到撤单确认弹窗,未继续撤单流程')).toBeVisible({
await expect(page.getByText('未识别到撤单确认弹窗,未继续撤单流程')).toBeVisible({
timeout: 3_000,
});

Expand All @@ -271,7 +271,7 @@ for (const dialogMode of ['extraAction', 'missingPrimary']) {

await page.getByRole('button', { name: '撤单' }).click();
await expect(page.getByText(
'HYPEUSDT 撤单确认弹窗结构异常,未执行弹窗操作',
'撤单确认弹窗结构异常,未执行弹窗操作',
)).toBeVisible();

const state = await readFixtureState(page);
Expand Down Expand Up @@ -299,8 +299,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('HYPEUSDT 已确认撤单,等待当前币挂单清空')).toBeVisible();
await expect(page.getByText('HYPEUSDT 撤单流程结束,已恢复筛选状态')).toBeVisible();
await expect(page.getByText('已确认撤单,等待当前币挂单清空')).toBeVisible();
await expect(page.getByText('撤单流程结束,已恢复筛选状态')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(otherSymbolOrders(scenario));
Expand All @@ -322,7 +322,7 @@ test('dialog tracking survives React replacing the native dialog subtree', async
await readFixtureState(page)
).events.filter((event) => event.type === 'dialog-replaced').length).toBe(1);
await page.getByRole('button', { name: '取消' }).click();
await expect(page.getByText('HYPEUSDT 已取消撤单,已恢复页面状态')).toBeVisible();
await expect(page.getByText('已取消撤单,已恢复页面状态')).toBeVisible();

const state = await readFixtureState(page);
expect(state.orders).toEqual(ORDER_SETS.current);
Expand All @@ -342,7 +342,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('HYPEUSDT 当前币挂单仍存在,撤单流程未完成')).toBeVisible({
await expect(page.getByText('当前币挂单仍存在,撤单流程未完成')).toBeVisible({
timeout: 10_000,
});

Expand Down
6 changes: 3 additions & 3 deletions e2e/binance-orderbook/specs/live-performance-probe.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('HYPEUSDT 当前币无挂单');
expect(snapshot.lastSemanticState.statusText).toBe('当前币无挂单');

const finishedEventCount = snapshot.events.length;
await page.evaluate(() => {
Expand Down Expand Up @@ -75,7 +75,7 @@ test('live probe has no user-decision deadline and follows a replaced portal dia

await page.getByRole('button', { name: '取消' }).click();
const snapshot = await finishLivePerformanceProbeWhenReady(page);
await expect(page.getByText('HYPEUSDT 已取消撤单,已恢复页面状态')).toBeVisible();
await expect(page.getByText('已取消撤单,已恢复页面状态')).toBeVisible();
expect(() => validateLivePerformanceProbeSnapshot(snapshot)).not.toThrow();
expect(snapshot.events.map((event) => event.kind)).toEqual(expect.arrayContaining([
'dialog-visible',
Expand All @@ -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('HYPEUSDT 撤单流程结束,已恢复筛选状态');
expect(snapshot.lastSemanticState.statusText).toBe('撤单流程结束,已恢复筛选状态');
await destroyLivePerformanceProbe(page);
});

Expand Down
68 changes: 43 additions & 25 deletions scripts/binance-orderbook-trade.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.143
// @version 2.7.144
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -926,6 +926,21 @@
});
}

// src/binance-orderbook-trade/core/status-symbol.js
var STATUS_QUOTE_ASSETS = Object.freeze(["USDT", "USDC"]);
function formatStatusBaseAsset(symbol) {
if (typeof symbol !== "string") {
throw new Error(`Unsupported futures status symbol: ${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}`);
}
return baseAsset;
}

// src/binance-orderbook-trade/core/order-feedback.js
function isPotentialOrderFeedbackText(text) {
if (!text) return false;
Expand Down Expand Up @@ -4744,7 +4759,7 @@
{ allowPartial: true }
)[0];
if (!row) {
throw new Error(`${plan.symbol} 当前币可撤挂单数量不足,已停止重挂`);
throw new Error("当前币可撤挂单数量不足,已停止重挂");
}
currentRoot = row.root || currentRoot;
const previousKeyCount = countOpenOrderRowsByKey(row.root, plan.symbol, row.key);
Expand All @@ -4769,18 +4784,18 @@
throw new Error("逐行撤单时交易对已变化");
}
if (outcome.status === "timeout") {
throw new Error(`${plan.symbol} 当前币挂单仍存在,已停止重挂`);
throw new Error("当前币挂单仍存在,已停止重挂");
}
if (outcome.status === "dialog_open") {
const { dialog } = outcome;
setLadderStatus(`${plan.spec.label}:${plan.symbol} 单行撤单确认弹窗已打开`);
setLadderStatus(`${plan.spec.label}:单行撤单确认弹窗已打开`);
const dialogClosed = await waitForDialogToClose(
dialog,
ROW_CANCEL_DIALOG_CLOSE_TIMEOUT_MS,
abortSignal
);
if (!dialogClosed) {
const error = new Error(`${plan.symbol} 单行撤单确认弹窗仍未关闭,未恢复页面状态`);
const error = new Error("单行撤单确认弹窗仍未关闭,未恢复页面状态");
error.name = "DialogNotClosedError";
throw error;
}
Expand All @@ -4790,7 +4805,7 @@
previousKeyCount,
abortSignal
)) {
throw new Error(`${plan.symbol} 当前币挂单仍存在,已停止重挂`);
throw new Error("当前币挂单仍存在,已停止重挂");
}
}
throwIfAborted(abortSignal);
Expand All @@ -4800,7 +4815,7 @@
previousKeyCount,
abortSignal
)) {
throw new Error(`${plan.symbol} 当前币挂单状态不稳定,已停止重挂`);
throw new Error("当前币挂单状态不稳定,已停止重挂");
}
cancelQty = addDecimalStrings(cancelQty, row.qty);
recordLadderCancelledOrder(progress);
Expand Down Expand Up @@ -5091,7 +5106,7 @@
return { ok: false, status: "symbol_changing", message };
}
if (getOpenOrdersTabCount() === 0) {
setLadderStatus(`${symbol} 当前币无挂单`);
setLadderStatus("当前币无挂单");
return { ok: true, status: "no_orders" };
}
const previousAccountOrdersTabIdentity = getAccountOrdersTabIdentity2(findSelectedAccountOrdersTab2());
Expand Down Expand Up @@ -5148,7 +5163,7 @@
return { ok: false, status: "symbol_changed", message };
}
if (!openOrdersEvidence.hasOrders) {
setLadderStatus(`${symbol} 当前币无挂单`);
setLadderStatus("当前币无挂单");
return { ok: true, status: "no_orders" };
}
let cancelAllButton = openOrdersEvidence.cancelAllButton;
Expand Down Expand Up @@ -5203,12 +5218,12 @@
dialogDecision = await waitForBinanceCancelAllDialogDecision(
dialogDecisionWatcher.watcher,
dialogDecisionWatcher.lifecycleSignal,
() => setLadderStatus(`${symbol} 撤单确认弹窗已打开`)
() => setLadderStatus("撤单确认弹窗已打开")
);
} catch (error) {
restoreTemporaryUiState = false;
emit("ERR", "币安撤单确认弹窗结构异常", error);
const message = `${symbol} 撤单确认弹窗结构异常,未执行弹窗操作`;
const message = "撤单确认弹窗结构异常,未执行弹窗操作";
setLadderStatus(message);
return { ok: false, status: "dialog_contract_invalid", message };
} finally {
Expand All @@ -5217,7 +5232,8 @@
if (dialogDecision.status === "aborted") {
restoreTemporaryUiState = false;
restoreTradingViewOrdersState = false;
const message = `${symbol} 页面已离开,撤单确认跟踪已停止`;
const interruptedBaseAsset = formatStatusBaseAsset(symbol);
const message = `原交易对 ${interruptedBaseAsset} 页面已离开,撤单确认跟踪已停止`;
setLadderStatus(message);
return { ok: false, status: "aborted", message };
}
Expand All @@ -5227,17 +5243,17 @@
return { ok: false, status: "symbol_changed", message };
}
if (dialogDecision.status === "not_found") {
const message = `${symbol} 未识别到撤单确认弹窗,未继续撤单流程`;
const message = "未识别到撤单确认弹窗,未继续撤单流程";
setLadderStatus(message);
return { ok: false, status: "dialog_not_found", message };
}
if (dialogDecision.status === "cancelled") {
const message = `${symbol} 已取消撤单`;
const message = "已取消撤单";
successStatusMessage = `${message},已恢复页面状态`;
return { ok: false, status: "cancelled", message };
}
waitForTradeUiMutation({ timeoutMs: 800 });
setLadderStatus(`${symbol} 已确认撤单,等待当前币挂单清空`);
setLadderStatus("已确认撤单,等待当前币挂单清空");
openOrdersScope = await waitForActiveOpenOrdersScope();
if (!openOrdersScope || !isCurrentObservedSymbol(symbol)) {
const message = "未定位到当前委托面板";
Expand All @@ -5262,11 +5278,11 @@
setLadderStatus(message2);
return { ok: false, status: "symbol_filter_not_confirmed", message: message2 };
}
const message = waitUntilCleared ? `${symbol} 当前币挂单仍存在,已停止重挂` : `${symbol} 当前币挂单仍存在,撤单流程未完成`;
const message = waitUntilCleared ? "当前币挂单仍存在,已停止重挂" : "当前币挂单仍存在,撤单流程未完成";
setLadderStatus(message);
return { ok: false, status: "not_cleared", message };
}
successStatusMessage = waitUntilCleared ? `${symbol} 当前币挂单已撤,继续重挂` : `${symbol} 撤单流程结束,已恢复筛选状态`;
successStatusMessage = waitUntilCleared ? "当前币挂单已撤,继续重挂" : "撤单流程结束,已恢复筛选状态";
return { ok: true, status: "cleared" };
} finally {
let temporaryUiRestoreSucceeded = true;
Expand Down Expand Up @@ -5357,7 +5373,7 @@
let symbolFilterOriginalChecked = null;
let restoreTemporaryUiState = true;
try {
setPlanStepStatus(`查找 ${symbol} 当前委托`);
setPlanStepStatus("查找当前委托");
const tabReady = await activateOpenOrdersTab(abortSignal);
throwIfAborted(abortSignal);
if (!tabReady || !isCurrentObservedSymbol(symbol)) {
Expand Down Expand Up @@ -5422,20 +5438,20 @@
throwIfAborted(abortSignal);
if (!rows.length) {
const directionLabel = getPlanDirectionLabel(plan);
const message = `未定位到 ${symbol}${directionLabel ? ` ${directionLabel}` : ""} 当前币可逐行撤单的基础单`;
const message = `未定位到当前币的${directionLabel || ""}可撤基础单`;
setPlanStepStatus(message);
return { ok: false, status: "rows_not_found", message };
}
const rowsToCancel = selectOpenOrderRowsToCancelForPlan(plan, rows);
if (!rowsToCancel.length) {
const message = `未选中 ${symbol} 当前币待撤挂单`;
const message = "未选中当前币待撤挂单";
setPlanStepStatus(message);
return { ok: false, status: "rows_not_selected", message };
}
setPlanStepStatus(`${symbol} 撤销 ${rowsToCancel.length} 笔当前币挂单`);
setPlanStepStatus(`撤销 ${rowsToCancel.length} 笔当前币挂单`);
await cancelOpenOrderRowsForPlan(openOrdersScope, plan, progress, abortSignal);
throwIfAborted(abortSignal);
setPlanStepStatus(`${symbol} 当前币挂单已替换,继续重挂`);
setPlanStepStatus("当前币挂单已替换,继续重挂");
return { ok: true, status: "rows_cleared" };
} catch (e) {
if (isLadderStoppedError(e)) throw e;
Expand Down Expand Up @@ -5487,9 +5503,9 @@
}
function formatOpenOrdersReplacementStatus(plan) {
if (plan?.spec?.mode === "OPEN") {
return `${plan.spec.label}:${plan.symbol} 同向开仓挂单可能占用可开数量,准备替换`;
return `${plan.spec.label}:同向开仓挂单可能占用可开数量,准备替换`;
}
return `${plan.spec.label}:${plan.symbol} 当前挂单占用可平数量,准备替换`;
return `${plan.spec.label}:当前挂单占用可平数量,准备替换`;
}
function createLadderExpectedContext(plan) {
return {
Expand Down Expand Up @@ -7080,7 +7096,9 @@
});
const currentSymbol = getCurrentSymbol();
if (!isCurrentObservedSymbol(qtyPlan.symbol)) {
throw new Error(`交易对已变化,点击时 ${qtyPlan.symbol},当前 ${currentSymbol || "-"}`);
const clickedBaseAsset = formatStatusBaseAsset(qtyPlan.symbol);
const currentBaseAsset = currentSymbol ? formatStatusBaseAsset(currentSymbol) : "未知";
throw new Error(`交易对已变化,点击时 ${clickedBaseAsset},当前 ${currentBaseAsset}`);
}
if (getActiveTradeMode() !== action.mode) {
throw new Error("开仓/平仓模式已变化,已停止提交");
Expand Down
14 changes: 14 additions & 0 deletions src/binance-orderbook-trade/core/status-symbol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const STATUS_QUOTE_ASSETS = Object.freeze(['USDT', 'USDC']);

export function formatStatusBaseAsset(symbol) {
if (typeof symbol !== 'string') {
throw new Error(`Unsupported futures status symbol: ${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}`);
}
return baseAsset;
}
Loading
Loading