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
51 changes: 37 additions & 14 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.173
// @version 2.7.174
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -6822,6 +6822,14 @@
view: window
}));
}
function createOpenOrderCancellationUnconfirmedError(message) {
const error = new Error(message);
error.name = "OpenOrderCancellationUnconfirmedError";
return error;
}
function isOpenOrderCancellationUnconfirmedError(error) {
return error?.name === "OpenOrderCancellationUnconfirmedError";
}
function getOpenOrderRowKey(cells, row) {
const cellText = cells.slice(0, 10).map((cell) => (cell.textContent || "").replace(/\s+/g, " ").trim()).join("|");
return cellText || (row.textContent || "").replace(/\s+/g, " ").trim();
Expand Down Expand Up @@ -7125,7 +7133,7 @@
throw new Error("撤销待替换挂单时交易对已变化");
}
if (outcome.status === "timeout") {
throw new Error("待替换挂单仍存在,已停止重新挂单");
throw createOpenOrderCancellationUnconfirmedError("待替换挂单仍存在,已停止重新挂单");
}
if (outcome.status === "dialog_open") {
const { dialog } = outcome;
Expand All @@ -7152,7 +7160,7 @@
previousKeyCount,
abortSignal
)) {
throw new Error("待替换挂单仍存在,已停止重新挂单");
throw createOpenOrderCancellationUnconfirmedError("待替换挂单仍存在,已停止重新挂单");
}
}
throwIfAborted(abortSignal);
Expand All @@ -7162,7 +7170,7 @@
previousKeyCount,
abortSignal
)) {
throw new Error("待替换挂单状态未稳定,已停止重新挂单");
throw createOpenOrderCancellationUnconfirmedError("待替换挂单状态未稳定,已停止重新挂单");
}
recordLadderCancelledOrder(progress);
return { status: "cancelled", qty: row.qty };
Expand Down Expand Up @@ -7201,6 +7209,7 @@
async function cancelFarthestOpenOrderRowsForPlan(root, plan, targets, progress, setExecutionStatus, abortSignal = null) {
let releasedCount = 0;
let cancelledCount = 0;
let unconfirmedCount = 0;
let currentRoot = root;
for (const target of targets) {
throwIfAborted(abortSignal);
Expand All @@ -7218,14 +7227,25 @@
row = completeRows.find((candidate) => candidate.key === target.key) || null;
}
if (row) {
const result = await cancelOneOpenOrderRowForPlan(
row,
plan,
progress,
setExecutionStatus,
abortSignal
);
if (result.status === "cancelled") cancelledCount += 1;
try {
const result = await cancelOneOpenOrderRowForPlan(
row,
plan,
progress,
setExecutionStatus,
abortSignal
);
if (result.status === "cancelled") cancelledCount += 1;
} catch (error) {
if (!isOpenOrderCancellationUnconfirmedError(error)) throw error;
unconfirmedCount += 1;
const detail2 = localizedText(
`已释放 ${releasedCount} 个挂单名额 · 当前撤单未确认,继续本轮`,
`Freed ${releasedCount} order slots · Current cancellation unconfirmed; continuing this round`
);
setExecutionStatus(combineLocalizedText([plan.spec.statusLabel, detail2], ":"), detail2);
break;
}
}
releasedCount += 1;
const detail = localizedText(
Expand All @@ -7234,7 +7254,7 @@
);
setExecutionStatus(combineLocalizedText([plan.spec.statusLabel, detail], ":"), detail);
}
return { ok: true, releasedCount, cancelledCount };
return { ok: true, releasedCount, cancelledCount, unconfirmedCount };
}
async function setHideOtherSymbolChecked(root, desiredChecked, symbol = getCurrentSymbol(), abortSignal = null) {
throwIfAborted(abortSignal);
Expand Down Expand Up @@ -8033,7 +8053,10 @@
abortSignal
);
throwIfAborted(abortSignal);
const completedDetail = localizedText(
const completedDetail = result.unconfirmedCount > 0 ? localizedText(
`已释放 ${result.releasedCount} 个挂单名额 · ${result.unconfirmedCount} 笔未确认,继续本轮`,
`Freed ${result.releasedCount} order slots · ${result.unconfirmedCount} unconfirmed; continuing this round`
) : localizedText(
`已释放 ${result.releasedCount} 个挂单名额,继续本轮`,
`Freed ${result.releasedCount} order slots; continuing this round`
);
Expand Down
63 changes: 46 additions & 17 deletions src/binance-orderbook-trade/index.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.173
// @version 2.7.174
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -4196,6 +4196,16 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
}));
}

function createOpenOrderCancellationUnconfirmedError(message) {
const error = new Error(message);
error.name = 'OpenOrderCancellationUnconfirmedError';
return error;
}

function isOpenOrderCancellationUnconfirmedError(error) {
return error?.name === 'OpenOrderCancellationUnconfirmedError';
}

