From 195068997d34e2ccfc4011a70dc391005af2adf2 Mon Sep 17 00:00:00 2001 From: Zaixin Cheng Date: Tue, 12 May 2026 05:11:50 -0400 Subject: [PATCH] feat: add account income endpoint --- README.md | 2 +- .../services/account-income.service.spec.ts | 79 +++++++++++++++++ src/bingx-client/services/account.service.ts | 13 +++ ...-account-profit-loss-fund-flow-endpoint.ts | 88 +++++++++++++++++++ src/bingx/endpoints/index.ts | 1 + 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/bingx-client/services/account-income.service.spec.ts create mode 100644 src/bingx/endpoints/bingx-account-profit-loss-fund-flow-endpoint.ts diff --git a/README.md b/README.md index 6dd246f..2bd34ba 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ stream.latestTradeDetail$.subscribe((v) => {}) * Account Interface - [x] Get Perpetual Swap Account Asset Information - [x] Perpetual Swap Positions - - [ ] Get Account Profit and Loss Fund Flow + - [x] Get Account Profit and Loss Fund Flow - [ ] Export fund flow - [ ] User fee rate * Trade Interface diff --git a/src/bingx-client/services/account-income.service.spec.ts b/src/bingx-client/services/account-income.service.spec.ts new file mode 100644 index 0000000..f626e72 --- /dev/null +++ b/src/bingx-client/services/account-income.service.spec.ts @@ -0,0 +1,79 @@ +import { AccountService } from 'bingx-api/bingx-client/services/account.service'; +import { AccountInterface } from 'bingx-api/bingx/account/account.interface'; +import { RequestExecutorInterface } from 'bingx-api/bingx/request-executor/request-executor.interface'; +import { EndpointInterface } from 'bingx-api/bingx/endpoints/endpoint.interface'; +import { BingxAccountProfitLossFundFlowEndpoint } from 'bingx-api/bingx/endpoints/bingx-account-profit-loss-fund-flow-endpoint'; + +describe('account income service', () => { + let account: AccountInterface; + let capturedEndpoints: EndpointInterface[]; + let requestExecutor: RequestExecutorInterface; + let executeSpy: jest.SpyInstance; + let nowSpy: jest.SpyInstance; + + beforeEach(() => { + account = { + getApiKey: jest.fn(() => 'api-key'), + sign: jest.fn(() => ({ + toString: () => 'signature', + secretKey: () => 'secret-key', + })), + }; + + capturedEndpoints = []; + requestExecutor = { + execute(endpoint: EndpointInterface): Promise { + capturedEndpoints.push(endpoint as EndpointInterface); + return Promise.resolve(endpoint as unknown as T); + }, + }; + + executeSpy = jest.spyOn(requestExecutor, 'execute'); + nowSpy = jest.spyOn(Date, 'now').mockReturnValue(1770000000123); + }); + + afterEach(() => { + nowSpy.mockRestore(); + }); + + it('dispatches the signed account profit and loss fund flow endpoint', async () => { + const service = new AccountService(requestExecutor); + const startTime = new Date('2026-01-02T03:04:05.006Z'); + const endTime = 1770000000000; + + const endpoint = (await service.getAccountProfitLossFundFlow( + { + symbol: 'BTC-USDT', + incomeType: 'FUNDING_FEE', + startTime, + endTime, + limit: 100, + recvWindow: 5000, + }, + account, + )) as unknown as BingxAccountProfitLossFundFlowEndpoint; + + expect(executeSpy).toHaveBeenCalledTimes(1); + expect(capturedEndpoints[0]).toBe(endpoint); + expect(endpoint).toBeInstanceOf(BingxAccountProfitLossFundFlowEndpoint); + expect(endpoint.method()).toBe('get'); + expect(endpoint.path()).toBe('/openApi/swap/v2/user/income'); + expect(endpoint.parameters().asRecord()).toEqual({ + symbol: 'BTC-USDT', + incomeType: 'FUNDING_FEE', + startTime: startTime.getTime().toString(10), + endTime: endTime.toString(10), + limit: '100', + recvWindow: '5000', + timestamp: '1770000000123', + }); + }); + + it('omits optional filters when no fund flow options are provided', () => { + const endpoint = new BingxAccountProfitLossFundFlowEndpoint({}, account); + + expect(endpoint.parameters().asRecord()).toEqual({ + timestamp: '1770000000123', + }); + }); +}); diff --git a/src/bingx-client/services/account.service.ts b/src/bingx-client/services/account.service.ts index d6d9a03..8923bdf 100644 --- a/src/bingx-client/services/account.service.ts +++ b/src/bingx-client/services/account.service.ts @@ -2,6 +2,10 @@ import { RequestExecutorInterface } from 'bingx-api/bingx/request-executor/reque import { AccountInterface } from 'bingx-api/bingx/account/account.interface'; import { BingxGetPerpetualSwapAccountAssetEndpoint } from 'bingx-api/bingx/endpoints/bingx-get-perpetual-swap-account-asset-endpoint'; import { BingxPerpetualSwapPositionsEndpoint } from 'bingx-api/bingx/endpoints/bingx-perpetual-swap-positions-endpoint'; +import { + BingxAccountProfitLossFundFlowEndpoint, + BingxAccountProfitLossFundFlowOptions, +} from 'bingx-api/bingx/endpoints/bingx-account-profit-loss-fund-flow-endpoint'; export class AccountService { constructor(private readonly requestExecutor: RequestExecutorInterface) {} @@ -17,4 +21,13 @@ export class AccountService { new BingxPerpetualSwapPositionsEndpoint(symbol, account), ); } + + public getAccountProfitLossFundFlow( + options: BingxAccountProfitLossFundFlowOptions, + account: AccountInterface, + ) { + return this.requestExecutor.execute( + new BingxAccountProfitLossFundFlowEndpoint(options, account), + ); + } } diff --git a/src/bingx/endpoints/bingx-account-profit-loss-fund-flow-endpoint.ts b/src/bingx/endpoints/bingx-account-profit-loss-fund-flow-endpoint.ts new file mode 100644 index 0000000..831ae5b --- /dev/null +++ b/src/bingx/endpoints/bingx-account-profit-loss-fund-flow-endpoint.ts @@ -0,0 +1,88 @@ +import { + AccountInterface, + BingxResponse, + DefaultSignatureParameters, + EndpointInterface, + SignatureParametersInterface, +} from 'bingx-api/bingx'; +import { Endpoint } from 'bingx-api/bingx/endpoints/endpoint'; + +export interface BingxAccountProfitLossFundFlowOptions { + symbol?: string; + incomeType?: string; + startTime?: Date | number; + endTime?: Date | number; + limit?: number; + recvWindow?: string | number; +} + +export interface BingxAccountProfitLossFundFlowData { + symbol: string; + incomeType: string; + income: string; + asset: string; + info: string; + time: number; + tranId: string; + tradeId: string; +} + +export class BingxAccountProfitLossFundFlowEndpoint< + R = BingxAccountProfitLossFundFlowData[], + > + extends Endpoint> + implements EndpointInterface> +{ + constructor( + private readonly options: BingxAccountProfitLossFundFlowOptions, + account: AccountInterface, + ) { + super(account); + } + + method(): 'get' | 'post' | 'put' | 'patch' | 'delete' { + return 'get'; + } + + parameters(): SignatureParametersInterface { + const parameters: Record = {}; + + if (this.options.symbol !== undefined) { + parameters.symbol = this.options.symbol; + } + + if (this.options.incomeType !== undefined) { + parameters.incomeType = this.options.incomeType; + } + + if (this.options.startTime !== undefined) { + parameters.startTime = this.timestampAsString(this.options.startTime); + } + + if (this.options.endTime !== undefined) { + parameters.endTime = this.timestampAsString(this.options.endTime); + } + + if (this.options.limit !== undefined) { + parameters.limit = this.options.limit.toString(10); + } + + if (this.options.recvWindow !== undefined) { + parameters.recvWindow = this.options.recvWindow.toString(); + } + + return new DefaultSignatureParameters(parameters); + } + + path(): string { + return '/openApi/swap/v2/user/income'; + } + + private timestampAsString(value: Date | number): string { + return value instanceof Date + ? value.getTime().toString(10) + : value.toString(); + } + + readonly t!: BingxResponse; +} diff --git a/src/bingx/endpoints/index.ts b/src/bingx/endpoints/index.ts index 6866f8b..b85074a 100644 --- a/src/bingx/endpoints/index.ts +++ b/src/bingx/endpoints/index.ts @@ -1,4 +1,5 @@ export * from './bingx-cancel-all-orders-endpoint'; +export * from './bingx-account-profit-loss-fund-flow-endpoint'; export * from './bingx-close-all-positions-endpoint'; export * from './bingx-generate-listen-key-endpoint'; export * from './bingx-generate-listen-key-response';