From 0c89386372a3b7e802f5532459cd2d9ed7974192 Mon Sep 17 00:00:00 2001
From: LiZhenhai-MBP14 <5935568+jackhai9@users.noreply.github.com>
Date: Sun, 30 Aug 2026 12:35:35 +0800
Subject: [PATCH 1/2] fix: clarify continuous ladder wait state
---
docs/binance-orderbook-trade-development.md | 3 +
scripts/binance-orderbook-trade.user.js | 55 +++++++++++++++++--
.../contracts/panel-copy.js | 4 ++
.../core/continuous-ladder.js | 32 +++++++++++
src/binance-orderbook-trade/index.user.js | 16 +++++-
.../continuous-ladder.test.js | 29 ++++++++++
.../panel-copy.test.js | 4 ++
.../source-regressions.test.js | 3 +
8 files changed, 138 insertions(+), 8 deletions(-)
diff --git a/docs/binance-orderbook-trade-development.md b/docs/binance-orderbook-trade-development.md
index b300218..d0e9cda 100644
--- a/docs/binance-orderbook-trade-development.md
+++ b/docs/binance-orderbook-trade-development.md
@@ -159,6 +159,8 @@ Continuous ladder trading is available only for close actions through `Option/Al
Continuous-session feedback stays in the shared ladder status row and uses compact Chinese counters. `2/3 轮` means two rounds completed out of three started, `本轮 1/3 笔` reports the latest partial plan, and `累计 7 笔` reports all confirmed submissions across the session. Confirmed cancellations are appended only when greater than zero. Round outcomes must expose a detached progress snapshot so a terminal continuous summary cannot be overwritten by the latest single-round message.
+The active continuous-close control keeps a direction-specific stop action (`停止连续平多` / `停止连续平空`) throughout both execution and inter-round waiting. Before the native submit control is ready, the status appends `等待按钮恢复`. Only after the fixed cooldown actually begins may it append `等待 1s 后继续下一轮`; this is a static duration label, not a countdown. The button must not temporarily revert to a ladder-start action between rounds.
+
When selecting account-order tabs, scope to the bottom account-orders tab group. Do not globally match `当前委托` or `Open Orders`.
When a pane is found through `aria-controls`, confirm it contains current-orders controls such as `隐藏其他合约` or `全撤`. Binance may reuse pane ids in unrelated tab systems.
@@ -199,6 +201,7 @@ Run manual checks when behavior touches trading flow, DOM selectors, account ord
- Option/Alt-click a close ladder button, confirm the next round starts only after the prior round is complete, the native close action is ready, and one full second has elapsed; change ratio, levels, row span, and precision during the wait and confirm the next plan uses the updated profile
- during a continuous close cooldown, make the native close action temporarily unavailable and confirm the cooldown restarts only after readiness returns; confirm Stop also aborts the cooldown immediately
- complete multiple continuous close rounds, then stop both during a round and during cooldown; confirm the Chinese status shows completed/started rounds, latest-round order ratio, cumulative confirmed submissions, and no zero-cancellation segment
+- confirm continuous close keeps the direction-specific stop button between rounds, distinguishes `等待按钮恢复` from `等待 1s 后继续下一轮`, and never exposes a start button during the cooldown
- cancel current-symbol orders, verify only Binance native confirmation opens
- keep the native cancel-all dialog open for longer than the former decision deadline, verify the script remains in the dialog-tracking state, then cancel and confirm the original order count and temporary page state are restored
- cancel the native cancel-all dialog through its secondary button, Escape, and backdrop; verify chart and account-order UI state restores immediately without waiting for order clearing
diff --git a/scripts/binance-orderbook-trade.user.js b/scripts/binance-orderbook-trade.user.js
index 9b40322..b631653 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.157
+// @version 2.7.158
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
@@ -153,6 +153,10 @@
OPEN_SHORT: "停止开空",
CLOSE_LONG: "停止平多",
CLOSE_SHORT: "停止平空"
+ }),
+ stopContinuousLadderByAction: freezeCopy({
+ CLOSE_LONG: "停止连续平多",
+ CLOSE_SHORT: "停止连续平空"
})
}),
status: freezeCopy({
@@ -1038,6 +1042,15 @@
if (reason !== null) parts.push(reason);
return parts.join(" · ");
}
+ function formatContinuousLadderWaitReason(phase, cooldownMs) {
+ if (!Number.isFinite(cooldownMs) || cooldownMs < 0) {
+ throw new Error("Invalid continuous ladder cooldown");
+ }
+ if (phase === "waiting_ready") return "等待按钮恢复";
+ if (phase !== "cooldown") throw new Error("Invalid continuous ladder wait phase");
+ const duration = cooldownMs % 1e3 === 0 ? `${cooldownMs / 1e3}s` : `${cooldownMs}ms`;
+ return `等待 ${duration} 后继续下一轮`;
+ }
function assertReadinessState(state) {
if (!["ready", "waiting", "stopped"].includes(state?.status)) {
throw new Error("Invalid continuous ladder readiness state");
@@ -1048,12 +1061,20 @@
readReadiness,
delay,
signal,
- readyCheckMs
+ readyCheckMs,
+ cooldownMs,
+ onWaitStateChange,
+ waitingAlreadyReported
}) {
+ let reported = waitingAlreadyReported;
while (true) {
throwIfAborted(signal);
const state = assertReadinessState(readReadiness());
if (state.status !== "waiting") return state;
+ if (!reported) {
+ onWaitStateChange({ phase: "waiting_ready", cooldownMs });
+ reported = true;
+ }
await waitForPromiseOrAbort(delay(readyCheckMs), signal);
}
}
@@ -1062,22 +1083,35 @@
delay,
signal = null,
cooldownMs = CONTINUOUS_LADDER_COOLDOWN_MS,
- readyCheckMs = CONTINUOUS_LADDER_READY_CHECK_MS
+ 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 (typeof onWaitStateChange !== "function") {
+ throw new Error("Invalid continuous ladder wait-state callback");
+ }
+ let waitingAlreadyReported = false;
while (true) {
const readyState = await waitUntilReadyOrStopped({
readReadiness,
delay,
signal,
- readyCheckMs
+ readyCheckMs,
+ cooldownMs,
+ onWaitStateChange,
+ waitingAlreadyReported
});
if (readyState.status === "stopped") return readyState;
+ waitingAlreadyReported = false;
+ onWaitStateChange({ phase: "cooldown", cooldownMs });
await waitForPromiseOrAbort(delay(cooldownMs), signal);
throwIfAborted(signal);
const afterCooldown = assertReadinessState(readReadiness());
if (afterCooldown.status !== "waiting") return afterCooldown;
+ onWaitStateChange({ phase: "waiting_ready", cooldownMs });
+ waitingAlreadyReported = true;
}
}
@@ -4776,7 +4810,15 @@
const readiness = await waitForContinuousLadderNextRound({
readReadiness: () => readContinuousLadderReadiness(actionType, actionSymbol),
delay,
- signal: abortController.signal
+ signal: abortController.signal,
+ onWaitStateChange: ({ phase, cooldownMs }) => {
+ setContinuousLadderProgressStatus(
+ spec.label,
+ "running",
+ continuousProgress,
+ formatContinuousLadderWaitReason(phase, cooldownMs)
+ );
+ }
});
if (readiness.status === "stopped") {
setContinuousLadderProgressStatus(
@@ -7016,7 +7058,8 @@
if (activeActionType !== actionType) {
return ladderActionButton(actionType, label, tone, disabled);
}
- return ``;
+ const stopLabel = activeContinuousLadderActionType === actionType ? PANEL_COPY.action.stopContinuousLadderByAction[actionType] : PANEL_COPY.action.stopLadderByAction[actionType];
+ return ``;
}
function getLadderControlSections(tradeMode, closeContext, symbol, precision) {
const ladderRunning = !!ladderTask || !!continuousLadderTask;
diff --git a/src/binance-orderbook-trade/contracts/panel-copy.js b/src/binance-orderbook-trade/contracts/panel-copy.js
index 91a8176..86d1475 100644
--- a/src/binance-orderbook-trade/contracts/panel-copy.js
+++ b/src/binance-orderbook-trade/contracts/panel-copy.js
@@ -29,6 +29,10 @@ export const PANEL_COPY = Object.freeze({
CLOSE_LONG: '停止平多',
CLOSE_SHORT: '停止平空',
}),
+ stopContinuousLadderByAction: freezeCopy({
+ CLOSE_LONG: '停止连续平多',
+ CLOSE_SHORT: '停止连续平空',
+ }),
}),
status: freezeCopy({
precisionUpdated: '精度推荐已更新',
diff --git a/src/binance-orderbook-trade/core/continuous-ladder.js b/src/binance-orderbook-trade/core/continuous-ladder.js
index 74068d5..df87a5f 100644
--- a/src/binance-orderbook-trade/core/continuous-ladder.js
+++ b/src/binance-orderbook-trade/core/continuous-ladder.js
@@ -97,6 +97,18 @@ export function formatContinuousLadderProgress(label, phase, progress, reason =
return parts.join(' · ');
}
+export function formatContinuousLadderWaitReason(phase, cooldownMs) {
+ if (!Number.isFinite(cooldownMs) || cooldownMs < 0) {
+ throw new Error('Invalid continuous ladder cooldown');
+ }
+ if (phase === 'waiting_ready') return '等待按钮恢复';
+ if (phase !== 'cooldown') throw new Error('Invalid continuous ladder wait phase');
+ const duration = cooldownMs % 1000 === 0
+ ? `${cooldownMs / 1000}s`
+ : `${cooldownMs}ms`;
+ return `等待 ${duration} 后继续下一轮`;
+}
+
function assertReadinessState(state) {
if (!['ready', 'waiting', 'stopped'].includes(state?.status)) {
throw new Error('Invalid continuous ladder readiness state');
@@ -109,11 +121,19 @@ async function waitUntilReadyOrStopped({
delay,
signal,
readyCheckMs,
+ cooldownMs,
+ onWaitStateChange,
+ waitingAlreadyReported,
}) {
+ let reported = waitingAlreadyReported;
while (true) {
throwIfAborted(signal);
const state = assertReadinessState(readReadiness());
if (state.status !== 'waiting') return state;
+ if (!reported) {
+ onWaitStateChange({ phase: 'waiting_ready', cooldownMs });
+ reported = true;
+ }
await waitForPromiseOrAbort(delay(readyCheckMs), signal);
}
}
@@ -128,22 +148,34 @@ export async function waitForContinuousLadderNextRound({
signal = null,
cooldownMs = CONTINUOUS_LADDER_COOLDOWN_MS,
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 (typeof onWaitStateChange !== 'function') {
+ throw new Error('Invalid continuous ladder wait-state callback');
+ }
+ let waitingAlreadyReported = false;
while (true) {
const readyState = await waitUntilReadyOrStopped({
readReadiness,
delay,
signal,
readyCheckMs,
+ cooldownMs,
+ onWaitStateChange,
+ waitingAlreadyReported,
});
if (readyState.status === 'stopped') return readyState;
+ waitingAlreadyReported = false;
+ onWaitStateChange({ phase: 'cooldown', cooldownMs });
await waitForPromiseOrAbort(delay(cooldownMs), signal);
throwIfAborted(signal);
const afterCooldown = assertReadinessState(readReadiness());
if (afterCooldown.status !== 'waiting') return afterCooldown;
+ onWaitStateChange({ phase: 'waiting_ready', cooldownMs });
+ waitingAlreadyReported = true;
}
}
diff --git a/src/binance-orderbook-trade/index.user.js b/src/binance-orderbook-trade/index.user.js
index f4f6caa..dc6fcc9 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.157
+// @version 2.7.158
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
@@ -80,6 +80,7 @@ import { keepInteractionFeedbackVisible } from './core/interaction-feedback.js';
import {
createContinuousLadderProgress,
formatContinuousLadderProgress,
+ formatContinuousLadderWaitReason,
recordContinuousLadderRound,
waitForContinuousLadderNextRound,
} from './core/continuous-ladder.js';
@@ -2930,6 +2931,14 @@ import {
readReadiness: () => readContinuousLadderReadiness(actionType, actionSymbol),
delay,
signal: abortController.signal,
+ onWaitStateChange: ({ phase, cooldownMs }) => {
+ setContinuousLadderProgressStatus(
+ spec.label,
+ 'running',
+ continuousProgress,
+ formatContinuousLadderWaitReason(phase, cooldownMs),
+ );
+ },
});
if (readiness.status === 'stopped') {
setContinuousLadderProgressStatus(
@@ -5548,7 +5557,10 @@ import {
if (activeActionType !== actionType) {
return ladderActionButton(actionType, label, tone, disabled);
}
- return ``;
+ const stopLabel = activeContinuousLadderActionType === actionType
+ ? PANEL_COPY.action.stopContinuousLadderByAction[actionType]
+ : PANEL_COPY.action.stopLadderByAction[actionType];
+ return ``;
}
function getLadderControlSections(tradeMode, closeContext, symbol, precision) {
diff --git a/test/unit/binance-orderbook-trade/continuous-ladder.test.js b/test/unit/binance-orderbook-trade/continuous-ladder.test.js
index a3f11bb..70db50b 100644
--- a/test/unit/binance-orderbook-trade/continuous-ladder.test.js
+++ b/test/unit/binance-orderbook-trade/continuous-ladder.test.js
@@ -6,6 +6,7 @@ import {
CONTINUOUS_LADDER_READY_CHECK_MS,
createContinuousLadderProgress,
formatContinuousLadderProgress,
+ formatContinuousLadderWaitReason,
recordContinuousLadderRound,
waitForContinuousLadderNextRound,
} from '../../../src/binance-orderbook-trade/core/continuous-ladder.js';
@@ -126,10 +127,12 @@ test('continuous ladder starts its cooldown only after the previous round is rea
{ status: 'ready' },
];
const delays = [];
+ const waitStates = [];
const result = await waitForContinuousLadderNextRound({
readReadiness: () => states.shift(),
delay: async (ms) => delays.push(ms),
+ onWaitStateChange: (state) => waitStates.push(state),
});
assert.deepEqual(result, { status: 'ready' });
@@ -138,6 +141,10 @@ test('continuous ladder starts its cooldown only after the previous round is rea
CONTINUOUS_LADDER_READY_CHECK_MS,
CONTINUOUS_LADDER_COOLDOWN_MS,
]);
+ assert.deepEqual(waitStates, [
+ { phase: 'waiting_ready', cooldownMs: CONTINUOUS_LADDER_COOLDOWN_MS },
+ { phase: 'cooldown', cooldownMs: CONTINUOUS_LADDER_COOLDOWN_MS },
+ ]);
});
test('continuous ladder restarts a full cooldown if readiness is lost', async () => {
@@ -149,10 +156,12 @@ test('continuous ladder restarts a full cooldown if readiness is lost', async ()
{ status: 'ready' },
];
const delays = [];
+ const waitStates = [];
const result = await waitForContinuousLadderNextRound({
readReadiness: () => states.shift(),
delay: async (ms) => delays.push(ms),
+ onWaitStateChange: (state) => waitStates.push(state),
});
assert.deepEqual(result, { status: 'ready' });
@@ -161,6 +170,26 @@ test('continuous ladder restarts a full cooldown if readiness is lost', async ()
CONTINUOUS_LADDER_READY_CHECK_MS,
CONTINUOUS_LADDER_COOLDOWN_MS,
]);
+ assert.deepEqual(waitStates, [
+ { phase: 'cooldown', cooldownMs: CONTINUOUS_LADDER_COOLDOWN_MS },
+ { phase: 'waiting_ready', cooldownMs: CONTINUOUS_LADDER_COOLDOWN_MS },
+ { phase: 'cooldown', cooldownMs: CONTINUOUS_LADDER_COOLDOWN_MS },
+ ]);
+});
+
+test('continuous ladder wait reasons distinguish readiness from the actual cooldown', () => {
+ assert.equal(
+ formatContinuousLadderWaitReason('waiting_ready', CONTINUOUS_LADDER_COOLDOWN_MS),
+ '等待按钮恢复',
+ );
+ assert.equal(
+ formatContinuousLadderWaitReason('cooldown', CONTINUOUS_LADDER_COOLDOWN_MS),
+ '等待 1s 后继续下一轮',
+ );
+ assert.throws(
+ () => formatContinuousLadderWaitReason('unknown', CONTINUOUS_LADDER_COOLDOWN_MS),
+ /Invalid continuous ladder wait phase/,
+ );
});
test('continuous ladder returns a terminal readiness state without another cooldown', async () => {
diff --git a/test/unit/binance-orderbook-trade/panel-copy.test.js b/test/unit/binance-orderbook-trade/panel-copy.test.js
index 58ac4fd..f36f3b9 100644
--- a/test/unit/binance-orderbook-trade/panel-copy.test.js
+++ b/test/unit/binance-orderbook-trade/panel-copy.test.js
@@ -36,6 +36,10 @@ test('panel copy keeps the approved labels and tooltips in one contract', () =>
CLOSE_LONG: '停止平多',
CLOSE_SHORT: '停止平空',
},
+ stopContinuousLadderByAction: {
+ CLOSE_LONG: '停止连续平多',
+ CLOSE_SHORT: '停止连续平空',
+ },
},
status: {
precisionUpdated: '精度推荐已更新',
diff --git a/test/unit/binance-orderbook-trade/source-regressions.test.js b/test/unit/binance-orderbook-trade/source-regressions.test.js
index e4257c4..317ab8e 100644
--- a/test/unit/binance-orderbook-trade/source-regressions.test.js
+++ b/test/unit/binance-orderbook-trade/source-regressions.test.js
@@ -629,6 +629,8 @@ test('Option or Alt click continuously repeats close ladders only after readines
assert.match(continuousBody, /setContinuousLadderProgressStatus\(/);
assert.match(continuousBody, /await waitForContinuousLadderNextRound\(/);
assert.match(continuousBody, /readContinuousLadderReadiness\(actionType, actionSymbol\)/);
+ assert.match(continuousBody, /onWaitStateChange:/);
+ assert.match(continuousBody, /formatContinuousLadderWaitReason\(phase, cooldownMs\)/);
assert.match(readinessBody, /isCurrentObservedSymbol\(actionSymbol\)/);
assert.match(readinessBody, /getActiveTradeMode\(\) !== spec\.mode/);
@@ -642,6 +644,7 @@ test('Option or Alt click continuously repeats close ladders only after readines
assert.match(actionButtonBody, /Option\/Alt \+ 单击:连续交易/);
assert.doesNotMatch(source, /Continuous trading (?:stopped|failed)/);
assert.match(executionButtonBody, /activeLadderActionType \|\| activeContinuousLadderActionType/);
+ assert.match(executionButtonBody, /PANEL_COPY\.action\.stopContinuousLadderByAction\[actionType\]/);
assert.match(controlSectionsBody, /!!ladderTask \|\| !!continuousLadderTask/);
});
From a3c07f0a1ddc020104bc86a0e069da5505003698 Mon Sep 17 00:00:00 2001
From: LiZhenhai-MBP14 <5935568+jackhai9@users.noreply.github.com>
Date: Sun, 30 Aug 2026 12:39:01 +0800
Subject: [PATCH 2/2] fix: prioritize continuous wait feedback
---
docs/binance-orderbook-trade-development.md | 4 +-
scripts/binance-orderbook-trade.user.js | 27 ++++++++----
.../core/continuous-ladder.js | 21 +++++++---
src/binance-orderbook-trade/index.user.js | 10 ++---
.../continuous-ladder.test.js | 42 ++++++++++++++++++-
.../source-regressions.test.js | 2 +-
6 files changed, 83 insertions(+), 23 deletions(-)
diff --git a/docs/binance-orderbook-trade-development.md b/docs/binance-orderbook-trade-development.md
index d0e9cda..c203035 100644
--- a/docs/binance-orderbook-trade-development.md
+++ b/docs/binance-orderbook-trade-development.md
@@ -159,7 +159,7 @@ Continuous ladder trading is available only for close actions through `Option/Al
Continuous-session feedback stays in the shared ladder status row and uses compact Chinese counters. `2/3 轮` means two rounds completed out of three started, `本轮 1/3 笔` reports the latest partial plan, and `累计 7 笔` reports all confirmed submissions across the session. Confirmed cancellations are appended only when greater than zero. Round outcomes must expose a detached progress snapshot so a terminal continuous summary cannot be overwritten by the latest single-round message.
-The active continuous-close control keeps a direction-specific stop action (`停止连续平多` / `停止连续平空`) throughout both execution and inter-round waiting. Before the native submit control is ready, the status appends `等待按钮恢复`. Only after the fixed cooldown actually begins may it append `等待 1s 后继续下一轮`; this is a static duration label, not a countdown. The button must not temporarily revert to a ladder-start action between rounds.
+The active continuous-close control keeps a direction-specific stop action (`停止连续平多` / `停止连续平空`) throughout both execution and inter-round waiting. Before the native submit control is ready, the status places `等待按钮恢复` immediately after the continuous-state label. Only after the fixed cooldown actually begins may it show `1s 后继续` in that same priority position; this is a static duration label, not a countdown. The button must not temporarily revert to a ladder-start action between rounds.
When selecting account-order tabs, scope to the bottom account-orders tab group. Do not globally match `当前委托` or `Open Orders`.
@@ -201,7 +201,7 @@ Run manual checks when behavior touches trading flow, DOM selectors, account ord
- Option/Alt-click a close ladder button, confirm the next round starts only after the prior round is complete, the native close action is ready, and one full second has elapsed; change ratio, levels, row span, and precision during the wait and confirm the next plan uses the updated profile
- during a continuous close cooldown, make the native close action temporarily unavailable and confirm the cooldown restarts only after readiness returns; confirm Stop also aborts the cooldown immediately
- complete multiple continuous close rounds, then stop both during a round and during cooldown; confirm the Chinese status shows completed/started rounds, latest-round order ratio, cumulative confirmed submissions, and no zero-cancellation segment
-- confirm continuous close keeps the direction-specific stop button between rounds, distinguishes `等待按钮恢复` from `等待 1s 后继续下一轮`, and never exposes a start button during the cooldown
+- confirm continuous close keeps the direction-specific stop button between rounds, places `等待按钮恢复` or `1s 后继续` before the progress counters, and never exposes a start button during the cooldown
- cancel current-symbol orders, verify only Binance native confirmation opens
- keep the native cancel-all dialog open for longer than the former decision deadline, verify the script remains in the dialog-tracking state, then cancel and confirm the original order count and temporary page state are restored
- cancel the native cancel-all dialog through its secondary button, Escape, and backdrop; verify chart and account-order UI state restores immediately without waiting for order clearing
diff --git a/scripts/binance-orderbook-trade.user.js b/scripts/binance-orderbook-trade.user.js
index b631653..e2a6acd 100644
--- a/scripts/binance-orderbook-trade.user.js
+++ b/scripts/binance-orderbook-trade.user.js
@@ -1018,15 +1018,12 @@
recordedRoundOutcomes.add(outcome);
assertContinuousLadderProgress(progress);
}
- function formatContinuousLadderProgress(label, phase, progress, reason = null) {
+ function buildContinuousLadderProgressParts(label, phase, progress) {
if (typeof label !== "string" || label.trim() === "") {
throw new Error("Invalid continuous ladder label");
}
const phaseText = CONTINUOUS_LADDER_PHASE_TEXT[phase];
if (!phaseText) throw new Error("Invalid continuous ladder phase");
- if (reason !== null && (typeof reason !== "string" || reason.trim() === "")) {
- throw new Error("Invalid continuous ladder reason");
- }
assertContinuousLadderProgress(progress);
const parts = [`${label}${phaseText}`];
parts.push(progress.startedRounds === 0 ? "0 轮" : `${progress.completedRounds}/${progress.startedRounds} 轮`);
@@ -1039,6 +1036,13 @@
}
parts.push(`累计 ${progress.submittedOrders} 笔`);
if (progress.cancelledOrders > 0) parts.push(`撤 ${progress.cancelledOrders} 笔`);
+ return parts;
+ }
+ function formatContinuousLadderProgress(label, phase, progress, reason = null) {
+ if (reason !== null && (typeof reason !== "string" || reason.trim() === "")) {
+ throw new Error("Invalid continuous ladder reason");
+ }
+ const parts = buildContinuousLadderProgressParts(label, phase, progress);
if (reason !== null) parts.push(reason);
return parts.join(" · ");
}
@@ -1049,7 +1053,12 @@
if (phase === "waiting_ready") return "等待按钮恢复";
if (phase !== "cooldown") throw new Error("Invalid continuous ladder wait phase");
const duration = cooldownMs % 1e3 === 0 ? `${cooldownMs / 1e3}s` : `${cooldownMs}ms`;
- return `等待 ${duration} 后继续下一轮`;
+ return `${duration} 后继续`;
+ }
+ function formatContinuousLadderWaitProgress(label, progress, phase, cooldownMs) {
+ const parts = buildContinuousLadderProgressParts(label, "running", progress);
+ parts.splice(1, 0, formatContinuousLadderWaitReason(phase, cooldownMs));
+ return parts.join(" · ");
}
function assertReadinessState(state) {
if (!["ready", "waiting", "stopped"].includes(state?.status)) {
@@ -4812,12 +4821,12 @@
delay,
signal: abortController.signal,
onWaitStateChange: ({ phase, cooldownMs }) => {
- setContinuousLadderProgressStatus(
+ setLadderStatus(formatContinuousLadderWaitProgress(
spec.label,
- "running",
continuousProgress,
- formatContinuousLadderWaitReason(phase, cooldownMs)
- );
+ phase,
+ cooldownMs
+ ));
}
});
if (readiness.status === "stopped") {
diff --git a/src/binance-orderbook-trade/core/continuous-ladder.js b/src/binance-orderbook-trade/core/continuous-ladder.js
index df87a5f..3fbaae4 100644
--- a/src/binance-orderbook-trade/core/continuous-ladder.js
+++ b/src/binance-orderbook-trade/core/continuous-ladder.js
@@ -69,15 +69,12 @@ export function recordContinuousLadderRound(progress, outcome) {
assertContinuousLadderProgress(progress);
}
-export function formatContinuousLadderProgress(label, phase, progress, reason = null) {
+function buildContinuousLadderProgressParts(label, phase, progress) {
if (typeof label !== 'string' || label.trim() === '') {
throw new Error('Invalid continuous ladder label');
}
const phaseText = CONTINUOUS_LADDER_PHASE_TEXT[phase];
if (!phaseText) throw new Error('Invalid continuous ladder phase');
- if (reason !== null && (typeof reason !== 'string' || reason.trim() === '')) {
- throw new Error('Invalid continuous ladder reason');
- }
assertContinuousLadderProgress(progress);
const parts = [`${label}${phaseText}`];
@@ -93,6 +90,14 @@ export function formatContinuousLadderProgress(label, phase, progress, reason =
}
parts.push(`累计 ${progress.submittedOrders} 笔`);
if (progress.cancelledOrders > 0) parts.push(`撤 ${progress.cancelledOrders} 笔`);
+ return parts;
+}
+
+export function formatContinuousLadderProgress(label, phase, progress, reason = null) {
+ if (reason !== null && (typeof reason !== 'string' || reason.trim() === '')) {
+ throw new Error('Invalid continuous ladder reason');
+ }
+ const parts = buildContinuousLadderProgressParts(label, phase, progress);
if (reason !== null) parts.push(reason);
return parts.join(' · ');
}
@@ -106,7 +111,13 @@ export function formatContinuousLadderWaitReason(phase, cooldownMs) {
const duration = cooldownMs % 1000 === 0
? `${cooldownMs / 1000}s`
: `${cooldownMs}ms`;
- return `等待 ${duration} 后继续下一轮`;
+ return `${duration} 后继续`;
+}
+
+export function formatContinuousLadderWaitProgress(label, progress, phase, cooldownMs) {
+ const parts = buildContinuousLadderProgressParts(label, 'running', progress);
+ parts.splice(1, 0, formatContinuousLadderWaitReason(phase, cooldownMs));
+ return parts.join(' · ');
}
function assertReadinessState(state) {
diff --git a/src/binance-orderbook-trade/index.user.js b/src/binance-orderbook-trade/index.user.js
index dc6fcc9..6887ecb 100644
--- a/src/binance-orderbook-trade/index.user.js
+++ b/src/binance-orderbook-trade/index.user.js
@@ -80,7 +80,7 @@ import { keepInteractionFeedbackVisible } from './core/interaction-feedback.js';
import {
createContinuousLadderProgress,
formatContinuousLadderProgress,
- formatContinuousLadderWaitReason,
+ formatContinuousLadderWaitProgress,
recordContinuousLadderRound,
waitForContinuousLadderNextRound,
} from './core/continuous-ladder.js';
@@ -2932,12 +2932,12 @@ import {
delay,
signal: abortController.signal,
onWaitStateChange: ({ phase, cooldownMs }) => {
- setContinuousLadderProgressStatus(
+ setLadderStatus(formatContinuousLadderWaitProgress(
spec.label,
- 'running',
continuousProgress,
- formatContinuousLadderWaitReason(phase, cooldownMs),
- );
+ phase,
+ cooldownMs,
+ ));
},
});
if (readiness.status === 'stopped') {
diff --git a/test/unit/binance-orderbook-trade/continuous-ladder.test.js b/test/unit/binance-orderbook-trade/continuous-ladder.test.js
index 70db50b..cca499a 100644
--- a/test/unit/binance-orderbook-trade/continuous-ladder.test.js
+++ b/test/unit/binance-orderbook-trade/continuous-ladder.test.js
@@ -6,6 +6,7 @@ import {
CONTINUOUS_LADDER_READY_CHECK_MS,
createContinuousLadderProgress,
formatContinuousLadderProgress,
+ formatContinuousLadderWaitProgress,
formatContinuousLadderWaitReason,
recordContinuousLadderRound,
waitForContinuousLadderNextRound,
@@ -184,7 +185,7 @@ test('continuous ladder wait reasons distinguish readiness from the actual coold
);
assert.equal(
formatContinuousLadderWaitReason('cooldown', CONTINUOUS_LADDER_COOLDOWN_MS),
- '等待 1s 后继续下一轮',
+ '1s 后继续',
);
assert.throws(
() => formatContinuousLadderWaitReason('unknown', CONTINUOUS_LADDER_COOLDOWN_MS),
@@ -192,6 +193,45 @@ test('continuous ladder wait reasons distinguish readiness from the actual coold
);
});
+test('continuous ladder wait status puts the current wait before progress counters', () => {
+ const progress = createContinuousLadderProgress();
+ recordContinuousLadderRound(progress, {
+ status: 'completed',
+ progress: roundProgress({
+ submittedOrders: 3,
+ plannedOrders: 3,
+ currentPlanSubmittedOrders: 3,
+ }),
+ });
+ recordContinuousLadderRound(progress, {
+ status: 'completed',
+ progress: roundProgress({
+ submittedOrders: 3,
+ plannedOrders: 3,
+ currentPlanSubmittedOrders: 3,
+ }),
+ });
+
+ assert.equal(
+ formatContinuousLadderWaitProgress(
+ '阶梯平空',
+ progress,
+ 'cooldown',
+ CONTINUOUS_LADDER_COOLDOWN_MS,
+ ),
+ '阶梯平空连续中 · 1s 后继续 · 2/2 轮 · 本轮 3/3 笔 · 累计 6 笔',
+ );
+ assert.equal(
+ formatContinuousLadderWaitProgress(
+ '阶梯平空',
+ progress,
+ 'waiting_ready',
+ CONTINUOUS_LADDER_COOLDOWN_MS,
+ ),
+ '阶梯平空连续中 · 等待按钮恢复 · 2/2 轮 · 本轮 3/3 笔 · 累计 6 笔',
+ );
+});
+
test('continuous ladder returns a terminal readiness state without another cooldown', async () => {
const delays = [];
const stopped = { status: 'stopped', reason: 'position_flat' };
diff --git a/test/unit/binance-orderbook-trade/source-regressions.test.js b/test/unit/binance-orderbook-trade/source-regressions.test.js
index 317ab8e..1bc8cf5 100644
--- a/test/unit/binance-orderbook-trade/source-regressions.test.js
+++ b/test/unit/binance-orderbook-trade/source-regressions.test.js
@@ -630,7 +630,7 @@ test('Option or Alt click continuously repeats close ladders only after readines
assert.match(continuousBody, /await waitForContinuousLadderNextRound\(/);
assert.match(continuousBody, /readContinuousLadderReadiness\(actionType, actionSymbol\)/);
assert.match(continuousBody, /onWaitStateChange:/);
- assert.match(continuousBody, /formatContinuousLadderWaitReason\(phase, cooldownMs\)/);
+ assert.match(continuousBody, /formatContinuousLadderWaitProgress\(/);
assert.match(readinessBody, /isCurrentObservedSymbol\(actionSymbol\)/);
assert.match(readinessBody, /getActiveTradeMode\(\) !== spec\.mode/);