From 2c081aeedfffda32e02f71467a3a1181f80524fd Mon Sep 17 00:00:00 2001 From: Zaixin Cheng Date: Tue, 12 May 2026 05:32:03 -0400 Subject: [PATCH] feat: add user force orders endpoint --- README.md | 2 +- .../trade-user-force-orders.service.spec.ts | 81 ++++++++++++++++++ src/bingx-client/services/trade.service.ts | 13 +++ .../bingx-user-force-orders-endpoint.ts | 85 +++++++++++++++++++ src/bingx/endpoints/index.ts | 1 + 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/bingx-client/services/trade-user-force-orders.service.spec.ts create mode 100644 src/bingx/endpoints/bingx-user-force-orders-endpoint.ts diff --git a/README.md b/README.md index 6dd246f..0be3834 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ stream.latestTradeDetail$.subscribe((v) => {}) - [ ] Switch Margin Mode - [ ] Query Leverage - [ ] Switch Leverage - - [ ] User's Force Orders + - [x] User's Force Orders - [x] User's History Orders - [ ] Adjust isolated margin - [ ] Query historical transaction orders diff --git a/src/bingx-client/services/trade-user-force-orders.service.spec.ts b/src/bingx-client/services/trade-user-force-orders.service.spec.ts new file mode 100644 index 0000000..5d7f26e --- /dev/null +++ b/src/bingx-client/services/trade-user-force-orders.service.spec.ts @@ -0,0 +1,81 @@ +import { TradeService } from 'bingx-api/bingx-client/services/trade.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 { BingxUserForceOrdersEndpoint } from 'bingx-api/bingx/endpoints/bingx-user-force-orders-endpoint'; + +describe('trade user force orders 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 user force orders endpoint', async () => { + const service = new TradeService(requestExecutor); + const startTime = new Date('2026-01-02T03:04:05.006Z'); + const endTime = 1770000000000; + + const endpoint = (await service.getUserForceOrders( + { + symbol: 'ATOM-USDT', + currency: 'USDT', + autoCloseType: 'LIQUIDATION', + startTime, + endTime, + limit: 100, + recvWindow: 5000, + }, + account, + )) as unknown as BingxUserForceOrdersEndpoint; + + expect(executeSpy).toHaveBeenCalledTimes(1); + expect(capturedEndpoints[0]).toBe(endpoint); + expect(endpoint).toBeInstanceOf(BingxUserForceOrdersEndpoint); + expect(endpoint.method()).toBe('get'); + expect(endpoint.path()).toBe('/openApi/swap/v2/trade/forceOrders'); + expect(endpoint.parameters().asRecord()).toEqual({ + symbol: 'ATOM-USDT', + currency: 'USDT', + autoCloseType: 'LIQUIDATION', + startTime: startTime.getTime().toString(10), + endTime: endTime.toString(10), + limit: '100', + recvWindow: '5000', + timestamp: '1770000000123', + }); + }); + + it('omits optional filters when no force order options are provided', () => { + const endpoint = new BingxUserForceOrdersEndpoint({}, account); + + expect(endpoint.parameters().asRecord()).toEqual({ + timestamp: '1770000000123', + }); + }); +}); diff --git a/src/bingx-client/services/trade.service.ts b/src/bingx-client/services/trade.service.ts index 65fa5bd..e6b6ce6 100644 --- a/src/bingx-client/services/trade.service.ts +++ b/src/bingx-client/services/trade.service.ts @@ -12,6 +12,10 @@ import { BingxSwitchLeverageEndpoint } from 'bingx-api/bingx/endpoints/bingx-swi import { OrderPositionSideEnum } from 'bingx-api/bingx'; import { BingxUserHistoryOrdersEndpoint } from 'bingx-api/bingx/endpoints/bingx-user-history-orders-endpoint'; import { BingxCancelOrderEndpoint } from 'bingx-api/bingx/endpoints/bingx-cancel-order-endpoint'; +import { + BingxUserForceOrdersEndpoint, + BingxUserForceOrdersOptions, +} from 'bingx-api/bingx/endpoints/bingx-user-force-orders-endpoint'; export class TradeService { constructor(private readonly requestExecutor: RequestExecutorInterface) {} @@ -62,6 +66,15 @@ export class TradeService { ); } + public async getUserForceOrders( + options: BingxUserForceOrdersOptions, + account: AccountInterface, + ) { + return this.requestExecutor.execute( + new BingxUserForceOrdersEndpoint(options, account), + ); + } + public closeAllPositions(account: AccountInterface) { return this.requestExecutor.execute( new BingxCloseAllPositionsEndpoint(account), diff --git a/src/bingx/endpoints/bingx-user-force-orders-endpoint.ts b/src/bingx/endpoints/bingx-user-force-orders-endpoint.ts new file mode 100644 index 0000000..50436a1 --- /dev/null +++ b/src/bingx/endpoints/bingx-user-force-orders-endpoint.ts @@ -0,0 +1,85 @@ +import { + AccountInterface, + BingxResponse, + DefaultSignatureParameters, + EndpointInterface, + SignatureParametersInterface, +} from 'bingx-api/bingx'; +import { Endpoint } from 'bingx-api/bingx/endpoints/endpoint'; +import { BingxUserHistoryOrdersResponse } from 'bingx-api/bingx/endpoints/bingx-user-history-orders-response'; + +export type ForceOrderAutoCloseType = 'LIQUIDATION' | 'ADL'; + +export interface BingxUserForceOrdersOptions { + symbol?: string; + currency?: string; + autoCloseType?: ForceOrderAutoCloseType; + startTime?: Date | number; + endTime?: Date | number; + limit?: number; + recvWindow?: string | number; +} + +export class BingxUserForceOrdersEndpoint< + R extends BingxUserHistoryOrdersResponse = BingxUserHistoryOrdersResponse, + > + extends Endpoint> + implements EndpointInterface> +{ + constructor( + private readonly options: BingxUserForceOrdersOptions, + 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.currency !== undefined) { + parameters.currency = this.options.currency; + } + + if (this.options.autoCloseType !== undefined) { + parameters.autoCloseType = this.options.autoCloseType; + } + + 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/trade/forceOrders'; + } + + 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..1286f81 100644 --- a/src/bingx/endpoints/index.ts +++ b/src/bingx/endpoints/index.ts @@ -10,6 +10,7 @@ export * from './bingx-response.interface'; export * from './bingx-trade-order-endpoint'; export * from './endpoint.interface'; export * from './endpoint'; +export * from './bingx-user-force-orders-endpoint'; export * from './bingx-user-history-orders-endpoint'; export * from './bingx-user-history-orders-response'; export * from './bingx-cancel-order-endpoint';