/** * Unit tests for HedgingManager */ import { HedgingManager } from '../../src/core/HedgingManager'; import { HedgingSessionRequest, HedgingSession } from '../../src/types/hedging'; import { Logger } from '../../src/utils/Logger'; // Mock dependencies jest.mock('../../src/utils/Logger'); jest.mock('../../src/core/RiskManager'); jest.mock('../../src/core/OrderCoordinator'); jest.mock('../../src/core/HedgingStrategyEngine'); jest.mock('../../src/core/HedgingConfigManager'); describe('HedgingManager', () => { let hedgingManager: HedgingManager; let mockConfig: any; beforeEach(() => { mockConfig = { maxConcurrentSessions: 5, sessionTimeout: 60000, riskCheckInterval: 1000 }; hedgingManager = new HedgingManager(mockConfig); }); afterEach(() => { jest.clearAllMocks(); }); describe('Initialization', () => { it('should initialize with correct configuration', async () => { await hedgingManager.initialize(); const status = hedgingManager.getStatus(); expect(status.isRunning).toBe(false); expect(status.totalSessions).toBe(0); }); it('should throw error if already initialized', async () => { await hedgingManager.initialize(); await expect(hedgingManager.initialize()).rejects.toThrow('HedgingManager is already initialized'); }); }); describe('Session Management', () => { beforeEach(async () => { await hedgingManager.initialize(); }); it('should create a new hedging session', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); expect(session).toBeDefined(); expect(session.id).toBeDefined(); expect(session.status).toBe('created'); expect(session.strategy.symbol).toBe('ETH/USD'); expect(session.accounts).toEqual(['account1', 'account2']); }); it('should throw error when creating session with invalid request', async () => { const invalidRequest = { 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' } }, accounts: [], duration: 300000 } as HedgingSessionRequest; await expect(hedgingManager.createSession(invalidRequest)).rejects.toThrow(); }); it('should start a hedging session', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); await hedgingManager.startSession(session.id); const updatedSession = hedgingManager.getSession(session.id); expect(updatedSession?.status).toBe('running'); }); it('should pause a running session', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); await hedgingManager.startSession(session.id); await hedgingManager.pauseSession(session.id); const updatedSession = hedgingManager.getSession(session.id); expect(updatedSession?.status).toBe('paused'); }); it('should resume a paused session', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); await hedgingManager.startSession(session.id); await hedgingManager.pauseSession(session.id); await hedgingManager.resumeSession(session.id); const updatedSession = hedgingManager.getSession(session.id); expect(updatedSession?.status).toBe('running'); }); it('should stop a session', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); await hedgingManager.startSession(session.id); await hedgingManager.stopSession(session.id); const updatedSession = hedgingManager.getSession(session.id); expect(updatedSession?.status).toBe('stopped'); }); it('should throw error when operating on non-existent session', async () => { await expect(hedgingManager.startSession('non-existent')).rejects.toThrow(); await expect(hedgingManager.pauseSession('non-existent')).rejects.toThrow(); await expect(hedgingManager.resumeSession('non-existent')).rejects.toThrow(); await expect(hedgingManager.stopSession('non-existent')).rejects.toThrow(); }); it('should throw error when starting already running session', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); await hedgingManager.startSession(session.id); await expect(hedgingManager.startSession(session.id)).rejects.toThrow(); }); }); describe('Session Retrieval', () => { beforeEach(async () => { await hedgingManager.initialize(); }); it('should get all sessions', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session1 = await hedgingManager.createSession(sessionRequest); const session2 = await hedgingManager.createSession(sessionRequest); const allSessions = hedgingManager.getAllSessions(); expect(allSessions).toHaveLength(2); expect(allSessions.map(s => s.id)).toContain(session1.id); expect(allSessions.map(s => s.id)).toContain(session2.id); }); it('should get session by ID', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); const retrievedSession = hedgingManager.getSession(session.id); expect(retrievedSession).toBeDefined(); expect(retrievedSession?.id).toBe(session.id); }); it('should return null for non-existent session', () => { const session = hedgingManager.getSession('non-existent'); expect(session).toBeNull(); }); }); describe('Order Management', () => { beforeEach(async () => { await hedgingManager.initialize(); }); it('should get session orders', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); const orders = hedgingManager.getSessionOrders(session.id); expect(orders).toBeDefined(); expect(Array.isArray(orders)).toBe(true); }); it('should return empty array for non-existent session orders', () => { const orders = hedgingManager.getSessionOrders('non-existent'); expect(orders).toEqual([]); }); }); describe('Risk Management', () => { beforeEach(async () => { await hedgingManager.initialize(); }); it('should get session risk status', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); const riskStatus = hedgingManager.getSessionRiskStatus(session.id); expect(riskStatus).toBeDefined(); expect(riskStatus.overallRisk).toBeDefined(); expect(riskStatus.activeBreaches).toBeDefined(); expect(riskStatus.acknowledgedBreaches).toBeDefined(); }); it('should get session risk breaches', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); const breaches = hedgingManager.getSessionRiskBreaches(session.id); expect(breaches).toBeDefined(); expect(Array.isArray(breaches)).toBe(true); }); it('should acknowledge risk breach', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); const breaches = hedgingManager.getSessionRiskBreaches(session.id); if (breaches.length > 0) { const breach = breaches[0]; await hedgingManager.acknowledgeRiskBreach(session.id, breach.id); const updatedBreaches = hedgingManager.getSessionRiskBreaches(session.id); const updatedBreach = updatedBreaches.find(b => b.id === breach.id); expect(updatedBreach?.acknowledged).toBe(true); } }); it('should throw error when acknowledging non-existent breach', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); await expect(hedgingManager.acknowledgeRiskBreach(session.id, 'non-existent')).rejects.toThrow(); }); }); describe('Lifecycle Management', () => { it('should start and stop the manager', async () => { await hedgingManager.initialize(); await hedgingManager.start(); const status = hedgingManager.getStatus(); expect(status.isRunning).toBe(true); await hedgingManager.stop(); const stoppedStatus = hedgingManager.getStatus(); expect(stoppedStatus.isRunning).toBe(false); }); it('should throw error when starting already running manager', async () => { await hedgingManager.initialize(); await hedgingManager.start(); await expect(hedgingManager.start()).rejects.toThrow(); }); it('should throw error when stopping non-running manager', async () => { await hedgingManager.initialize(); await expect(hedgingManager.stop()).rejects.toThrow(); }); }); describe('Status and Statistics', () => { beforeEach(async () => { await hedgingManager.initialize(); }); it('should return correct status', () => { const status = hedgingManager.getStatus(); expect(status).toBeDefined(); expect(typeof status.isRunning).toBe('boolean'); expect(typeof status.totalSessions).toBe('number'); expect(typeof status.activeSessions).toBe('number'); expect(typeof status.pausedSessions).toBe('number'); expect(typeof status.stoppedSessions).toBe('number'); }); it('should return correct statistics', () => { const stats = hedgingManager.getStatistics(); expect(stats).toBeDefined(); expect(typeof stats.totalSessions).toBe('number'); expect(typeof stats.totalOrders).toBe('number'); expect(typeof stats.totalVolume).toBe('number'); expect(typeof stats.averageExecutionTime).toBe('number'); expect(typeof stats.successRate).toBe('number'); }); }); describe('Error Handling', () => { it('should handle initialization errors gracefully', async () => { const invalidConfig = { maxConcurrentSessions: -1, // Invalid negative value sessionTimeout: 60000, riskCheckInterval: 1000 }; const invalidManager = new HedgingManager(invalidConfig); await expect(invalidManager.initialize()).rejects.toThrow(); }); it('should handle session creation errors gracefully', async () => { await hedgingManager.initialize(); const invalidRequest = { strategy: null, // Invalid null strategy accounts: ['account1'], duration: 300000 } as any; await expect(hedgingManager.createSession(invalidRequest)).rejects.toThrow(); }); }); describe('Event Emission', () => { beforeEach(async () => { await hedgingManager.initialize(); }); it('should emit session created event', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const eventSpy = jest.fn(); hedgingManager.on('sessionCreated', eventSpy); await hedgingManager.createSession(sessionRequest); expect(eventSpy).toHaveBeenCalled(); }); it('should emit session started event', async () => { const sessionRequest: HedgingSessionRequest = { 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' } }, accounts: ['account1', 'account2'], duration: 300000 }; const session = await hedgingManager.createSession(sessionRequest); const eventSpy = jest.fn(); hedgingManager.on('sessionStarted', eventSpy); await hedgingManager.startSession(session.id); expect(eventSpy).toHaveBeenCalled(); }); }); });