"use strict"; /** * Unit tests for HedgingStrategyEngine */ Object.defineProperty(exports, "__esModule", { value: true }); const HedgingStrategyEngine_1 = require("../../src/core/HedgingStrategyEngine"); // Mock dependencies jest.mock('../../src/utils/Logger'); describe('HedgingStrategyEngine', () => { let strategyEngine; let mockConfig; beforeEach(() => { mockConfig = { maxConcurrentStrategies: 5, strategyTimeout: 60000, orderGenerationInterval: 5000 }; strategyEngine = new HedgingStrategyEngine_1.HedgingStrategyEngine(mockConfig); }); afterEach(() => { jest.clearAllMocks(); }); describe('Initialization', () => { it('should initialize with correct configuration', async () => { await strategyEngine.initialize(); const status = strategyEngine.getStatus(); expect(status.isRunning).toBe(false); expect(status.totalStrategies).toBe(0); }); it('should throw error if already initialized', async () => { await strategyEngine.initialize(); await expect(strategyEngine.initialize()).rejects.toThrow('HedgingStrategyEngine is already initialized'); }); }); describe('Strategy Management', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should add a hedging strategy', () => { const strategy = { id: 'test-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; strategyEngine.addStrategy(strategy); const retrievedStrategy = strategyEngine.getStrategy('test-strategy'); expect(retrievedStrategy).toBeDefined(); expect(retrievedStrategy?.id).toBe('test-strategy'); }); it('should remove a hedging strategy', () => { const strategy = { id: 'remove-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; strategyEngine.addStrategy(strategy); strategyEngine.removeStrategy('remove-strategy'); const retrievedStrategy = strategyEngine.getStrategy('remove-strategy'); expect(retrievedStrategy).toBeNull(); }); it('should update a hedging strategy', () => { const strategy = { id: 'update-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; strategyEngine.addStrategy(strategy); const updatedStrategy = { ...strategy, symbol: 'BTC/USD', updatedAt: new Date() }; strategyEngine.updateStrategy(updatedStrategy); const retrievedStrategy = strategyEngine.getStrategy('update-strategy'); expect(retrievedStrategy?.symbol).toBe('BTC/USD'); }); it('should get all strategies', () => { const strategy1 = { id: 'strategy-1', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const strategy2 = { id: 'strategy-2', symbol: 'BTC/USD', volumeDistribution: 'weighted', priceRange: { min: 0.002, max: 0.02 }, timing: { minInterval: 60, maxInterval: 180, orderSize: { min: 200, max: 1000 } }, riskLimits: { maxPositionSize: 0.15, stopLossThreshold: 0.08, maxSlippage: 0.03 }, orderTypes: { primary: 'market', fallback: 'limit' }, createdAt: new Date(), updatedAt: new Date() }; strategyEngine.addStrategy(strategy1); strategyEngine.addStrategy(strategy2); const allStrategies = strategyEngine.getAllStrategies(); expect(allStrategies).toHaveLength(2); expect(allStrategies.map(s => s.id)).toContain('strategy-1'); expect(allStrategies.map(s => s.id)).toContain('strategy-2'); }); }); describe('Order Generation', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should generate hedging orders', () => { const strategy = { id: 'order-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice); expect(orders).toBeDefined(); expect(Array.isArray(orders)).toBe(true); expect(orders.length).toBeGreaterThan(0); }); it('should generate orders with correct structure', () => { const strategy = { id: 'structure-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice); if (orders.length > 0) { const order = orders[0]; expect(order.id).toBeDefined(); expect(order.sessionId).toBeDefined(); expect(order.strategyId).toBe('structure-strategy'); expect(order.orderPair).toBeDefined(); expect(order.orderPair.buyOrder).toBeDefined(); expect(order.orderPair.sellOrder).toBeDefined(); expect(order.status).toBe('pending'); } }); it('should respect volume distribution', () => { const strategy = { id: 'volume-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2', 'account3']; const currentPrice = 2500; const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice); // With equal distribution, each account should get similar order sizes if (orders.length > 0) { const order = orders[0]; const buySize = order.orderPair.buyOrder.size; const sellSize = order.orderPair.sellOrder.size; expect(buySize).toBe(sellSize); // Buy and sell should be equal for neutrality } }); it('should respect price range', () => { const strategy = { id: 'price-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice); if (orders.length > 0) { const order = orders[0]; const buyPrice = order.orderPair.buyOrder.price; const sellPrice = order.orderPair.sellOrder.price; if (buyPrice && sellPrice) { const minPrice = currentPrice * (1 - strategy.priceRange.max); const maxPrice = currentPrice * (1 + strategy.priceRange.max); expect(buyPrice).toBeGreaterThanOrEqual(minPrice); expect(buyPrice).toBeLessThanOrEqual(maxPrice); expect(sellPrice).toBeGreaterThanOrEqual(minPrice); expect(sellPrice).toBeLessThanOrEqual(maxPrice); } } }); it('should respect order size limits', () => { const strategy = { id: 'size-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice); if (orders.length > 0) { const order = orders[0]; const buySize = order.orderPair.buyOrder.size; const sellSize = order.orderPair.sellOrder.size; expect(buySize).toBeGreaterThanOrEqual(strategy.timing.orderSize.min); expect(buySize).toBeLessThanOrEqual(strategy.timing.orderSize.max); expect(sellSize).toBeGreaterThanOrEqual(strategy.timing.orderSize.min); expect(sellSize).toBeLessThanOrEqual(strategy.timing.orderSize.max); } }); }); describe('Strategy Execution', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should execute a hedging strategy', async () => { const strategy = { id: 'execute-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const result = await strategyEngine.executeStrategy(strategy, accounts, currentPrice); expect(result).toBeDefined(); expect(result.success).toBeDefined(); expect(typeof result.success).toBe('boolean'); }); it('should handle strategy execution failure', async () => { const invalidStrategy = { id: 'invalid-strategy', symbol: '', // Invalid empty symbol volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const result = await strategyEngine.executeStrategy(invalidStrategy, accounts, currentPrice); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('should execute strategy with retry mechanism', async () => { const strategy = { id: 'retry-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; // Mock retry scenario jest.spyOn(strategyEngine, 'executeStrategyWithRetry') .mockResolvedValueOnce({ success: false, error: 'Network error' }) .mockResolvedValueOnce({ success: true, orders: [] }); const result = await strategyEngine.executeStrategy(strategy, accounts, currentPrice); expect(result).toBeDefined(); }); }); describe('Strategy Validation', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should validate hedging strategy', () => { const validStrategy = { id: 'valid-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const isValid = strategyEngine.validateStrategy(validStrategy); expect(isValid).toBe(true); }); it('should reject invalid hedging strategy', () => { const invalidStrategy = { id: 'invalid-strategy', symbol: '', // Invalid empty symbol volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const isValid = strategyEngine.validateStrategy(invalidStrategy); expect(isValid).toBe(false); }); it('should validate price range', () => { const strategy = { id: 'price-range-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const isValid = strategyEngine.validatePriceRange(strategy.priceRange); expect(isValid).toBe(true); }); it('should reject invalid price range', () => { const invalidPriceRange = { min: 0.01, max: 0.001 }; // min > max const isValid = strategyEngine.validatePriceRange(invalidPriceRange); expect(isValid).toBe(false); }); it('should validate timing configuration', () => { const strategy = { id: 'timing-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const isValid = strategyEngine.validateTimingConfig(strategy.timing); expect(isValid).toBe(true); }); it('should reject invalid timing configuration', () => { const invalidTiming = { minInterval: 120, maxInterval: 30, orderSize: { min: 500, max: 100 } }; // min > max const isValid = strategyEngine.validateTimingConfig(invalidTiming); expect(isValid).toBe(false); }); }); describe('Market Adaptation', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should adapt strategy to market conditions', () => { const strategy = { id: 'adapt-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const marketConditions = { volatility: 0.08, volume: 1000000, spread: 0.001 }; const adaptedStrategy = strategyEngine.adaptStrategyToMarket(strategy, marketConditions); expect(adaptedStrategy).toBeDefined(); expect(adaptedStrategy.id).toBe(strategy.id); }); it('should adjust order size based on volatility', () => { const strategy = { id: 'volatility-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const highVolatilityConditions = { volatility: 0.15, volume: 1000000, spread: 0.001 }; const adaptedStrategy = strategyEngine.adaptStrategyToMarket(strategy, highVolatilityConditions); // High volatility should result in smaller order sizes expect(adaptedStrategy.timing.orderSize.max).toBeLessThanOrEqual(strategy.timing.orderSize.max); }); it('should adjust price range based on spread', () => { const strategy = { id: 'spread-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const wideSpreadConditions = { volatility: 0.05, volume: 1000000, spread: 0.005 }; const adaptedStrategy = strategyEngine.adaptStrategyToMarket(strategy, wideSpreadConditions); // Wide spread should result in wider price range expect(adaptedStrategy.priceRange.max).toBeGreaterThanOrEqual(strategy.priceRange.max); }); }); describe('Statistics and Monitoring', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should return correct status', () => { const status = strategyEngine.getStatus(); expect(status).toBeDefined(); expect(typeof status.isRunning).toBe('boolean'); expect(typeof status.totalStrategies).toBe('number'); expect(typeof status.activeStrategies).toBe('number'); }); it('should return correct statistics', () => { const stats = strategyEngine.getStatistics(); expect(stats).toBeDefined(); expect(typeof stats.totalStrategies).toBe('number'); expect(typeof stats.totalOrders).toBe('number'); expect(typeof stats.successfulOrders).toBe('number'); expect(typeof stats.failedOrders).toBe('number'); expect(typeof stats.successRate).toBe('number'); expect(typeof stats.averageExecutionTime).toBe('number'); }); it('should calculate success rate correctly', async () => { const strategy1 = { id: 'success-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const strategy2 = { id: 'fail-strategy', symbol: '', // Invalid symbol volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; await strategyEngine.executeStrategy(strategy1, accounts, currentPrice); await strategyEngine.executeStrategy(strategy2, accounts, currentPrice); const stats = strategyEngine.getStatistics(); expect(stats.successRate).toBe(0.5); // 1 out of 2 successful }); }); describe('Event Emission', () => { beforeEach(async () => { await strategyEngine.initialize(); }); it('should emit strategy added event', () => { const strategy = { id: 'event-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const eventSpy = jest.fn(); strategyEngine.on('strategyAdded', eventSpy); strategyEngine.addStrategy(strategy); expect(eventSpy).toHaveBeenCalledWith(strategy); }); it('should emit strategy executed event', async () => { const strategy = { id: 'execute-event-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = ['account1', 'account2']; const currentPrice = 2500; const eventSpy = jest.fn(); strategyEngine.on('strategyExecuted', eventSpy); await strategyEngine.executeStrategy(strategy, accounts, currentPrice); expect(eventSpy).toHaveBeenCalled(); }); }); describe('Error Handling', () => { it('should handle initialization errors gracefully', async () => { const invalidConfig = { maxConcurrentStrategies: -1, // Invalid negative value strategyTimeout: 60000, orderGenerationInterval: 5000 }; const invalidEngine = new HedgingStrategyEngine_1.HedgingStrategyEngine(invalidConfig); await expect(invalidEngine.initialize()).rejects.toThrow(); }); it('should handle strategy execution errors gracefully', async () => { await strategyEngine.initialize(); const invalidStrategy = { id: 'error-strategy', symbol: 'ETH/USD', volumeDistribution: 'equal', priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit', fallback: 'market' }, createdAt: new Date(), updatedAt: new Date() }; const accounts = []; // Empty accounts array const currentPrice = 2500; const result = await strategyEngine.executeStrategy(invalidStrategy, accounts, currentPrice); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); describe('Lifecycle Management', () => { it('should start and stop the engine', async () => { await strategyEngine.initialize(); await strategyEngine.start(); const status = strategyEngine.getStatus(); expect(status.isRunning).toBe(true); await strategyEngine.stop(); const stoppedStatus = strategyEngine.getStatus(); expect(stoppedStatus.isRunning).toBe(false); }); it('should throw error when starting already running engine', async () => { await strategyEngine.initialize(); await strategyEngine.start(); await expect(strategyEngine.start()).rejects.toThrow(); }); it('should throw error when stopping non-running engine', async () => { await strategyEngine.initialize(); await expect(strategyEngine.stop()).rejects.toThrow(); }); }); }); //# sourceMappingURL=test_hedging_strategy_engine.js.map