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
2 changes: 1 addition & 1 deletion docs/binance-orderbook-trade-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ Do not auto-confirm destructive Binance dialogs. The script may open Binance's n

USDT account rebalancing is a manual action, not an automatic post-close side effect. The inline `再平衡` action becomes available only after the account-order widget has continuously reported zero positions and zero open orders for three seconds, and a fresh all-symbol futures-position response also confirms that every position is flat. Starting any script trade or cancel task invalidates that eligibility immediately.

The rebalance path is pinned to the current Binance page bundle contract instead of automating the native transfer dialog. It reads Spot and Funding available USDT from `/bapi/asset/v2/private/asset-service/wallet/balance`, reads the U-M Futures transferable amount from `/bapi/futures/v1/private/future/user-data/getMaxWithdrawAmount`, and submits at most two sequential transfers through `/bapi/asset/v1/private/asset-service/wallet/transfer` with `{ asset, amount, kindType }`. The private transfer BAPI uses the page bundle's `MAIN`, `CARD`, and `FUTURE` account codes; do not substitute the public SAPI names `FUNDING` or `UMFUTURE`. It does not save an API key and does not fall back to DOM clicking if this private BAPI contract changes.
The rebalance path is pinned to the current Binance page bundle contract instead of automating the native transfer dialog. It reads Spot and Funding available USDT from `/bapi/asset/v2/private/asset-service/wallet/balance`, reads the U-M Futures transferable amount from `/bapi/futures/v1/private/future/user-data/getMaxWithdrawAmount`, and submits at most two sequential transfers through `/bapi/asset/v1/private/asset-service/wallet/transfer` with `{ asset, amount, kindType }`. Wallet rows are identified by the stable `accountType` values `MAIN`, `CARD`, and `FUTURE`; `walletName` is localized and must not be used as an identifier. The private transfer BAPI uses those same account codes; do not substitute the public SAPI names `FUNDING` or `UMFUTURE`. It does not save an API key and does not fall back to DOM clicking if this private BAPI contract changes.

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.

