Skip to content

Commit 2d5880c

Browse files
authored
Merge pull request #178 from jackhai9/codex/fix-adaptive-precision-recommendation
fix: expand until precision recommendation is ready
2 parents 5264971 + cb0f400 commit 2d5880c

5 files changed

Lines changed: 123 additions & 68 deletions

File tree

‎scripts/binance-orderbook-trade.user.js‎

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// @namespace binance.orderbook.trade
44
// @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
55
// @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
6-
// @version 2.7.125
6+
// @version 2.7.126
77
// @author jackhai9
88
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
99
// @match https://www.binance.com/*/futures/*
@@ -557,29 +557,6 @@
557557
}
558558
return moves;
559559
}
560-
function collectPriceMovesWithExpandingWindow(prices, {
561-
initialLimit = 10,
562-
expansionStep = 10,
563-
minSamples = 5
564-
} = {}) {
565-
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
566-
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
567-
}
568-
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
569-
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
570-
}
571-
if (!Number.isInteger(minSamples) || minSamples < 1) {
572-
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
573-
}
574-
const observedPrices = Array.isArray(prices) ? prices : [];
575-
let usedCount = Math.min(initialLimit, observedPrices.length);
576-
let samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
577-
while (samples.length < minSamples && usedCount < observedPrices.length) {
578-
usedCount = Math.min(usedCount + expansionStep, observedPrices.length);
579-
samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
580-
}
581-
return { samples, usedCount };
582-
}
583560
function sortedPositiveDecimals(values) {
584561
return (values || []).map((value) => normalizeDecimalString(value)).filter((value) => value && isPositiveDecimalString(value)).sort((a, b) => compareDecimalStrings(a, b));
585562
}
@@ -629,6 +606,42 @@
629606
}
630607
return selectedOption;
631608
}
609+
function recommendOrderbookPrecisionWithExpandingWindow({
610+
prices,
611+
options,
612+
initialLimit = 10,
613+
expansionStep = 10,
614+
minSamples = 5,
615+
minBucketShare = 0.25
616+
}) {
617+
if (!Array.isArray(prices)) {
618+
throw new Error("Precision trade prices must be an array");
619+
}
620+
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
621+
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
622+
}
623+
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
624+
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
625+
}
626+
if (!Number.isInteger(minSamples) || minSamples < 1) {
627+
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
628+
}
629+
let usedCount = Math.min(initialLimit, prices.length);
630+
let samples = [];
631+
let recommendation = null;
632+
while (true) {
633+
samples = collectNonZeroPriceMoves(prices.slice(0, usedCount));
634+
recommendation = recommendOrderbookPrecision({
635+
samples,
636+
options,
637+
minSamples,
638+
minBucketShare
639+
});
640+
if (recommendation || usedCount >= prices.length) break;
641+
usedCount = Math.min(usedCount + expansionStep, prices.length);
642+
}
643+
return { samples, usedCount, recommendation };
644+
}
632645

