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
72 changes: 43 additions & 29 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.125
// @version 2.7.126
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -557,29 +557,6 @@
}
return moves;
}
function collectPriceMovesWithExpandingWindow(prices, {
initialLimit = 10,
expansionStep = 10,
minSamples = 5
} = {}) {
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
}
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
}
if (!Number.isInteger(minSamples) || minSamples < 1) {
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
}
const observedPrices = Array.isArray(prices) ? prices : [];
let usedCount = Math.min(initialLimit, observedPrices.length);
let samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
while (samples.length < minSamples && usedCount < observedPrices.length) {
usedCount = Math.min(usedCount + expansionStep, observedPrices.length);
samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
}
return { samples, usedCount };
}
function sortedPositiveDecimals(values) {
return (values || []).map((value) => normalizeDecimalString(value)).filter((value) => value && isPositiveDecimalString(value)).sort((a, b) => compareDecimalStrings(a, b));
}
Expand Down Expand Up @@ -629,6 +606,42 @@
}
return selectedOption;
}
function recommendOrderbookPrecisionWithExpandingWindow({
prices,
options,
initialLimit = 10,
expansionStep = 10,
minSamples = 5,
minBucketShare = 0.25
}) {
if (!Array.isArray(prices)) {
throw new Error("Precision trade prices must be an array");
}
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
}
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
}
if (!Number.isInteger(minSamples) || minSamples < 1) {
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
}
let usedCount = Math.min(initialLimit, prices.length);
let samples = [];
let recommendation = null;
while (true) {
samples = collectNonZeroPriceMoves(prices.slice(0, usedCount));
recommendation = recommendOrderbookPrecision({
samples,
options,
minSamples,
minBucketShare
});
if (recommendation || usedCount >= prices.length) break;
usedCount = Math.min(usedCount + expansionStep, prices.length);
}
return { samples, usedCount, recommendation };
}

