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
10 changes: 10 additions & 0 deletions docs/binance-orderbook-trade-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ The rebalance path is pinned to the current Binance page bundle contract instead

The user must approve one native confirmation that shows the current balances, 5:4:1 targets, and exact transfer list. After confirmation, the script rechecks all positions, all open orders, and the exact three-account balance snapshot before every transfer. Each successful response must then be reflected by a fresh balance read before the next transfer starts. An intervening position, order, or balance change stops the task; a partial completion is reported explicitly and is never retried or rolled back automatically.

Hiding the tab after the user confirms a rebalance does not cancel its remaining transfers. The initial eligibility and preview still require a visible trading page; each confirmed transfer continues to require the same fresh account checks, matching balances, and current trading route while hidden.

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.
Expand All @@ -286,6 +288,8 @@ Every observed place-order response keeps a sanitized diagnostic contract: HTTP

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.

An already clicked ladder or single-order task may continue when Chrome hides the page. Input and action-button stability checks use separate timer tasks while hidden because paint frames can pause; they still re-read live controls, current symbol, mode, precision, price, quantity, and the exact native response before moving to the next order. A single-order draft expires 15 seconds after its trusted price click, so a long background stall cannot later submit its old clicked price and quantity. Continuous close readiness and reduce-only recovery use those same live conditions without treating `document.hidden` as a refusal. Stop aborts an active ladder wait before any later submit. Background timers can be throttled, so neither order timing nor the one-second continuous cooldown has a strict wall-clock guarantee while hidden. A frozen or discarded page cannot run page JavaScript until Chrome resumes or reloads it; this userscript does not resume an unconfirmed order automatically after reload.

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.

A session waiting for readiness, stopping, or failing before its first recorded
Expand Down Expand Up @@ -334,6 +338,12 @@ Run manual checks when behavior touches trading flow, DOM selectors, account ord
- verify the precision apply button changes Binance orderbook precision only after an explicit user click
- verify precision decrease/increase selects the exact native divide-by-10/multiply-by-10 option, restores the corresponding symbol-mode-precision panel profile, and stops at a missing native decade option
- start ladder order, confirm start buttons are disabled while running
- after clicking ordinary open and close ladders, hide or minimize Chrome while the first order is pending; verify the task completes the remaining orders with the captured direction, and that Stop prevents a pending hidden control check from submitting
- hide the page immediately after a trusted single-order price click; verify exactly one current-symbol order is submitted after the input settles
- hold a hidden single-order draft for longer than 15 seconds, then resume the page; verify the stale clicked price is refused without a native order request
- hide the page after one continuous-close round is acknowledged; verify the next round can start after live readiness and cooldown, then Stop while its response is pending and verify no additional order starts
- hide the page after confirming current-symbol cancellation or a reviewed USDT rebalance; verify the original scope, manual confirmation, per-step account checks, final balance, and temporary UI restoration
- repeat one background scenario in an actual Chrome/Tampermonkey tab and after minimizing Chrome; record the installed userscript version, hidden/frozen/discarded state, elapsed time, request count, exact exchange response, and refreshed authoritative order/account state
- 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 every active-round status starts with `连续阶梯平多` or `连续阶梯平空` and shows completed/started rounds, live-round order ratio, cumulative confirmed submissions, and no zero-cancellation segment
Expand Down
10 changes: 10 additions & 0 deletions docs/binance-orderbook-trade-ui-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ not a latency or throughput measurement. The ordinary single-round unknown
submission scenario must not be generalized to continuous mode, whose existing
`submit_unconfirmed` policy deliberately permits a later recovery round.

Background-tab L2 scenarios override `document.hidden`, dispatch
`visibilitychange`, and pause paint-frame callbacks after the real generated
userscript is loaded. They verify that already clicked open/close ladders,
continuous close, single-order submission, confirmed cancellation, and confirmed
rebalance continue through the fixture's native request and response boundaries.
This harness does not model Chrome timer throttling, page freeze, discard, or
Tampermonkey injection. Those require a separate L3/L4 observation in an actual
background or minimized Chrome tab; record elapsed time and authoritative
exchange/account state before claiming that the live task completed.