Expand Down
10 changes: 5 additions & 5 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.165
// @version 2.7.166
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -565,19 +565,19 @@
var ACCOUNT_ORDER = ["FUNDING", "MAIN", "UMFUTURE"];
var USDT_REBALANCE_ACCOUNTS = Object.freeze({
FUNDING: Object.freeze({
walletName: "Funding",
accountType: "CARD",
bapiCode: "CARD",
label: "资金",
ratio: 50
}),
MAIN: Object.freeze({
walletName: "Spot",
accountType: "MAIN",
bapiCode: "MAIN",
label: "现货",
ratio: 40
}),
UMFUTURE: Object.freeze({
walletName: "USDⓈ-M Futures",
accountType: "FUTURE",
bapiCode: "FUTURE",
label: "U本位合约",
ratio: 10
Expand Down Expand Up @@ -649,7 +649,7 @@
const balances = {};
for (const accountCode of ACCOUNT_ORDER) {
const account = USDT_REBALANCE_ACCOUNTS[accountCode];
const matches = payload.data.filter((wallet) => wallet?.walletName === account.walletName);
const matches = payload.data.filter((wallet) => wallet?.accountType === account.accountType);
if (matches.length === 0) throw new Error(`钱包余额缺少 ${account.label}账户`);
if (matches.length > 1) throw new Error(`钱包余额存在重复的${account.label}账户`);
const free = readWalletUsdtBalance(matches[0], account);
Expand Down
13 changes: 7 additions & 6 deletions src/binance-orderbook-trade/core/usdt-rebalance.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,25 @@ const USDT_SCALE = 8;
const ACCOUNT_ORDER = ['FUNDING', 'MAIN', 'UMFUTURE'];

/**
* The private transfer BAPI uses the current page bundle's account codes, which
* intentionally differ from the public universal-transfer API enum names.
* Wallet responses and the private transfer BAPI share the current page
* bundle's stable account codes. `walletName` is localized and cannot identify
* an account.
*/
export const USDT_REBALANCE_ACCOUNTS = Object.freeze({
FUNDING: Object.freeze({
walletName: 'Funding',
accountType: 'CARD',
bapiCode: 'CARD',
label: '资金',
ratio: 50,
}),
MAIN: Object.freeze({
walletName: 'Spot',
accountType: 'MAIN',
bapiCode: 'MAIN',
label: '现货',
ratio: 40,
}),
UMFUTURE: Object.freeze({
walletName: 'USDⓈ-M Futures',
accountType: 'FUTURE',
bapiCode: 'FUTURE',
label: 'U本位合约',
ratio: 10,
Expand Down Expand Up @@ -105,7 +106,7 @@ export function parseUsdtWalletBalances(payload) {
const balances = {};
for (const accountCode of ACCOUNT_ORDER) {
const account = USDT_REBALANCE_ACCOUNTS[accountCode];
const matches = payload.data.filter((wallet) => wallet?.walletName === account.walletName);
const matches = payload.data.filter((wallet) => wallet?.accountType === account.accountType);
if (matches.length === 0) throw new Error(`钱包余额缺少 ${account.label}账户`);
if (matches.length > 1) throw new Error(`钱包余额存在重复的${account.label}账户`);
const free = readWalletUsdtBalance(matches[0], account);
Expand Down
2 changes: 1 addition & 1 deletion 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.165
// @version 2.7.166
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down
47 changes: 30 additions & 17 deletions test/unit/binance-orderbook-trade/usdt-rebalance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,24 @@ import {
withFuturesTransferableBalance,
} from '../../../src/binance-orderbook-trade/core/usdt-rebalance.js';

test('private transfer BAPI account codes match the current Binance page bundle', () => {
test('wallet response and private transfer use stable account codes from the current page bundle', () => {
assert.deepEqual({
funding: USDT_REBALANCE_ACCOUNTS.FUNDING.bapiCode,
spot: USDT_REBALANCE_ACCOUNTS.MAIN.bapiCode,
futures: USDT_REBALANCE_ACCOUNTS.UMFUTURE.bapiCode,
funding: {
response: USDT_REBALANCE_ACCOUNTS.FUNDING.accountType,
transfer: USDT_REBALANCE_ACCOUNTS.FUNDING.bapiCode,
},
spot: {
response: USDT_REBALANCE_ACCOUNTS.MAIN.accountType,
transfer: USDT_REBALANCE_ACCOUNTS.MAIN.bapiCode,
},
futures: {
response: USDT_REBALANCE_ACCOUNTS.UMFUTURE.accountType,
transfer: USDT_REBALANCE_ACCOUNTS.UMFUTURE.bapiCode,
},
}, {
funding: 'CARD',
spot: 'MAIN',
futures: 'FUTURE',
funding: { response: 'CARD', transfer: 'CARD' },
spot: { response: 'MAIN', transfer: 'MAIN' },
futures: { response: 'FUTURE', transfer: 'FUTURE' },
});
});

Expand Down Expand Up @@ -67,21 +76,24 @@ test('wallet response reads exact USDT free balances for spot and funding', () =
data: [
{
activate: true,
walletName: 'Spot',
accountType: 'MAIN',
walletName: '现货账户',
assetBalances: [{
asset: 'USDT', free: '40.5', locked: '0', freeze: '0', withdrawing: '0',
}],
},
{
activate: true,
walletName: 'Funding',
accountType: 'CARD',
walletName: '资金账户',
assetBalances: [{
asset: 'USDT', free: '50', locked: '0', freeze: '0', withdrawing: '0',
}],
},
{
activate: true,
walletName: 'USDⓈ-M Futures',
accountType: 'FUTURE',
walletName: '合约账户(U本位)',
assetBalances: [{
asset: 'USDT', free: '12', locked: '0', freeze: '0', withdrawing: '0',
}],
Expand All @@ -95,9 +107,10 @@ test('wallet response reads exact USDT free balances for spot and funding', () =
});

test('wallet response rejects locked, frozen, withdrawing, missing, and duplicate balances', () => {
const wallet = (walletName, overrides = {}) => ({
const wallet = (accountType, overrides = {}) => ({
activate: true,
walletName,
accountType,
walletName: `localized-${accountType}`,
assetBalances: [{
asset: 'USDT', free: '1', locked: '0', freeze: '0', withdrawing: '0', ...overrides,
}],
Expand All @@ -106,35 +119,35 @@ test('wallet response rejects locked, frozen, withdrawing, missing, and duplicat
assert.throws(
() => parseUsdtWalletBalances({
success: true,
data: [wallet('Spot', { locked: '0.1' }), wallet('Funding'), wallet('USDⓈ-M Futures')],
data: [wallet('MAIN', { locked: '0.1' }), wallet('CARD'), wallet('FUTURE')],
}),
/现货账户仍有不可划转 USDT/,
);
assert.throws(
() => parseUsdtWalletBalances({
success: true,
data: [wallet('Spot'), wallet('Funding', { freeze: '0.1' }), wallet('USDⓈ-M Futures')],
data: [wallet('MAIN'), wallet('CARD', { freeze: '0.1' }), wallet('FUTURE')],
}),
/资金账户仍有不可划转 USDT/,
);
assert.throws(
() => parseUsdtWalletBalances({
success: true,
data: [wallet('Spot'), wallet('Funding'), wallet('USDⓈ-M Futures', { withdrawing: '0.1' })],
data: [wallet('MAIN'), wallet('CARD'), wallet('FUTURE', { withdrawing: '0.1' })],
}),
/U本位合约账户仍有不可划转 USDT/,
);
assert.throws(
() => parseUsdtWalletBalances({
success: true,
data: [wallet('Spot'), wallet('Funding')],
data: [wallet('MAIN'), wallet('CARD')],
}),
/缺少 U本位合约账户/,
);
assert.throws(
() => parseUsdtWalletBalances({
success: true,
data: [wallet('Spot'), wallet('Spot'), wallet('Funding'), wallet('USDⓈ-M Futures')],
data: [wallet('MAIN'), wallet('MAIN'), wallet('CARD'), wallet('FUTURE')],
}),
/重复的现货账户/,
);
Expand Down
Loading