// src/binance-orderbook-trade/core/quantity.js
function pow102(exp) {
Expand Down Expand Up @@ -3002,16 +3015,17 @@
scheduleRenderPanel();
return;
}
const { samples: latestSamples } = collectPriceMovesWithExpandingWindow(getLatestTradePrices(), {
const {
samples: latestSamples,
recommendation
} = recommendOrderbookPrecisionWithExpandingWindow({
prices: getLatestTradePrices(),
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS,
initialLimit: ORDERBOOK_PRECISION_INITIAL_TRADE_LIMIT,
expansionStep: ORDERBOOK_PRECISION_TRADE_EXPANSION_STEP,
minSamples: ORDERBOOK_PRECISION_MIN_EFFECTIVE_MOVES
});
const samples = saveStoredOrderbookPrecisionSamples(symbol, latestSamples);
const recommendation = recommendOrderbookPrecision({
samples,
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS
});
orderbookPrecisionState = {
...orderbookPrecisionState,
symbol,
Expand Down
62 changes: 38 additions & 24 deletions src/binance-orderbook-trade/core/precision.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,30 +65,6 @@ export function collectNonZeroPriceMoves(prices) {
return moves;
}

export function collectPriceMovesWithExpandingWindow(prices, {
initialLimit = 10,
expansionStep = 10,
minSamples = 5,
} = {}) {
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
}
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
}
if (!Number.isInteger(minSamples) || minSamples < 1) {
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
}
const observedPrices = Array.isArray(prices) ? prices : [];
let usedCount = Math.min(initialLimit, observedPrices.length);
let samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
while (samples.length < minSamples && usedCount < observedPrices.length) {
usedCount = Math.min(usedCount + expansionStep, observedPrices.length);
samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
}
return { samples, usedCount };
}

function sortedPositiveDecimals(values) {
return (values || [])
.map((value) => normalizeDecimalString(value))
Expand Down Expand Up @@ -149,3 +125,41 @@ export function recommendOrderbookPrecision({
}
return selectedOption;
}

export function recommendOrderbookPrecisionWithExpandingWindow({
prices,
options,
initialLimit = 10,
expansionStep = 10,
minSamples = 5,
minBucketShare = 0.25,
}) {
if (!Array.isArray(prices)) {
throw new Error('Precision trade prices must be an array');
}
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
}
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
}
if (!Number.isInteger(minSamples) || minSamples < 1) {
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
}

let usedCount = Math.min(initialLimit, prices.length);
let samples = [];
let recommendation = null;
while (true) {
samples = collectNonZeroPriceMoves(prices.slice(0, usedCount));
recommendation = recommendOrderbookPrecision({
samples,
options,
minSamples,
minBucketShare,
});
if (recommendation || usedCount >= prices.length) break;
usedCount = Math.min(usedCount + expansionStep, prices.length);
}
return { samples, usedCount, recommendation };
}
15 changes: 8 additions & 7 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.125
// @version 2.7.126
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -65,10 +65,10 @@ import {
isPositiveDecimalString,
} from './core/decimal.js';
import {
collectPriceMovesWithExpandingWindow,
formatOrderbookPrecisionShortcutLabel,
getOrderbookPrecisionShortcutOptions,
recommendOrderbookPrecision,
recommendOrderbookPrecisionWithExpandingWindow,
} from './core/precision.js';
import { allocateLadderQuantities } from './core/quantity.js';
import {
Expand Down Expand Up @@ -1742,16 +1742,17 @@ import {
scheduleRenderPanel();
return;
}
const { samples: latestSamples } = collectPriceMovesWithExpandingWindow(getLatestTradePrices(), {
const {
samples: latestSamples,
recommendation,
} = recommendOrderbookPrecisionWithExpandingWindow({
prices: getLatestTradePrices(),
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS,
initialLimit: ORDERBOOK_PRECISION_INITIAL_TRADE_LIMIT,
expansionStep: ORDERBOOK_PRECISION_TRADE_EXPANSION_STEP,
minSamples: ORDERBOOK_PRECISION_MIN_EFFECTIVE_MOVES,
});
const samples = saveStoredOrderbookPrecisionSamples(symbol, latestSamples);
const recommendation = recommendOrderbookPrecision({
samples,
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS,
});
orderbookPrecisionState = {
...orderbookPrecisionState,
symbol,
Expand Down
36 changes: 30 additions & 6 deletions test/unit/binance-orderbook-trade/precision.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import assert from 'node:assert/strict';

import {
collectNonZeroPriceMoves,
collectPriceMovesWithExpandingWindow,
formatOrderbookPrecisionShortcutLabel,
getOrderbookPrecisionDecadeTarget,
getOrderbookPrecisionShortcutOptions,
recommendOrderbookPrecision,
recommendOrderbookPrecisionWithExpandingWindow,
} from '../../../src/binance-orderbook-trade/core/precision.js';

test('keeps only the four smallest exact native precision shortcuts', () => {
Expand Down Expand Up @@ -117,7 +117,7 @@ test('ten latest trade rows provide enough movement evidence when eight do not',
}), '0.001');
});

test('expands the latest-trade window only until enough effective price moves exist', () => {
test('expands the latest-trade window until it produces a recommendation', () => {
const prices = [
'80.757', '80.757', '80.756', '80.756', '80.756',
'80.757', '80.756', '80.755', '80.755', '80.755',
Expand All @@ -126,17 +126,41 @@ test('expands the latest-trade window only until enough effective price moves ex
'80.700',
];

const result = collectPriceMovesWithExpandingWindow(prices);
const result = recommendOrderbookPrecisionWithExpandingWindow({
prices,
options: ['0.0001', '0.001', '0.01', '0.1', '1'],
});
assert.equal(result.usedCount, 20);
assert.equal(result.samples.length, 13);
assert.deepEqual([...new Set(result.samples)], ['0.001']);
assert.equal(result.recommendation, '0.001');
});

test('returns the complete visible snapshot when price changes remain insufficient', () => {
assert.deepEqual(collectPriceMovesWithExpandingWindow([
'80.7', '80.7', '80.7', '80.7', '80.7', '80.7',
]), {
assert.deepEqual(recommendOrderbookPrecisionWithExpandingWindow({
prices: ['80.7', '80.7', '80.7', '80.7', '80.7', '80.7'],
options: ['0.001', '0.01', '0.1', '1'],
}), {
samples: [],
usedCount: 6,
recommendation: null,
});
});

test('keeps expanding when sample count is sufficient but no precision bucket is decisive', () => {
const prices = [
'79.748', '79.747', '79.742', '79.742', '79.737',
'79.731', '79.730', '79.730', '79.715', '79.748',
'79.748', '79.746', '79.740', '79.727', '79.727',
'79.746', '79.745', '79.745', '79.767', '79.767',
'79.745', '79.738', '79.737',
];
const options = ['0.0001', '0.001', '0.01', '0.1', '1'];
const firstWindowSamples = collectNonZeroPriceMoves(prices.slice(0, 10));

assert.equal(firstWindowSamples.length, 7);
assert.equal(recommendOrderbookPrecision({ samples: firstWindowSamples, options }), null);
const result = recommendOrderbookPrecisionWithExpandingWindow({ prices, options });
assert.equal(result.usedCount, 20);
assert.equal(result.recommendation, '0.01');
});
6 changes: 4 additions & 2 deletions test/unit/binance-orderbook-trade/source-regressions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,12 +948,14 @@ test('orderbook precision recommendation marks one shortcut without applying it
assert.match(source, /orderbookPrecisionOptionsLoadAttemptedSymbol/);

const sampleBody = readFunctionBody('refreshOrderbookPrecisionSamplesNow');
assert.match(sampleBody, /collectPriceMovesWithExpandingWindow\(getLatestTradePrices\(\),/);
assert.match(sampleBody, /recommendOrderbookPrecisionWithExpandingWindow\(\{/);
assert.match(sampleBody, /prices: getLatestTradePrices\(\)/);
assert.match(sampleBody, /options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS/);
assert.match(sampleBody, /initialLimit: ORDERBOOK_PRECISION_INITIAL_TRADE_LIMIT/);
assert.match(sampleBody, /expansionStep: ORDERBOOK_PRECISION_TRADE_EXPANSION_STEP/);
assert.match(sampleBody, /minSamples: ORDERBOOK_PRECISION_MIN_EFFECTIVE_MOVES/);
assert.match(sampleBody, /saveStoredOrderbookPrecisionSamples/);
assert.match(sampleBody, /recommendOrderbookPrecision/);
assert.match(sampleBody, /samples: latestSamples,[\s\S]*recommendation,/);
assert.match(sampleBody, /status: recommendation \? 'ready' : PANEL_COPY\.status\.precisionInsufficient/);
assert.match(sampleBody, /PANEL_COPY\.status\.precisionUpdated/);
assert.doesNotMatch(sampleBody, /setTimeout|setInterval|await delay/);
Expand Down
Loading