import { FutureConnector } from '../../src/exchanges/binance/FutureConnector' const describeBinance = process.env.TEST_BINANCE === '1' ? describe : describe.skip import { DerivativesTradingUsdsFuturesRestAPI } from '@binance/derivatives-trading-usds-futures' // Mock Binance API client jest.mock('@binance/derivatives-trading-usds-futures', () => ({ DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL: 'https://fapi.binance.com', DerivativesTradingUsdsFutures: jest.fn(), DerivativesTradingUsdsFuturesRestAPI: { NewOrderSideEnum: { BUY: 'BUY', SELL: 'SELL', }, NewOrderPositionSideEnum: { LONG: 'LONG', SHORT: 'SHORT', BOTH: 'BOTH', }, NewOrderTimeInForceEnum: { GTC: 'GTC', IOC: 'IOC', FOK: 'FOK', GTX: 'GTX', GTD: 'GTD', }, NewOrderNewOrderRespTypeEnum: { ACK: 'ACK', RESULT: 'RESULT', FULL: 'FULL', }, ChangeMarginTypeMarginTypeEnum: { ISOLATED: 'ISOLATED', CROSSED: 'CROSSED', }, ModifyOrderSideEnum: { BUY: 'BUY', SELL: 'SELL', }, }, })) describeBinance('FutureConnector', () => { let connector: FutureConnector let mockClient: any beforeEach(() => { // 创建模拟的 API 客户端 mockClient = { restAPI: { accountInformationV3: jest.fn(), newOrder: jest.fn(), modifyOrder: jest.fn(), currentAllOpenOrders: jest.fn(), cancelOrder: jest.fn(), cancelAllOpenOrders: jest.fn(), allOrders: jest.fn(), accountTradeList: jest.fn(), changeInitialLeverage: jest.fn(), changeMarginType: jest.fn(), }, } // Mock 构造函数 const { DerivativesTradingUsdsFutures } = require('@binance/derivatives-trading-usds-futures') DerivativesTradingUsdsFutures.mockImplementation(() => mockClient) connector = new FutureConnector('test-api-key', 'test-api-secret') }) afterEach(() => { jest.clearAllMocks() }) describe('Constructor', () => { test('should create instance with API credentials', () => { expect(connector).toBeInstanceOf(FutureConnector) expect(connector.client).toBeDefined() }) test('should initialize with correct configuration', () => { const { DerivativesTradingUsdsFutures } = require('@binance/derivatives-trading-usds-futures') expect(DerivativesTradingUsdsFutures).toHaveBeenCalledWith({ configurationRestAPI: { apiKey: 'test-api-key', apiSecret: 'test-api-secret', basePath: 'https://fapi.binance.com', }, }) }) }) describe('getAssetsInfo', () => { test('should return assets with positive wallet balance', async () => { const mockData = { assets: [ { symbol: 'USDT', walletBalance: '1000.0' }, { symbol: 'BTC', walletBalance: '0.0' }, { symbol: 'ETH', walletBalance: '5.0' }, ], } mockClient.restAPI.accountInformationV3.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockData), }) const result = await connector.getAssetsInfo() expect(result).toEqual([ { symbol: 'USDT', walletBalance: '1000.0' }, { symbol: 'ETH', walletBalance: '5.0' }, ]) expect(mockClient.restAPI.accountInformationV3).toHaveBeenCalled() }) test('should handle API errors gracefully', async () => { mockClient.restAPI.accountInformationV3.mockRejectedValue(new Error('API Error')) const result = await connector.getAssetsInfo() expect(result).toEqual([]) }) }) describe('getPositonInfo', () => { test('should return positions with positive position amount', async () => { const mockData = { positions: [ { symbol: 'BTCUSDT', positionAmt: '0.1' }, { symbol: 'ETHUSDT', positionAmt: '0.0' }, { symbol: 'ADAUSDT', positionAmt: '100.0' }, ], } mockClient.restAPI.accountInformationV3.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockData), }) const result = await connector.getPositonInfo() expect(result).toEqual([ { symbol: 'BTCUSDT', positionAmt: '0.1' }, { symbol: 'ADAUSDT', positionAmt: '100.0' }, ]) }) test('should handle API errors gracefully', async () => { mockClient.restAPI.accountInformationV3.mockRejectedValue(new Error('API Error')) const result = await connector.getPositonInfo() expect(result).toEqual([]) }) }) describe('getPositionBalanceBySymbol', () => { test('should return position for existing symbol', async () => { const mockData = { positions: [ { symbol: 'BTCUSDT', positionAmt: '0.1', unrealizedPnl: '10.5' }, { symbol: 'ETHUSDT', positionAmt: '0.0', unrealizedPnl: '0.0' }, ], } mockClient.restAPI.accountInformationV3.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockData), }) const result = await connector.getPositionBalanceBySymbol('BTCUSDT') expect(result).toEqual({ symbol: 'BTCUSDT', positionAmt: '0.1', unrealizedPnl: '10.5' }) }) test('should return null for non-existing symbol', async () => { const mockData = { positions: [{ symbol: 'BTCUSDT', positionAmt: '0.1' }], } mockClient.restAPI.accountInformationV3.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockData), }) const result = await connector.getPositionBalanceBySymbol('ETHUSDT') expect(result).toBeNull() }) }) describe('openPosition', () => { test('should place order with basic parameters', async () => { const mockOrderResult = { orderId: 12345, symbol: 'BTCUSDT', status: 'NEW', } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openPosition( 'BTCUSDT', DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY, 0.001, 50000, ) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', timeInForce: 'GTC', quantity: 0.001, price: 50000, }) }) test('should place order with all optional parameters', async () => { const mockOrderResult = { orderId: 12346, symbol: 'ETHUSDT', status: 'NEW', } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const options = { type: 'LIMIT', timeInForce: DerivativesTradingUsdsFuturesRestAPI.NewOrderTimeInForceEnum.GTC, positionSide: DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG, reduceOnly: 'false', closePosition: 'false', activationPrice: 3000, callbackRate: 0.1, workingType: 'CONTRACT_PRICE', priceProtect: 'TRUE', newOrderRespType: DerivativesTradingUsdsFuturesRestAPI.NewOrderNewOrderRespTypeEnum.RESULT, newClientOrderId: 'test_order_123', stopPrice: 2800, icebergQty: 0.001, orderTag: 'test_tag', } const result = await connector.openPosition( 'ETHUSDT', DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY, 0.01, 3000, options, ) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'ETHUSDT', side: 'BUY', type: 'LIMIT', timeInForce: 'GTC', quantity: 0.01, price: 3000, positionSide: 'LONG', reduceOnly: 'false', closePosition: 'false', activationPrice: 3000, callbackRate: 0.1, workingType: 'CONTRACT_PRICE', priceProtect: 'TRUE', newOrderRespType: 'RESULT', newClientOrderId: 'test_order_123', stopPrice: 2800, icebergQty: 0.001, orderTag: 'test_tag', }) }) test('should handle API errors gracefully', async () => { mockClient.restAPI.newOrder.mockRejectedValue(new Error('Order failed')) const result = await connector.openPosition( 'BTCUSDT', DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY, 0.001, 50000, ) expect(result).toBeNull() }) }) describe('openLimitOrder', () => { test('should place long limit order', async () => { const mockOrderResult = { orderId: 12347, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openLimitOrder('BTCUSDT', 'long', 0.001, 50000, 'GTC') expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', timeInForce: 'GTC', quantity: 0.001, price: 50000, positionSide: 'LONG', }) }) test('should place short limit order', async () => { const mockOrderResult = { orderId: 12348, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openLimitOrder('BTCUSDT', 'short', 0.001, 50000, 'GTC') expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'SELL', type: 'LIMIT', timeInForce: 'GTC', quantity: 0.001, price: 50000, positionSide: 'SHORT', }) }) }) describe('openMarketOrder', () => { test('should place long market order', async () => { const mockOrderResult = { orderId: 12349, status: 'FILLED' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openMarketOrder('BTCUSDT', 'long', 0.001) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, positionSide: 'LONG', }) }) test('should place short market order', async () => { const mockOrderResult = { orderId: 12350, status: 'FILLED' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openMarketOrder('BTCUSDT', 'short', 0.001) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'SELL', type: 'MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, positionSide: 'SHORT', }) }) }) describe('openStopOrder', () => { test('should place stop market order', async () => { const mockOrderResult = { orderId: 12351, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openStopOrder('BTCUSDT', 'long', 0.001, 48000) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'STOP_MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, stopPrice: 48000, reduceOnly: 'true', positionSide: 'LONG', }) }) test('should place stop limit order', async () => { const mockOrderResult = { orderId: 12352, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openStopOrder('BTCUSDT', 'short', 0.001, 52000, 'STOP', 51900, 'GTC') expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'SELL', type: 'STOP', timeInForce: 'GTC', quantity: 0.001, price: 51900, stopPrice: 52000, reduceOnly: 'true', positionSide: 'SHORT', }) }) }) describe('openTakeProfitOrder', () => { test('should place take profit market order', async () => { const mockOrderResult = { orderId: 12353, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openTakeProfitOrder('BTCUSDT', 'long', 0.001, 52000) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'TAKE_PROFIT_MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, stopPrice: 52000, reduceOnly: 'true', positionSide: 'LONG', }) }) }) describe('openTrailingStopOrder', () => { test('should place trailing stop order', async () => { const mockOrderResult = { orderId: 12354, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openTrailingStopOrder('BTCUSDT', 'long', 0.001, 50000, 0.1) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'TRAILING_STOP_MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, activationPrice: 50000, callbackRate: 0.1, reduceOnly: 'true', positionSide: 'LONG', }) }) }) describe('openIcebergOrder', () => { test('should place iceberg order', async () => { const mockOrderResult = { orderId: 12355, status: 'NEW' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.openIcebergOrder('BTCUSDT', 'long', 0.01, 50000, 0.001, 'GTC') expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', timeInForce: 'GTC', quantity: 0.01, price: 50000, icebergQty: 0.001, positionSide: 'LONG', }) }) }) describe('closeLongPosition', () => { test('should close long position', async () => { const mockOrderResult = { orderId: 12356, status: 'FILLED' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.closeLongPosition('BTCUSDT', 0.001) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'SELL', type: 'MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, reduceOnly: 'true', positionSide: 'LONG', }) }) }) describe('closeShortPosition', () => { test('should close short position', async () => { const mockOrderResult = { orderId: 12357, status: 'FILLED' } mockClient.restAPI.newOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrderResult), }) const result = await connector.closeShortPosition('BTCUSDT', 0.001) expect(result).toEqual(mockOrderResult) expect(mockClient.restAPI.newOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', timeInForce: 'GTC', quantity: 0.001, price: 0, reduceOnly: 'true', positionSide: 'SHORT', }) }) }) describe('closeAllPositions', () => { test('should close all positions', async () => { const mockPositions = [ { symbol: 'BTCUSDT', positionAmt: '0.1' }, { symbol: 'ETHUSDT', positionAmt: '-0.05' }, ] mockClient.restAPI.accountInformationV3.mockResolvedValue({ data: jest.fn().mockResolvedValue({ positions: mockPositions }), }) const mockOrderResults = [ { orderId: 12358, status: 'FILLED' }, { orderId: 12359, status: 'FILLED' }, ] mockClient.restAPI.newOrder .mockResolvedValueOnce({ data: jest.fn().mockResolvedValue(mockOrderResults[0]) }) .mockResolvedValueOnce({ data: jest.fn().mockResolvedValue(mockOrderResults[1]) }) const result = await connector.closeAllPositions() expect(result).toEqual(mockOrderResults) expect(mockClient.restAPI.newOrder).toHaveBeenCalledTimes(2) }) }) describe('cancelOrder', () => { test('should cancel order by orderId', async () => { const mockResult = { orderId: 12360, status: 'CANCELED' } mockClient.restAPI.cancelOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.cancelOrder('BTCUSDT', 12360) expect(mockClient.restAPI.cancelOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', orderId: 12360, origClientOrderId: undefined, }) }) test('should cancel order by clientOrderId', async () => { const mockResult = { orderId: 12361, status: 'CANCELED' } mockClient.restAPI.cancelOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.cancelOrder('BTCUSDT', undefined, 'client_order_123') expect(mockClient.restAPI.cancelOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', orderId: undefined, origClientOrderId: 'client_order_123', }) }) }) describe('cancelAllOrders', () => { test('should cancel all orders for symbol', async () => { const mockResult = [{ orderId: 12362, status: 'CANCELED' }] mockClient.restAPI.cancelAllOpenOrders.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.cancelAllOrders('BTCUSDT') expect(mockClient.restAPI.cancelAllOpenOrders).toHaveBeenCalledWith({ symbol: 'BTCUSDT', }) }) }) describe('getOrderHistory', () => { test('should get order history', async () => { const mockOrders = [ { orderId: 12363, symbol: 'BTCUSDT', status: 'FILLED' }, { orderId: 12364, symbol: 'BTCUSDT', status: 'CANCELED' }, ] mockClient.restAPI.allOrders.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrders), }) const result = await connector.getOrderHistory('BTCUSDT', 1640995200000, 1641081600000, 100) expect(result).toEqual(mockOrders) expect(mockClient.restAPI.allOrders).toHaveBeenCalledWith({ symbol: 'BTCUSDT', startTime: 1640995200000, endTime: 1641081600000, limit: 100, }) }) }) describe('getTradeHistory', () => { test('should get trade history', async () => { const mockTrades = [ { id: 12365, symbol: 'BTCUSDT', price: '50000', qty: '0.001' }, { id: 12366, symbol: 'BTCUSDT', price: '50100', qty: '0.002' }, ] mockClient.restAPI.accountTradeList.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockTrades), }) const result = await connector.getTradeHistory('BTCUSDT', 1640995200000, 1641081600000, 100) expect(result).toEqual(mockTrades) expect(mockClient.restAPI.accountTradeList).toHaveBeenCalledWith({ symbol: 'BTCUSDT', startTime: 1640995200000, endTime: 1641081600000, limit: 100, }) }) }) describe('setLeverage', () => { test('should set leverage', async () => { const mockResult = { leverage: 10, maxNotionalValue: '1000000' } mockClient.restAPI.changeInitialLeverage.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.setLeverage('BTCUSDT', 10) expect(mockClient.restAPI.changeInitialLeverage).toHaveBeenCalledWith({ symbol: 'BTCUSDT', leverage: 10, }) }) }) describe('setMarginType', () => { test('should set margin type to isolated', async () => { const mockResult = { code: 200, msg: 'success' } mockClient.restAPI.changeMarginType.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.setMarginType( 'BTCUSDT', DerivativesTradingUsdsFuturesRestAPI.ChangeMarginTypeMarginTypeEnum.ISOLATED, ) expect(mockClient.restAPI.changeMarginType).toHaveBeenCalledWith({ symbol: 'BTCUSDT', marginType: 'ISOLATED', }) }) test('should set margin type to crossed', async () => { const mockResult = { code: 200, msg: 'success' } mockClient.restAPI.changeMarginType.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.setMarginType( 'BTCUSDT', DerivativesTradingUsdsFuturesRestAPI.ChangeMarginTypeMarginTypeEnum.CROSSED, ) expect(mockClient.restAPI.changeMarginType).toHaveBeenCalledWith({ symbol: 'BTCUSDT', marginType: 'CROSSED', }) }) }) describe('getAllPositions', () => { test('should get all positions', async () => { const mockPositions = [ { symbol: 'BTCUSDT', positionAmt: '0.1', unrealizedPnl: '10.5' }, { symbol: 'ETHUSDT', positionAmt: '0.0', unrealizedPnl: '0.0' }, { symbol: 'ADAUSDT', positionAmt: '-100.0', unrealizedPnl: '-5.2' }, ] mockClient.restAPI.accountInformationV3.mockResolvedValue({ data: jest.fn().mockResolvedValue({ positions: mockPositions }), }) const result = await connector.getAllPositions() expect(result).toEqual(mockPositions) expect(mockClient.restAPI.accountInformationV3).toHaveBeenCalled() }) }) describe('getCurrentAllOpenPosition', () => { test('should get current open orders', async () => { const mockOrders = [ { orderId: 12367, symbol: 'BTCUSDT', status: 'NEW' }, { orderId: 12368, symbol: 'ETHUSDT', status: 'NEW' }, ] mockClient.restAPI.currentAllOpenOrders.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockOrders), }) await connector.getCurrentAllOpenPosition() expect(mockClient.restAPI.currentAllOpenOrders).toHaveBeenCalled() }) test('should handle API errors gracefully', async () => { mockClient.restAPI.currentAllOpenOrders.mockRejectedValue(new Error('API Error')) await expect(connector.getCurrentAllOpenPosition()).resolves.not.toThrow() }) }) describe('openModifyPosition', () => { test('should modify existing order', async () => { const mockResult = { orderId: 12369, status: 'REPLACED' } mockClient.restAPI.modifyOrder.mockResolvedValue({ data: jest.fn().mockResolvedValue(mockResult), }) const result = await connector.openModifyPosition( 'BTCUSDT', DerivativesTradingUsdsFuturesRestAPI.ModifyOrderSideEnum.BUY, 0.002, 51000, ) expect(result).toEqual(mockResult) expect(mockClient.restAPI.modifyOrder).toHaveBeenCalledWith({ symbol: 'BTCUSDT', side: 'BUY', quantity: 0.002, price: 51000, }) }) test('should handle API errors gracefully', async () => { mockClient.restAPI.modifyOrder.mockRejectedValue(new Error('Modify failed')) const result = await connector.openModifyPosition( 'BTCUSDT', DerivativesTradingUsdsFuturesRestAPI.ModifyOrderSideEnum.BUY, 0.002, 51000, ) expect(result).toBeNull() }) }) })