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
16 changes: 6 additions & 10 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.130
// @version 2.7.131
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -708,6 +708,7 @@
}

// src/binance-orderbook-trade/core/ladder-plan.js
var MAX_AUTO_FIT_LADDER_PERCENT = "100";
var LADDER_ACTION_SPECS = {
OPEN_LONG: {
mode: "OPEN",
Expand Down Expand Up @@ -771,10 +772,11 @@
return candidateMinRequiredQty || minRequiredQty;
}
function fitLadderPlanForMinimumQty(options) {
const { baseQty, minRequiredQty, minRequiredQtyByLevel, percent, levels, stepSize, maxPercent } = options;
const { baseQty, minRequiredQty, minRequiredQtyByLevel, percent, levels, stepSize } = options;
const maxPercent = MAX_AUTO_FIT_LADDER_PERCENT;
const requestedLevels = Number(levels);
let minimumPercent = null;
if (!maxPercent || !Number.isInteger(requestedLevels) || requestedLevels <= 0) {
if (!Number.isInteger(requestedLevels) || requestedLevels <= 0) {
return { allocation: null, minimumPercent, maxPercent };
}
for (let candidateLevels = requestedLevels; candidateLevels >= 1; candidateLevels -= 1) {
Expand Down Expand Up @@ -3255,8 +3257,7 @@
minRequiredQtyByLevel,
percent,
levels,
stepSize: ruleContext.stepSize,
maxPercent: getMaxAutoFitLadderPercent(spec.mode)
stepSize: ruleContext.stepSize
});
if (autoFit.allocation) {
allocation = autoFit.allocation;
Expand Down Expand Up @@ -3299,11 +3300,6 @@
orders: orderPrices.map((price, index) => ({ price, qty: allocation.quantities[index] }))
};
}
function getMaxAutoFitLadderPercent(mode) {
if (mode === "OPEN") return String(Math.max(...LADDER_OPEN_PERCENTS));
if (mode === "CLOSE") return "100";
return null;
}
function createLadderMinimumQtyFailure(options) {
const {
spec,
Expand Down
8 changes: 6 additions & 2 deletions src/binance-orderbook-trade/core/ladder-plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
} from './decimal.js';
import { allocateLadderQuantities, decimalToStepCount, formatStepCount } from './quantity.js';

/** Auto-fit may use the full available quantity without changing the user's saved panel preset. */
export const MAX_AUTO_FIT_LADDER_PERCENT = '100';

const LADDER_ACTION_SPECS = {
OPEN_LONG: {
mode: 'OPEN',
Expand Down Expand Up @@ -79,10 +82,11 @@ function getMinRequiredQtyForLevels(minRequiredQty, minRequiredQtyByLevel, level
}

export function fitLadderPlanForMinimumQty(options) {
const { baseQty, minRequiredQty, minRequiredQtyByLevel, percent, levels, stepSize, maxPercent } = options;
const { baseQty, minRequiredQty, minRequiredQtyByLevel, percent, levels, stepSize } = options;
const maxPercent = MAX_AUTO_FIT_LADDER_PERCENT;
const requestedLevels = Number(levels);
let minimumPercent = null;
if (!maxPercent || !Number.isInteger(requestedLevels) || requestedLevels <= 0) {
if (!Number.isInteger(requestedLevels) || requestedLevels <= 0) {
return { allocation: null, minimumPercent, maxPercent };
}

Expand Down
9 changes: 1 addition & 8 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.130
// @version 2.7.131
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -2026,7 +2026,6 @@ import {
percent,
levels,
stepSize: ruleContext.stepSize,
maxPercent: getMaxAutoFitLadderPercent(spec.mode),
});
if (autoFit.allocation) {
allocation = autoFit.allocation;
Expand Down Expand Up @@ -2071,12 +2070,6 @@ import {
};
}

function getMaxAutoFitLadderPercent(mode) {
if (mode === 'OPEN') return String(Math.max(...LADDER_OPEN_PERCENTS));
if (mode === 'CLOSE') return '100';
return null;
}

function createLadderMinimumQtyFailure(options) {
const {
spec,
Expand Down
57 changes: 49 additions & 8 deletions test/unit/binance-orderbook-trade/ladder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import test from 'node:test';
import assert from 'node:assert/strict';

import {
MAX_AUTO_FIT_LADDER_PERCENT,
fitLadderPlanForMinimumQty,
getLadderActionSpec,
getLadderPercentForMode,
Expand Down Expand Up @@ -52,27 +53,68 @@ test('auto-fits ladder percent before reducing requested levels', () => {
percent: 30,
levels: 5,
stepSize: '0.1',
maxPercent: '70',
});

assert.equal(fit.percent, '50');
assert.equal(fit.levels, 5);
assert.deepEqual(fit.allocation.quantities, ['1', '1', '1', '1', '1']);
});

test('auto-fits ladder levels when keeping requested levels needs too much percent', () => {
test('auto-fits up to 100 percent without depending on panel percent presets', () => {
const fit = fitLadderPlanForMinimumQty({
baseQty: '9',
minRequiredQty: '0.8',
percent: 30,
levels: 9,
stepSize: '0.01',
});

assert.equal(MAX_AUTO_FIT_LADDER_PERCENT, '100');
assert.equal(fit.maxPercent, '100');
assert.equal(fit.percent, '80');
assert.equal(fit.levels, 9);
assert.deepEqual(fit.allocation.quantities, Array(9).fill('0.8'));
});

test('reduces requested levels only when they need more than 100 percent', () => {
const fit = fitLadderPlanForMinimumQty({
baseQty: '10',
minRequiredQty: '1',
minRequiredQty: '2.1',
percent: 30,
levels: 9,
stepSize: '0.1',
maxPercent: '70',
});

assert.equal(fit.percent, '70');
assert.equal(fit.levels, 7);
assert.deepEqual(fit.allocation.quantities, ['1', '1', '1', '1', '1', '1', '1']);
assert.equal(fit.percent, '84');
assert.equal(fit.levels, 4);
assert.deepEqual(fit.allocation.quantities, ['2.1', '2.1', '2.1', '2.1']);
});

test('never lowers the saved percent while auto-reducing levels', () => {
const fit = fitLadderPlanForMinimumQty({
baseQty: '10',
minRequiredQty: '2.1',
percent: 90,
levels: 9,
stepSize: '0.1',
});

assert.equal(fit.percent, '90');
assert.equal(fit.levels, 4);
assert.deepEqual(fit.allocation.quantities, ['2.2', '2.2', '2.2', '2.4']);
});

test('rejects the ladder only when even one order needs more than 100 percent', () => {
const fit = fitLadderPlanForMinimumQty({
baseQty: '10',
minRequiredQty: '10.1',
percent: 30,
levels: 3,
stepSize: '0.1',
});

assert.equal(fit.allocation, null);
assert.equal(fit.maxPercent, '100');
});

test('auto-fit recomputes the minimum quantity for retained open ladder levels', () => {
Expand All @@ -83,7 +125,6 @@ test('auto-fit recomputes the minimum quantity for retained open ladder levels',
percent: 30,
levels: 5,
stepSize: '1',
maxPercent: '70',
});

assert.equal(fit.percent, '60');
Expand Down
9 changes: 5 additions & 4 deletions test/unit/binance-orderbook-trade/source-regressions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,8 @@ test('ladder minimum quantity failure explains safe manual options', () => {
const buildBody = readFunctionBody('buildLadderPlan');
assert.match(buildBody, /const minRequiredQtyByLevel = spec\.mode === 'OPEN'/);
assert.match(buildBody, /getQtyRuleContext\(startSymbol,\s*spec\.mode,\s*price\)\.effectiveMinQty \|\| ruleContext\.baseMinQty/);
assert.match(buildBody, /fitLadderPlanForMinimumQty\(\{\s*baseQty,\s*minRequiredQty,\s*minRequiredQtyByLevel,\s*percent,\s*levels,\s*stepSize: ruleContext\.stepSize,\s*maxPercent: getMaxAutoFitLadderPercent\(spec\.mode\),\s*\}\)/);
assert.match(buildBody, /fitLadderPlanForMinimumQty\(\{\s*baseQty,\s*minRequiredQty,\s*minRequiredQtyByLevel,\s*percent,\s*levels,\s*stepSize: ruleContext\.stepSize,\s*\}\)/);
assert.doesNotMatch(buildBody, /setLadderOpenPercent|setLadderLevels/);
assert.match(buildBody, /allocation = autoFit\.allocation/);
assert.match(buildBody, /percent = autoFit\.percent/);
assert.match(buildBody, /minRequiredQty = autoFit\.minRequiredQty \|\| minRequiredQty/);
Expand Down Expand Up @@ -498,6 +499,8 @@ test('ladder minimum quantity failure explains safe manual options', () => {
assert.match(percentBody, /formatDecimalParts\(scaledPercent,\s*2\)/);

const fitBody = readFunctionBody('fitLadderPlanForMinimumQty', ladderPlanSource);
assert.match(ladderPlanSource, /export const MAX_AUTO_FIT_LADDER_PERCENT = '100'/);
assert.match(fitBody, /const maxPercent = MAX_AUTO_FIT_LADDER_PERCENT/);
assert.match(fitBody, /getMinRequiredQtyForLevels\(minRequiredQty,\s*minRequiredQtyByLevel,\s*candidateLevels\)/);
assert.match(fitBody, /for \(let candidateLevels = requestedLevels; candidateLevels >= 1; candidateLevels -= 1\)/);
assert.match(fitBody, /computeMinimumLadderPercent\(baseQty,\s*candidateMinRequiredQty,\s*candidateLevels,\s*stepSize\)/);
Expand All @@ -506,9 +509,7 @@ test('ladder minimum quantity failure explains safe manual options', () => {
assert.match(fitBody, /minRequiredQty: candidateMinRequiredQty/);
assert.match(fitBody, /levels: candidateLevels/);

const maxBody = readFunctionBody('getMaxAutoFitLadderPercent');
assert.match(maxBody, /Math\.max\(\.\.\.LADDER_OPEN_PERCENTS\)/);
assert.match(maxBody, /100/);
assert.doesNotMatch(source, /getMaxAutoFitLadderPercent|Math\.max\(\.\.\.LADDER_OPEN_PERCENTS\)/);

const statusBody = readFunctionBody('setLadderStatus');
assert.match(statusBody, /statusEl\.title =/);
Expand Down
Loading