Skip to content
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 81 additions & 0 deletions src/bingx-client/services/trade-user-force-orders.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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<unknown>[];
let requestExecutor: RequestExecutorInterface;
let executeSpy: jest.SpyInstance;
let nowSpy: jest.SpyInstance<number, []>;

beforeEach(() => {
account = {
getApiKey: jest.fn(() => 'api-key'),
sign: jest.fn(() => ({
toString: () => 'signature',
secretKey: () => 'secret-key',
})),
};

capturedEndpoints = [];
requestExecutor = {
execute<T>(endpoint: EndpointInterface<T>): Promise<T> {
capturedEndpoints.push(endpoint as EndpointInterface<unknown>);
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',
});
});
});
13 changes: 13 additions & 0 deletions src/bingx-client/services/trade.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down Expand Up @@ -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),
Expand Down
85 changes: 85 additions & 0 deletions src/bingx/endpoints/bingx-user-force-orders-endpoint.ts
Original file line number Diff line number Diff line change
@@ -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<BingxResponse<R>>
implements EndpointInterface<BingxResponse<R>>
{
constructor(
private readonly options: BingxUserForceOrdersOptions,
account: AccountInterface,
) {
super(account);
}

method(): 'get' | 'post' | 'put' | 'patch' | 'delete' {
return 'get';
}

parameters(): SignatureParametersInterface {
const parameters: Record<string, string> = {};

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<R>;
}
1 change: 1 addition & 0 deletions src/bingx/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';