633646
// src/binance-orderbook-trade/core/quantity.js
634647
function pow102(exp) {
@@ -3002,16 +3015,17 @@
30023015
scheduleRenderPanel();
30033016
return;
30043017
}
3005-
const { samples: latestSamples } = collectPriceMovesWithExpandingWindow(getLatestTradePrices(), {
3018+
const {
3019+
samples: latestSamples,
3020+
recommendation
3021+
} = recommendOrderbookPrecisionWithExpandingWindow({
3022+
prices: getLatestTradePrices(),
3023+
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS,
30063024
initialLimit: ORDERBOOK_PRECISION_INITIAL_TRADE_LIMIT,
30073025
expansionStep: ORDERBOOK_PRECISION_TRADE_EXPANSION_STEP,
30083026
minSamples: ORDERBOOK_PRECISION_MIN_EFFECTIVE_MOVES
30093027
});
30103028
const samples = saveStoredOrderbookPrecisionSamples(symbol, latestSamples);
3011-
const recommendation = recommendOrderbookPrecision({
3012-
samples,
3013-
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS
3014-
});
30153029
orderbookPrecisionState = {
30163030
...orderbookPrecisionState,
30173031
symbol,

‎src/binance-orderbook-trade/core/precision.js‎

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,30 +65,6 @@ export function collectNonZeroPriceMoves(prices) {
6565
return moves;
6666
}
6767

68-
export function collectPriceMovesWithExpandingWindow(prices, {
69-
initialLimit = 10,
70-
expansionStep = 10,
71-
minSamples = 5,
72-
} = {}) {
73-
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
74-
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
75-
}
76-
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
77-
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
78-
}
79-
if (!Number.isInteger(minSamples) || minSamples < 1) {
80-
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
81-
}
82-
const observedPrices = Array.isArray(prices) ? prices : [];
83-
let usedCount = Math.min(initialLimit, observedPrices.length);
84-
let samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
85-
while (samples.length < minSamples && usedCount < observedPrices.length) {
86-
usedCount = Math.min(usedCount + expansionStep, observedPrices.length);
87-
samples = collectNonZeroPriceMoves(observedPrices.slice(0, usedCount));
88-
}
89-
return { samples, usedCount };
90-
}
91-
9268
function sortedPositiveDecimals(values) {
9369
return (values || [])
9470
.map((value) => normalizeDecimalString(value))
@@ -149,3 +125,41 @@ export function recommendOrderbookPrecision({
149125
}
150126
return selectedOption;
151127
}
128+
129+
export function recommendOrderbookPrecisionWithExpandingWindow({
130+
prices,
131+
options,
132+
initialLimit = 10,
133+
expansionStep = 10,
134+
minSamples = 5,
135+
minBucketShare = 0.25,
136+
}) {
137+
if (!Array.isArray(prices)) {
138+
throw new Error('Precision trade prices must be an array');
139+
}
140+
if (!Number.isInteger(initialLimit) || initialLimit < 2) {
141+
throw new Error(`Invalid initial precision trade limit: ${initialLimit}`);
142+
}
143+
if (!Number.isInteger(expansionStep) || expansionStep < 1) {
144+
throw new Error(`Invalid precision trade expansion step: ${expansionStep}`);
145+
}
146+
if (!Number.isInteger(minSamples) || minSamples < 1) {
147+
throw new Error(`Invalid minimum precision sample count: ${minSamples}`);
148+
}
149+
150+
let usedCount = Math.min(initialLimit, prices.length);
151+
let samples = [];
152+
let recommendation = null;
153+
while (true) {
154+
samples = collectNonZeroPriceMoves(prices.slice(0, usedCount));
155+
recommendation = recommendOrderbookPrecision({
156+
samples,
157+
options,
158+
minSamples,
159+
minBucketShare,
160+
});
161+
if (recommendation || usedCount >= prices.length) break;
162+
usedCount = Math.min(usedCount + expansionStep, prices.length);
163+
}
164+
return { samples, usedCount, recommendation };
165+
}

‎src/binance-orderbook-trade/index.user.js‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// @namespace binance.orderbook.trade
44
// @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
55
// @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
6-
// @version 2.7.125
6+
// @version 2.7.126
77
// @author jackhai9
88
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
99
// @match https://www.binance.com/*/futures/*
@@ -65,10 +65,10 @@ import {
6565
isPositiveDecimalString,
6666
} from './core/decimal.js';
6767
import {
68-
collectPriceMovesWithExpandingWindow,
6968
formatOrderbookPrecisionShortcutLabel,
7069
getOrderbookPrecisionShortcutOptions,
7170
recommendOrderbookPrecision,
71+
recommendOrderbookPrecisionWithExpandingWindow,
7272
} from './core/precision.js';
7373
import { allocateLadderQuantities } from './core/quantity.js';
7474
import {
@@ -1742,16 +1742,17 @@ import {
17421742
scheduleRenderPanel();
17431743
return;
17441744
}
1745-
const { samples: latestSamples } = collectPriceMovesWithExpandingWindow(getLatestTradePrices(), {
1745+
const {
1746+
samples: latestSamples,
1747+
recommendation,
1748+
} = recommendOrderbookPrecisionWithExpandingWindow({
1749+
prices: getLatestTradePrices(),
1750+
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS,
17461751
initialLimit: ORDERBOOK_PRECISION_INITIAL_TRADE_LIMIT,
17471752
expansionStep: ORDERBOOK_PRECISION_TRADE_EXPANSION_STEP,
17481753
minSamples: ORDERBOOK_PRECISION_MIN_EFFECTIVE_MOVES,
17491754
});
17501755
const samples = saveStoredOrderbookPrecisionSamples(symbol, latestSamples);
1751-
const recommendation = recommendOrderbookPrecision({
1752-
samples,
1753-
options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS,
1754-
});
17551756
orderbookPrecisionState = {
17561757
...orderbookPrecisionState,
17571758
symbol,

‎test/unit/binance-orderbook-trade/precision.test.js‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import assert from 'node:assert/strict';
33

44
import {
55
collectNonZeroPriceMoves,
6-
collectPriceMovesWithExpandingWindow,
76
formatOrderbookPrecisionShortcutLabel,
87
getOrderbookPrecisionDecadeTarget,
98
getOrderbookPrecisionShortcutOptions,
109
recommendOrderbookPrecision,
10+
recommendOrderbookPrecisionWithExpandingWindow,
1111
} from '../../../src/binance-orderbook-trade/core/precision.js';
1212

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

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

129-
const result = collectPriceMovesWithExpandingWindow(prices);
129+
const result = recommendOrderbookPrecisionWithExpandingWindow({
130+
prices,
131+
options: ['0.0001', '0.001', '0.01', '0.1', '1'],
132+
});
130133
assert.equal(result.usedCount, 20);
131134
assert.equal(result.samples.length, 13);
132135
assert.deepEqual([...new Set(result.samples)], ['0.001']);
136+
assert.equal(result.recommendation, '0.001');
133137
});
134138

135139
test('returns the complete visible snapshot when price changes remain insufficient', () => {
136-
assert.deepEqual(collectPriceMovesWithExpandingWindow([
137-
'80.7', '80.7', '80.7', '80.7', '80.7', '80.7',
138-
]), {
140+
assert.deepEqual(recommendOrderbookPrecisionWithExpandingWindow({
141+
prices: ['80.7', '80.7', '80.7', '80.7', '80.7', '80.7'],
142+
options: ['0.001', '0.01', '0.1', '1'],
143+
}), {
139144
samples: [],
140145
usedCount: 6,
146+
recommendation: null,
141147
});
142148
});
149+
150+
test('keeps expanding when sample count is sufficient but no precision bucket is decisive', () => {
151+
const prices = [
152+
'79.748', '79.747', '79.742', '79.742', '79.737',
153+
'79.731', '79.730', '79.730', '79.715', '79.748',
154+
'79.748', '79.746', '79.740', '79.727', '79.727',
155+
'79.746', '79.745', '79.745', '79.767', '79.767',
156+
'79.745', '79.738', '79.737',
157+
];
158+
const options = ['0.0001', '0.001', '0.01', '0.1', '1'];
159+
const firstWindowSamples = collectNonZeroPriceMoves(prices.slice(0, 10));
160+
161+
assert.equal(firstWindowSamples.length, 7);
162+
assert.equal(recommendOrderbookPrecision({ samples: firstWindowSamples, options }), null);
163+
const result = recommendOrderbookPrecisionWithExpandingWindow({ prices, options });
164+
assert.equal(result.usedCount, 20);
165+
assert.equal(result.recommendation, '0.01');
166+
});

‎test/unit/binance-orderbook-trade/source-regressions.test.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -948,12 +948,14 @@ test('orderbook precision recommendation marks one shortcut without applying it
948948
assert.match(source, /orderbookPrecisionOptionsLoadAttemptedSymbol/);
949949

950950
const sampleBody = readFunctionBody('refreshOrderbookPrecisionSamplesNow');
951-
assert.match(sampleBody, /collectPriceMovesWithExpandingWindow\(getLatestTradePrices\(\),/);
951+
assert.match(sampleBody, /recommendOrderbookPrecisionWithExpandingWindow\(\{/);
952+
assert.match(sampleBody, /prices: getLatestTradePrices\(\)/);
953+
assert.match(sampleBody, /options: ORDERBOOK_PRECISION_CANDIDATE_OPTIONS/);
952954
assert.match(sampleBody, /initialLimit: ORDERBOOK_PRECISION_INITIAL_TRADE_LIMIT/);
953955
assert.match(sampleBody, /expansionStep: ORDERBOOK_PRECISION_TRADE_EXPANSION_STEP/);
954956
assert.match(sampleBody, /minSamples: ORDERBOOK_PRECISION_MIN_EFFECTIVE_MOVES/);
955957
assert.match(sampleBody, /saveStoredOrderbookPrecisionSamples/);
956-
assert.match(sampleBody, /recommendOrderbookPrecision/);
958+
assert.match(sampleBody, /samples: latestSamples,[\s\S]*recommendation,/);
957959
assert.match(sampleBody, /status: recommendation \? 'ready' : PANEL_COPY\.status\.precisionInsufficient/);
958960
assert.match(sampleBody, /PANEL_COPY\.status\.precisionUpdated/);
959961
assert.doesNotMatch(sampleBody, /setTimeout|setInterval|await delay/);

0 commit comments

Comments
 (0)