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: 1 addition & 3 deletions e2e/binance-orderbook/specs/control-flows.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,7 @@ test('starting a ladder preserves action slots and turns only the active action
const submissionsAfterStop = state.events
.filter((event) => event.type === 'order-submitted').length;
await expect(status).toHaveText(
submissionsAfterStop > 0
? `阶梯开多已停止 · 已挂 ${submissionsAfterStop} 笔`
: '阶梯开多已停止',
`阶梯开多已停止 · 已挂 ${submissionsAfterStop}/5 笔`,
);
expect(submissionsAfterStop).toBeLessThanOrEqual(submissionsBeforeStop + 1);
await page.waitForTimeout(700);
Expand Down
30 changes: 26 additions & 4 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.149
// @version 2.7.150
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -837,7 +837,7 @@

// 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) {
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");
}
}
Expand All @@ -854,7 +854,11 @@
function formatLadderProgressCounts(progress) {
assertLadderProgress(progress);
const counts = [];
if (progress.submittedOrders > 0) counts.push(`已挂 ${progress.submittedOrders} 笔`);
if (progress.plannedOrders !== null) {
counts.push(`已挂 ${progress.currentPlanSubmittedOrders}/${progress.plannedOrders} 笔`);
} else if (progress.submittedOrders > 0) {
counts.push(`已挂 ${progress.submittedOrders} 笔`);
}
if (progress.cancelledOrders > 0) counts.push(`已撤 ${progress.cancelledOrders} 笔`);
return counts;
}
Expand All @@ -865,12 +869,26 @@
function createLadderProgress() {
return {
submittedOrders: 0,
cancelledOrders: 0
cancelledOrders: 0,
plannedOrders: null,
currentPlanSubmittedOrders: 0
};
}
function setLadderPlannedOrders(progress, plannedOrders) {
assertLadderProgress(progress);
if (!Number.isInteger(plannedOrders) || plannedOrders <= 0) {
throw new Error("Invalid ladder planned orders");
}
progress.plannedOrders = plannedOrders;
progress.currentPlanSubmittedOrders = 0;
}
function recordLadderSubmittedOrder(progress) {
assertLadderProgress(progress);
if (progress.plannedOrders !== null && progress.currentPlanSubmittedOrders >= progress.plannedOrders) {
throw new Error("Ladder submitted orders exceed plan");
}
progress.submittedOrders += 1;
if (progress.plannedOrders !== null) progress.currentPlanSubmittedOrders += 1;
}
function recordLadderCancelledOrder(progress) {
assertLadderProgress(progress);
Expand Down Expand Up @@ -899,6 +917,9 @@
if (completedOrders !== totalOrders) {
throw new Error("Completed ladder progress mismatch");
}
if (progress.plannedOrders !== totalOrders || progress.currentPlanSubmittedOrders !== completedOrders) {
throw new Error("Completed ladder progress mismatch");
}
const cancelledText = progress.cancelledOrders > 0 ? ` · 已撤 ${progress.cancelledOrders} 笔` : "";
return `${label}已完成 · 已挂 ${completedOrders}/${totalOrders} 笔${cancelledText}`;
}
Expand Down Expand Up @@ -5958,6 +5979,7 @@
let plan = null;
try {
plan = await buildLadderPlan(actionType, replacementContext);
setLadderPlannedOrders(progress, plan.orders.length);
throwIfAborted(abortSignal);
setLadderStatus(formatLadderPlanStatus(plan));
const execution = await executeLadderPlan(plan, progress, abortSignal);
Expand Down
41 changes: 40 additions & 1 deletion src/binance-orderbook-trade/core/ladder-progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ function assertLadderProgress(progress) {
|| 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');
}
Expand All @@ -25,7 +36,11 @@ function assertLadderMessage(message) {
function formatLadderProgressCounts(progress) {
assertLadderProgress(progress);
const counts = [];
if (progress.submittedOrders > 0) counts.push(`已挂 ${progress.submittedOrders} 笔`);
if (progress.plannedOrders !== null) {
counts.push(`已挂 ${progress.currentPlanSubmittedOrders}/${progress.plannedOrders} 笔`);
} else if (progress.submittedOrders > 0) {
counts.push(`已挂 ${progress.submittedOrders} 笔`);
}
if (progress.cancelledOrders > 0) counts.push(`已撤 ${progress.cancelledOrders} 笔`);
return counts;
}
Expand All @@ -42,12 +57,30 @@ export function createLadderProgress() {
return {
submittedOrders: 0,
cancelledOrders: 0,
plannedOrders: null,
currentPlanSubmittedOrders: 0,
};
}

export function setLadderPlannedOrders(progress, plannedOrders) {
assertLadderProgress(progress);
if (!Number.isInteger(plannedOrders) || plannedOrders <= 0) {
throw new Error('Invalid ladder planned orders');
}
progress.plannedOrders = plannedOrders;
progress.currentPlanSubmittedOrders = 0;
}

export function recordLadderSubmittedOrder(progress) {
assertLadderProgress(progress);
if (
progress.plannedOrders !== null
&& progress.currentPlanSubmittedOrders >= progress.plannedOrders
) {
throw new Error('Ladder submitted orders exceed plan');
}
progress.submittedOrders += 1;
if (progress.plannedOrders !== null) progress.currentPlanSubmittedOrders += 1;
}

export function recordLadderCancelledOrder(progress) {
Expand Down Expand Up @@ -86,6 +119,12 @@ export function formatCompletedLadderProgress(label, completedOrders, totalOrder
if (completedOrders !== totalOrders) {
throw new Error('Completed ladder progress mismatch');
}
if (
progress.plannedOrders !== totalOrders
|| progress.currentPlanSubmittedOrders !== completedOrders
) {
throw new Error('Completed ladder progress mismatch');
}
const cancelledText = progress.cancelledOrders > 0
? ` · 已撤 ${progress.cancelledOrders} 笔`
: '';
Expand Down
4 changes: 3 additions & 1 deletion 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.149
// @version 2.7.150
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -85,6 +85,7 @@ import {
formatStoppedLadderProgress,
recordLadderCancelledOrder,
recordLadderSubmittedOrder,
setLadderPlannedOrders,
} from './core/ladder-progress.js';
import {
throwIfAborted,
Expand Down Expand Up @@ -4503,6 +4504,7 @@ import {
let plan = null;
try {
plan = await buildLadderPlan(actionType, replacementContext);
setLadderPlannedOrders(progress, plan.orders.length);
throwIfAborted(abortSignal);
setLadderStatus(formatLadderPlanStatus(plan));
const execution = await executeLadderPlan(plan, progress, abortSignal);
Expand Down
69 changes: 61 additions & 8 deletions test/unit/binance-orderbook-trade/ladder-progress.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ import {
formatStoppedLadderProgress,
recordLadderCancelledOrder,
recordLadderSubmittedOrder,
setLadderPlannedOrders,
} from '../../../src/binance-orderbook-trade/core/ladder-progress.js';

test('stopped ladder status reports confirmed submitted and cancelled orders', () => {
const progress = createLadderProgress();

setLadderPlannedOrders(progress, 5);
recordLadderSubmittedOrder(progress);
recordLadderSubmittedOrder(progress);
recordLadderCancelledOrder(progress);

assert.deepEqual(progress, {
submittedOrders: 2,
cancelledOrders: 1,
plannedOrders: 5,
currentPlanSubmittedOrders: 2,
});
assert.equal(
formatStoppedLadderProgress('阶梯开空', progress),
'阶梯开空已停止 · 已挂 2 笔 · 已撤 1 笔',
'阶梯开空已停止 · 已挂 2/5 笔 · 已撤 1 笔',
);
});

Expand All @@ -35,10 +39,18 @@ test('stopped ladder status omits counters for actions that did not happen', ()
);

const submittedOnly = createLadderProgress();
setLadderPlannedOrders(submittedOnly, 3);
recordLadderSubmittedOrder(submittedOnly);
assert.equal(
formatStoppedLadderProgress('阶梯平空', submittedOnly),
'阶梯平空已停止 · 已挂 1 笔',
'阶梯平空已停止 · 已挂 1/3 笔',
);

const stoppedBeforeFirstOrder = createLadderProgress();
setLadderPlannedOrders(stoppedBeforeFirstOrder, 5);
assert.equal(
formatStoppedLadderProgress('阶梯开空', stoppedBeforeFirstOrder),
'阶梯开空已停止 · 已挂 0/5 笔',
);

const cancelledOnly = createLadderProgress();
Expand All @@ -51,34 +63,37 @@ test('stopped ladder status omits counters for actions that did not happen', ()

test('completed ladder status names the action and confirmed result', () => {
const progress = createLadderProgress();
setLadderPlannedOrders(progress, 5);
for (let index = 0; index < 5; index += 1) recordLadderSubmittedOrder(progress);

assert.equal(
formatCompletedLadderProgress('阶梯平空', 5, 5, progress),
'阶梯平空已完成 · 已挂 5/5 笔',
);

recordLadderSubmittedOrder(progress);
recordLadderSubmittedOrder(progress);
recordLadderCancelledOrder(progress);
const progressWithCancellation = createLadderProgress();
setLadderPlannedOrders(progressWithCancellation, 5);
for (let index = 0; index < 5; index += 1) recordLadderSubmittedOrder(progressWithCancellation);
recordLadderCancelledOrder(progressWithCancellation);
assert.equal(
formatCompletedLadderProgress('阶梯平空', 5, 5, progress),
formatCompletedLadderProgress('阶梯平空', 5, 5, progressWithCancellation),
'阶梯平空已完成 · 已挂 5/5 笔 · 已撤 1 笔',
);
});

test('failed and interrupted ladder statuses retain confirmed progress', () => {
const progress = createLadderProgress();
setLadderPlannedOrders(progress, 5);
recordLadderSubmittedOrder(progress);
recordLadderCancelledOrder(progress);

assert.equal(
formatFailedLadderProgress('阶梯开多', '数量框状态未稳定', progress),
'阶梯开多失败:数量框状态未稳定 · 已挂 1 笔 · 已撤 1 笔',
'阶梯开多失败:数量框状态未稳定 · 已挂 1/5 笔 · 已撤 1 笔',
);
assert.equal(
formatInterruptedLadderProgress('阶梯开多', '交易对已切换', progress),
'阶梯开多已中止:交易对已切换 · 已挂 1 笔 · 已撤 1 笔',
'阶梯开多已中止:交易对已切换 · 已挂 1/5 笔 · 已撤 1 笔',
);

assert.equal(
Expand All @@ -104,7 +119,45 @@ test('ladder progress rejects invalid counters instead of masking them', () => {
() => formatCompletedLadderProgress('阶梯开多', 1, 2, {
submittedOrders: 1,
cancelledOrders: 0,
plannedOrders: 2,
currentPlanSubmittedOrders: 1,
}),
/Completed ladder progress mismatch/,
);

assert.throws(
() => setLadderPlannedOrders(createLadderProgress(), 0),
/Invalid ladder planned orders/,
);

const completedProgress = createLadderProgress();
setLadderPlannedOrders(completedProgress, 1);
recordLadderSubmittedOrder(completedProgress);
assert.throws(
() => recordLadderSubmittedOrder(completedProgress),
/Ladder submitted orders exceed plan/,
);
});

test('a replacement plan resets only the ratio numerator and retains cumulative activity', () => {
const progress = createLadderProgress();
setLadderPlannedOrders(progress, 5);
recordLadderSubmittedOrder(progress);
recordLadderSubmittedOrder(progress);
recordLadderCancelledOrder(progress);
recordLadderCancelledOrder(progress);

setLadderPlannedOrders(progress, 3);
recordLadderSubmittedOrder(progress);

assert.equal(
formatStoppedLadderProgress('阶梯平空', progress),
'阶梯平空已停止 · 已挂 1/3 笔 · 已撤 2 笔',
);
assert.deepEqual(progress, {
submittedOrders: 3,
cancelledOrders: 2,
plannedOrders: 3,
currentPlanSubmittedOrders: 1,
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ test('stopping a ladder preserves confirmed submit and cancel progress', () => {
assert.match(startBody, /runLadderPlanWithOpenOrderReplacement\(\s*actionType,\s*progress,\s*abortController\.signal/);
assert.match(startBody, /isLadderStoppedError\(e\)[\s\S]*formatStoppedLadderProgress\(spec\.label,\s*progress\)/);
assert.match(startBody, /formatCompletedLadderProgress\(\s*spec\.label,\s*done,\s*plan\.orders\.length,\s*progress/);
assert.match(runBody, /buildLadderPlan\(actionType,\s*replacementContext\)[\s\S]*setLadderPlannedOrders\(progress,\s*plan\.orders\.length\)/);
assert.match(runBody, /executeLadderPlan\(plan,\s*progress,\s*abortSignal\)/);
assert.match(runBody, /cancelCurrentSymbolOpenOrdersForPlan\(\s*replacementPlan,\s*progress,\s*abortSignal/);
assert.match(executeBody, /waitForOrderSubmitAcknowledgement\([\s\S]*recordLadderSubmittedOrder\(progress\)/);
Expand Down
Loading