`npm run test:coverage` additionally collects V8 execution and maps it to the
complete production source set. The collector's own browser proof uses virtual
code and remains separate from production coverage. See [Source Coverage](test-coverage.md)
Expand Down
49 changes: 49 additions & 0 deletions e2e/binance-orderbook/helpers/simulated-visibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/** Simulate a background tab after the real userscript has loaded in the fixture. */
export async function installSimulatedVisibility(page) {
await page.evaluate(() => {
const nativeRequestAnimationFrame = window.requestAnimationFrame.bind(window);
const nativeCancelAnimationFrame = window.cancelAnimationFrame.bind(window);
const pendingHiddenFrames = new Map();
const resumedHiddenFrames = new Map();
let nextHiddenFrame = 1_000_000;
const state = {
hidden: false,
setHidden(hidden) {
if (this.hidden === hidden) return;
this.hidden = hidden;
document.dispatchEvent(new Event('visibilitychange'));
if (!hidden) {
for (const [handle, callback] of pendingHiddenFrames) {
pendingHiddenFrames.delete(handle);
resumedHiddenFrames.set(handle, nativeRequestAnimationFrame(timestamp => {
resumedHiddenFrames.delete(handle);
callback(timestamp);
}));
}
}
},
};
Object.defineProperty(document, 'hidden', { configurable: true, get: () => state.hidden });
window.requestAnimationFrame = callback => {
if (!state.hidden) return nativeRequestAnimationFrame(callback);
const handle = nextHiddenFrame++;
pendingHiddenFrames.set(handle, callback);
return handle;
};
window.cancelAnimationFrame = handle => {
if (pendingHiddenFrames.delete(handle)) return;
const resumedHandle = resumedHiddenFrames.get(handle);
if (resumedHiddenFrames.has(handle)) {
resumedHiddenFrames.delete(handle);
nativeCancelAnimationFrame(resumedHandle);
return;
}
nativeCancelAnimationFrame(handle);
};
window.__SIMULATED_VISIBILITY__ = state;
});
}

export async function setSimulatedVisibility(page, hidden) {
await page.evaluate(value => window.__SIMULATED_VISIBILITY__.setHidden(value), hidden);
}
28 changes: 28 additions & 0 deletions e2e/binance-orderbook/specs/account-rebalance-behavior.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ACCOUNT_PATHS, createAccountRebalanceApi } from '../fixtures/account-re
import { createCancelScenario } from '../scenarios/cancel-current-symbol.js';
import { openUserscriptScenario, readFixtureState } from '../helpers/userscript-page.js';
import { installScenarioClock, pauseScenarioClock } from '../helpers/scenario-clock.js';
import { installSimulatedVisibility, setSimulatedVisibility } from '../helpers/simulated-visibility.js';

async function openRebalance(page, balances, options) {
await installScenarioClock(page);
Expand Down Expand Up @@ -53,6 +54,33 @@ test('user completes exactly two USDT transfers only after confirming the comple
expect(errors).toEqual([]);
});

test('user completes a confirmed account rebalance after the tab becomes hidden', async ({ page }) => {
// Given the user has reviewed a two-transfer plan while the trading page is visible.
const { api, errors, action, status } = await openRebalance(page, {
FUNDING: '100', MAIN: '0', UMFUTURE: '0',
});
await installSimulatedVisibility(page);
await action.evaluate(button => button.click());
const dialog = page.getByRole('dialog', { name: '账户再平衡' });
await expect(dialog).toBeVisible();
await expect(dialog).toContainText('40 USDT');
await expect(dialog).toContainText('10 USDT');

// When confirmation is clicked and the tab hides in the same event turn.
await dialog.getByRole('button', { name: '确认再平衡', exact: true }).evaluate(button => {
button.click();
window.__SIMULATED_VISIBILITY__.setHidden(true);
});

// Then each transfer still requires fresh account checks and reaches the reviewed target.
await expect.poll(() => api.snapshot().requests.filter(request => request.pathname === ACCOUNT_PATHS.transfer))
.toHaveLength(2);
await setSimulatedVisibility(page, false);
await expect(status).toHaveText('账户再平衡已完成 · 2/2 笔');
expect(api.snapshot().balances).toEqual({ FUNDING: '50', MAIN: '40', UMFUTURE: '10' });
expect(errors).toEqual([]);
});