function getOpenOrderRowKey(cells, row) {
const cellText = cells
.slice(0, 10)
Expand Down Expand Up @@ -4579,7 +4589,7 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
throw new Error('撤销待替换挂单时交易对已变化');
}
if (outcome.status === 'timeout') {
throw new Error('待替换挂单仍存在,已停止重新挂单');
throw createOpenOrderCancellationUnconfirmedError('待替换挂单仍存在,已停止重新挂单');
}
if (outcome.status === 'dialog_open') {
const { dialog } = outcome;
Expand All @@ -4606,7 +4616,7 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
previousKeyCount,
abortSignal,
))) {
throw new Error('待替换挂单仍存在,已停止重新挂单');
throw createOpenOrderCancellationUnconfirmedError('待替换挂单仍存在,已停止重新挂单');
}
}
throwIfAborted(abortSignal);
Expand All @@ -4616,7 +4626,7 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
previousKeyCount,
abortSignal,
))) {
throw new Error('待替换挂单状态未稳定,已停止重新挂单');
throw createOpenOrderCancellationUnconfirmedError('待替换挂单状态未稳定,已停止重新挂单');
}
recordLadderCancelledOrder(progress);
return { status: 'cancelled', qty: row.qty };
Expand Down Expand Up @@ -4670,6 +4680,7 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
) {
let releasedCount = 0;
let cancelledCount = 0;
let unconfirmedCount = 0;
let currentRoot = root;

for (const target of targets) {
Expand All @@ -4690,14 +4701,27 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
}

if (row) {
const result = await cancelOneOpenOrderRowForPlan(
row,
plan,
progress,
setExecutionStatus,
abortSignal,
);
if (result.status === 'cancelled') cancelledCount += 1;
try {
const result = await cancelOneOpenOrderRowForPlan(
row,
plan,
progress,
setExecutionStatus,
abortSignal,
);
if (result.status === 'cancelled') cancelledCount += 1;
} catch (error) {
if (!isOpenOrderCancellationUnconfirmedError(error)) throw error;
unconfirmedCount += 1;
const detail = localizedText(
`已释放 ${releasedCount} 个挂单名额 · 当前撤单未确认,继续本轮`,
`Freed ${releasedCount} order slots · Current cancellation unconfirmed; continuing this round`,
);
setExecutionStatus(combineLocalizedText([plan.spec.statusLabel, detail], ':'), detail);
// The confirmed slots already released may be enough for the unfinished ladder.
// Do not claim this row was cancelled or discard the completed submit progress.
break;
}
}
releasedCount += 1;
const detail = localizedText(
Expand All @@ -4706,7 +4730,7 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
);
setExecutionStatus(combineLocalizedText([plan.spec.statusLabel, detail], ':'), detail);
}
return { ok: true, releasedCount, cancelledCount };
return { ok: true, releasedCount, cancelledCount, unconfirmedCount };
}

async function setHideOtherSymbolChecked(
Expand Down Expand Up @@ -5618,10 +5642,15 @@ import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
abortSignal,
);
throwIfAborted(abortSignal);
const completedDetail = localizedText(
`已释放 ${result.releasedCount} 个挂单名额,继续本轮`,
`Freed ${result.releasedCount} order slots; continuing this round`,
);
const completedDetail = result.unconfirmedCount > 0
? localizedText(
`已释放 ${result.releasedCount} 个挂单名额 · ${result.unconfirmedCount} 笔未确认,继续本轮`,
`Freed ${result.releasedCount} order slots · ${result.unconfirmedCount} unconfirmed; continuing this round`,
)
: localizedText(
`已释放 ${result.releasedCount} 个挂单名额,继续本轮`,
`Freed ${result.releasedCount} order slots; continuing this round`,
);
setExecutionStatus(
combineLocalizedText([plan.spec.statusLabel, completedDetail], ':'),
completedDetail,
Expand Down
20 changes: 20 additions & 0 deletions test/unit/binance-orderbook-trade/source-regressions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,26 @@ test('continuous close recovers a confirmed max-open-orders rejection by freeing
assert.doesNotMatch(cancelPlanBody, /findCurrentSymbolCancelAllButton/);
});

test('capacity recovery skips an unconfirmed row cancellation without claiming the slot was released', () => {
const cancelOneRowBody = readFunctionBody('cancelOneOpenOrderRowForPlan');
const cancelFarthestBody = readFunctionBody('cancelFarthestOpenOrderRowsForPlan');

assert.match(cancelOneRowBody, /createOpenOrderCancellationUnconfirmedError\('待替换挂单仍存在,已停止重新挂单'\)/);
assert.match(cancelOneRowBody, /createOpenOrderCancellationUnconfirmedError\('待替换挂单状态未稳定,已停止重新挂单'\)/);
assert.match(cancelFarthestBody, /catch \(error\) \{[\s\S]*isOpenOrderCancellationUnconfirmedError\(error\)/);
assert.match(cancelFarthestBody, /unconfirmedCount \+= 1;[\s\S]*break;/);
assert.match(cancelFarthestBody, /releasedCount \+= 1;/);

const unconfirmedCatchStart = cancelFarthestBody.indexOf('catch (error) {');
const unconfirmedBreak = cancelFarthestBody.indexOf('break;', unconfirmedCatchStart);
const releasedCountIncrement = cancelFarthestBody.indexOf('releasedCount += 1;', unconfirmedBreak);
assert.ok(
unconfirmedCatchStart >= 0
&& unconfirmedCatchStart < unconfirmedBreak
&& unconfirmedBreak < releasedCountIncrement,
);
});

test('open and close ladders reprice only remaining orders after explicit maker conflicts', () => {
const retryBody = readFunctionBody('isRetryableLadderMakerPriceFailure');
assert.match(retryBody, /plan\?\.spec\?\.mode !== 'OPEN'/);
Expand Down
Loading