123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'
- import { DeltaNeutralController } from '../../src/controllers/DeltaNeutralController.js'
- import { AccountManager } from '../../src/modules/account/AccountManager.js'
- import { HedgeExecutionService } from '../../src/services/HedgeExecutionService.js'
- import { RiskMonitoringService } from '../../src/services/RiskMonitoringService.js'
- /**
- * DeltaNeutralController 单元测试
- * 测试Delta中性控制器的核心功能
- */
- describe('DeltaNeutralController Unit Tests', () => {
- let controller: DeltaNeutralController
- let mockAccountManager: jest.Mocked<AccountManager>
- let mockHedgeService: jest.Mocked<HedgeExecutionService>
- let mockRiskService: jest.Mocked<RiskMonitoringService>
- beforeEach(() => {
- // 创建模拟依赖
- mockAccountManager = {
- getAccounts: jest.fn().mockReturnValue(new Map([
- ['account-1', { netPosition: 0.5, unrealizedPnl: 100 }],
- ['account-2', { netPosition: -0.3, unrealizedPnl: -50 }]
- ])),
- getAccountState: jest.fn(),
- updateAccountState: jest.fn(),
- } as any
- mockHedgeService = {
- executeHedge: jest.fn().mockResolvedValue({
- success: true,
- executedOrders: [
- { accountId: 'account-1', symbol: 'BTC-USD', amount: 0.2, side: 'sell' }
- ],
- totalCost: 1000,
- executionTime: 150,
- netDeltaChange: -0.2
- }),
- getStatus: jest.fn().mockReturnValue('running'),
- } as any
- mockRiskService = {
- getRiskMetrics: jest.fn().mockResolvedValue({
- accountId: 'test-account',
- deltaDeviation: 0.5,
- utilizationRate: 0.7,
- leverageRatio: 2,
- unrealizedPnl: 100,
- maxDrawdown: 200,
- timestamp: Date.now()
- }),
- updateAccountRisk: jest.fn(),
- getActiveAlerts: jest.fn().mockReturnValue([]),
- getStatus: jest.fn().mockReturnValue('running'),
- } as any
- controller = new DeltaNeutralController(
- mockAccountManager,
- mockHedgeService,
- mockRiskService
- )
- })
- afterEach(async () => {
- await controller.stop()
- })
- describe('Service Lifecycle', () => {
- it('should initialize with stopped status', () => {
- expect(controller.getStatus()).toBe('stopped')
- })
- it('should start successfully', async () => {
- await controller.start()
- expect(controller.getStatus()).toBe('running')
- })
- it('should stop successfully', async () => {
- await controller.start()
- await controller.stop()
- expect(controller.getStatus()).toBe('stopped')
- })
- it('should handle multiple start calls gracefully', async () => {
- await controller.start()
- await controller.start() // 第二次调用应该被忽略
- expect(controller.getStatus()).toBe('running')
- })
- })
- describe('Delta Control Execution', () => {
- beforeEach(async () => {
- await controller.start()
- })
- it('should execute delta control with valid request', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(true)
- expect(response.accountsProcessed).toEqual(['account-1', 'account-2'])
- expect(response.totalAdjustments).toBeGreaterThan(0)
- expect(response.finalDelta).toBeDefined()
- expect(mockHedgeService.executeHedge).toHaveBeenCalled()
- })
- it('should calculate net delta correctly', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- // 净Delta = 0.5 + (-0.3) = 0.2
- expect(response.initialDelta).toBe(0.2)
- expect(response.deltaDeviation).toBe(0.2) // 偏离目标0.0
- })
- it('should skip adjustment when delta is within tolerance', async () => {
- // 设置小的净仓位偏差
- mockAccountManager.getAccounts.mockReturnValue(new Map([
- ['account-1', { netPosition: 0.05, unrealizedPnl: 10 }],
- ['account-2', { netPosition: -0.03, unrealizedPnl: -5 }]
- ]))
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1, // 10% 容差
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(true)
- expect(response.adjustmentNeeded).toBe(false)
- expect(response.reason).toContain('在容差范围内')
- expect(mockHedgeService.executeHedge).not.toHaveBeenCalled()
- })
- it('should handle hedge execution failure gracefully', async () => {
- mockHedgeService.executeHedge.mockResolvedValue({
- success: false,
- error: 'Insufficient balance',
- executedOrders: [],
- totalCost: 0,
- executionTime: 0,
- netDeltaChange: 0
- })
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(false)
- expect(response.error).toContain('对冲执行失败')
- })
- })
- describe('Utilization Rebalancing', () => {
- beforeEach(async () => {
- await controller.start()
- })
- it('should execute utilization rebalancing successfully', async () => {
- const request = {
- targetUtilization: 0.75, // 75% 目标利用率
- accounts: ['account-1', 'account-2'],
- symbol: 'BTC-USD'
- }
- const response = await controller.executeUtilizationRebalance(request)
- expect(response.success).toBe(true)
- expect(response.accountsProcessed).toEqual(['account-1', 'account-2'])
- expect(response.rebalanceActions).toBeDefined()
- })
- it('should calculate utilization rates correctly', async () => {
- // 模拟风险服务返回特定利用率
- mockRiskService.getRiskMetrics
- .mockResolvedValueOnce({
- accountId: 'account-1',
- deltaDeviation: 0.5,
- utilizationRate: 0.85, // 85% 高利用率
- leverageRatio: 2,
- unrealizedPnl: 100,
- maxDrawdown: 200,
- timestamp: Date.now()
- })
- .mockResolvedValueOnce({
- accountId: 'account-2',
- deltaDeviation: 0.3,
- utilizationRate: 0.65, // 65% 低利用率
- leverageRatio: 1,
- unrealizedPnl: -50,
- maxDrawdown: 100,
- timestamp: Date.now()
- })
- const request = {
- targetUtilization: 0.75,
- accounts: ['account-1', 'account-2'],
- symbol: 'BTC-USD'
- }
- const response = await controller.executeUtilizationRebalance(request)
- expect(response.utilizationMetrics).toHaveLength(2)
- expect(response.utilizationMetrics[0].currentUtilization).toBe(0.85)
- expect(response.utilizationMetrics[1].currentUtilization).toBe(0.65)
- expect(response.averageUtilization).toBe(0.75) // (0.85 + 0.65) / 2
- })
- it('should identify accounts needing rebalancing', async () => {
- mockRiskService.getRiskMetrics
- .mockResolvedValueOnce({
- accountId: 'account-1',
- deltaDeviation: 0.5,
- utilizationRate: 0.95, // 需要降低
- leverageRatio: 3,
- unrealizedPnl: 200,
- maxDrawdown: 300,
- timestamp: Date.now()
- })
- .mockResolvedValueOnce({
- accountId: 'account-2',
- deltaDeviation: 0.2,
- utilizationRate: 0.45, // 需要提高
- leverageRatio: 1,
- unrealizedPnl: 50,
- maxDrawdown: 50,
- timestamp: Date.now()
- })
- const request = {
- targetUtilization: 0.75,
- accounts: ['account-1', 'account-2'],
- symbol: 'BTC-USD',
- tolerancePercent: 0.1 // 10% 容差
- }
- const response = await controller.executeUtilizationRebalance(request)
- expect(response.rebalanceActions).toHaveLength(2)
- const account1Action = response.rebalanceActions.find(a => a.accountId === 'account-1')
- const account2Action = response.rebalanceActions.find(a => a.accountId === 'account-2')
- expect(account1Action?.action).toBe('reduce') // 降低利用率
- expect(account2Action?.action).toBe('increase') // 提高利用率
- })
- it('should skip rebalancing when utilization is optimal', async () => {
- mockRiskService.getRiskMetrics.mockResolvedValue({
- accountId: 'test-account',
- deltaDeviation: 0.1,
- utilizationRate: 0.75, // 正好是目标利用率
- leverageRatio: 2,
- unrealizedPnl: 100,
- maxDrawdown: 150,
- timestamp: Date.now()
- })
- const request = {
- targetUtilization: 0.75,
- accounts: ['account-1', 'account-2'],
- symbol: 'BTC-USD',
- tolerancePercent: 0.05 // 5% 容差
- }
- const response = await controller.executeUtilizationRebalance(request)
- expect(response.success).toBe(true)
- expect(response.rebalanceNeeded).toBe(false)
- expect(response.reason).toContain('在目标范围内')
- })
- })
- describe('Risk Integration', () => {
- beforeEach(async () => {
- await controller.start()
- })
- it('should check risk metrics before executing operations', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- await controller.executeDeltaControl(request)
- expect(mockRiskService.getRiskMetrics).toHaveBeenCalledWith('account-1')
- })
- it('should abort operations when risk alerts are present', async () => {
- // 模拟活跃的风险警报
- mockRiskService.getActiveAlerts.mockReturnValue([
- {
- id: 'alert-1',
- type: 'delta-alert',
- severity: 'CRITICAL',
- accountId: 'account-1',
- message: '严重Delta偏差',
- timestamp: Date.now(),
- data: { deltaDeviation: 5.0 }
- }
- ])
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(false)
- expect(response.error).toContain('存在CRITICAL级别风险警报')
- expect(mockHedgeService.executeHedge).not.toHaveBeenCalled()
- })
- it('should update risk monitoring after successful operations', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- await controller.executeDeltaControl(request)
- expect(mockRiskService.updateAccountRisk).toHaveBeenCalled()
- })
- })
- describe('Error Handling and Edge Cases', () => {
- beforeEach(async () => {
- await controller.start()
- })
- it('should handle empty account list', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: [],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(false)
- expect(response.error).toContain('账户列表为空')
- })
- it('should handle invalid target delta values', async () => {
- const request = {
- targetDelta: NaN,
- accounts: ['account-1'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(false)
- expect(response.error).toContain('无效的目标Delta值')
- })
- it('should handle account manager failures', async () => {
- mockAccountManager.getAccounts.mockImplementation(() => {
- throw new Error('AccountManager connection failed')
- })
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.success).toBe(false)
- expect(response.error).toContain('获取账户状态失败')
- })
- it('should handle risk service unavailability', async () => {
- mockRiskService.getRiskMetrics.mockRejectedValue(new Error('Risk service unavailable'))
- const request = {
- targetUtilization: 0.75,
- accounts: ['account-1'],
- symbol: 'BTC-USD'
- }
- const response = await controller.executeUtilizationRebalance(request)
- expect(response.success).toBe(false)
- expect(response.error).toContain('风险评估失败')
- })
- })
- describe('Performance and Monitoring', () => {
- beforeEach(async () => {
- await controller.start()
- })
- it('should track operation execution time', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.executionTime).toBeGreaterThan(0)
- expect(response.executionTime).toBeLessThan(10000) // 应该在10秒内完成
- })
- it('should provide detailed operation metrics', async () => {
- const request = {
- targetDelta: 0.0,
- accounts: ['account-1', 'account-2'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const response = await controller.executeDeltaControl(request)
- expect(response.operationId).toBeDefined()
- expect(response.timestamp).toBeDefined()
- expect(response.accountsProcessed).toBeDefined()
- expect(response.totalAdjustments).toBeDefined()
- })
- it('should handle concurrent operation requests', async () => {
- const request1 = {
- targetDelta: 0.0,
- accounts: ['account-1'],
- maxDeviation: 0.1,
- symbol: 'BTC-USD'
- }
- const request2 = {
- targetUtilization: 0.75,
- accounts: ['account-2'],
- symbol: 'BTC-USD'
- }
- const [response1, response2] = await Promise.all([
- controller.executeDeltaControl(request1),
- controller.executeUtilizationRebalance(request2)
- ])
- expect(response1.success).toBe(true)
- expect(response2.success).toBe(true)
- expect(response1.operationId).not.toBe(response2.operationId)
- })
- })
- })
|