test('user cancels an account preview without sending a transfer', async ({ page }) => {
// Given a flat account has a valid two-transfer rebalance plan.
const { api, errors, action, status } = await openRebalance(page, { FUNDING: '100', MAIN: '0', UMFUTURE: '0' });
Expand Down
30 changes: 30 additions & 0 deletions e2e/binance-orderbook/specs/cancel-current-symbol.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
readFixtureState,
} from '../helpers/userscript-page.js';
import { installScenarioClock, pauseScenarioClock } from '../helpers/scenario-clock.js';
import { installSimulatedVisibility, setSimulatedVisibility } from '../helpers/simulated-visibility.js';
import {
assertResponsiveInteraction,
assertStableGeometry,
Expand Down Expand Up @@ -216,6 +217,35 @@ test('user confirms cancellation for the current symbol while other-symbol order
expect(errors).toEqual([]);
});

test('user finishes confirmed current-symbol cancellation after the tab becomes hidden', async ({ page }) => {
// Given a native confirmation is open for current and other-symbol Basic orders.
const scenario = createCancelScenario({
positions: POSITION_SETS.both,
orders: ORDER_SETS.both,
ui: { hideOtherSymbols: false, accountTab: 'positions', showOrders: true },
});
const { errors } = await openUserscriptScenario(page, scenario);
await installSimulatedVisibility(page);
await page.getByRole('button', { name: '撤单' }).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible();

// When the user confirms, then moves the page to a background tab.
await dialog.getByRole('button', { name: '确认' }).evaluate(button => {
button.click();
window.__SIMULATED_VISIBILITY__.setHidden(true);
});

// Then only the captured current-symbol order is cancelled and UI state is restored.
await expect.poll(async () => (await readFixtureState(page)).events
.filter(({ type }) => type === 'cancel-requested')).toHaveLength(1);
await setSimulatedVisibility(page, false);
await expect(page.getByText('撤单已完成')).toBeVisible();
expect((await readFixtureState(page)).orders).toEqual(otherSymbolOrders(scenario));
await expectRestoredState(page, scenario);
expect(errors).toEqual([]);
});

test('user keeps an already enabled symbol filter after confirming cancellation', async ({ page }) => {
// Given the initial open-orders view has Hide Other Symbols enabled and chart orders hidden.
const scenario = createCancelScenario({
Expand Down
26 changes: 26 additions & 0 deletions e2e/binance-orderbook/specs/continuous-readiness-behavior.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { test, expect } from '../test.js';
import { CURRENT_SYMBOL, createCancelScenario } from '../scenarios/cancel-current-symbol.js';
import { openUserscriptScenario, readFixtureState } from '../helpers/userscript-page.js';
import { installScenarioClock, pauseScenarioClock } from '../helpers/scenario-clock.js';
import { installSimulatedVisibility, setSimulatedVisibility } from '../helpers/simulated-visibility.js';

const PANEL = '#jh-binance-close-qty-multiplier-panel';
const STATUS = '#jh-binance-ladder-status';
Expand Down Expand Up @@ -174,6 +175,31 @@ test('user completes two close-short rounds with a full cooldown and exact cumul
expect(host.errors).toEqual([]);
});

test('user continues a confirmed close round while hidden and can stop the next hidden round', async ({ page }) => {
// Given the first round's final native acknowledgement is held after a visible user click.
const host = await openPendingFirstRound(page);
await installSimulatedVisibility(page);

// When the tab becomes hidden and the held acknowledgement completes.
await setSimulatedVisibility(page, true);
await host.releaseSubmitResponse(3);
await page.clock.resume();

// Then the next round begins after its readiness check without a foreground frame.
await expect.poll(host.pendingSubmitSequences, { timeout: 10_000 }).toEqual([4]);
expect((await readSubmissions(page)).map(({ action }) => action)).toEqual(Array(4).fill('平空'));

// When Stop is clicked while that fourth native response remains held.
await host.panel.locator('[data-ladder-stop]').evaluate(button => button.click());
await host.releaseSubmitResponse(4);
await setSimulatedVisibility(page, false);

// Then no later order starts and the acknowledged progress remains exact.
await expect(host.status).toContainText('已停止');
expect(await readSubmissions(page)).toHaveLength(4);
expect(host.errors).toEqual([]);
});

test('user waits for a disabled close button and then receives a complete cooldown', async ({ page }) => {
// Given the current round can finish while the native close button is disabled.
const host = await openPendingFirstRound(page);
Expand Down
Loading
Loading