test_DeltaNeutralController.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'
  2. import { DeltaNeutralController } from '../../src/controllers/DeltaNeutralController.js'
  3. import { AccountManager } from '../../src/modules/account/AccountManager.js'
  4. import { HedgeExecutionService } from '../../src/services/HedgeExecutionService.js'
  5. import { RiskMonitoringService } from '../../src/services/RiskMonitoringService.js'
  6. /**
  7. * DeltaNeutralController 单元测试
  8. * 测试Delta中性控制器的核心功能
  9. */
  10. describe('DeltaNeutralController Unit Tests', () => {
  11. let controller: DeltaNeutralController
  12. let mockAccountManager: jest.Mocked<AccountManager>
  13. let mockHedgeService: jest.Mocked<HedgeExecutionService>
  14. let mockRiskService: jest.Mocked<RiskMonitoringService>
  15. beforeEach(() => {
  16. // 创建模拟依赖
  17. mockAccountManager = {
  18. getAccounts: jest.fn().mockReturnValue(new Map([
  19. ['account-1', { netPosition: 0.5, unrealizedPnl: 100 }],
  20. ['account-2', { netPosition: -0.3, unrealizedPnl: -50 }]
  21. ])),
  22. getAccountState: jest.fn(),
  23. updateAccountState: jest.fn(),
  24. } as any
  25. mockHedgeService = {
  26. executeHedge: jest.fn().mockResolvedValue({
  27. success: true,
  28. executedOrders: [
  29. { accountId: 'account-1', symbol: 'BTC-USD', amount: 0.2, side: 'sell' }
  30. ],
  31. totalCost: 1000,
  32. executionTime: 150,
  33. netDeltaChange: -0.2
  34. }),
  35. getStatus: jest.fn().mockReturnValue('running'),
  36. } as any
  37. mockRiskService = {
  38. getRiskMetrics: jest.fn().mockResolvedValue({
  39. accountId: 'test-account',
  40. deltaDeviation: 0.5,
  41. utilizationRate: 0.7,
  42. leverageRatio: 2,
  43. unrealizedPnl: 100,
  44. maxDrawdown: 200,
  45. timestamp: Date.now()
  46. }),
  47. updateAccountRisk: jest.fn(),
  48. getActiveAlerts: jest.fn().mockReturnValue([]),
  49. getStatus: jest.fn().mockReturnValue('running'),
  50. } as any
  51. controller = new DeltaNeutralController(
  52. mockAccountManager,
  53. mockHedgeService,
  54. mockRiskService
  55. )
  56. })
  57. afterEach(async () => {
  58. await controller.stop()
  59. })
  60. describe('Service Lifecycle', () => {
  61. it('should initialize with stopped status', () => {
  62. expect(controller.getStatus()).toBe('stopped')
  63. })
  64. it('should start successfully', async () => {
  65. await controller.start()
  66. expect(controller.getStatus()).toBe('running')
  67. })
  68. it('should stop successfully', async () => {
  69. await controller.start()
  70. await controller.stop()
  71. expect(controller.getStatus()).toBe('stopped')
  72. })
  73. it('should handle multiple start calls gracefully', async () => {
  74. await controller.start()
  75. await controller.start() // 第二次调用应该被忽略
  76. expect(controller.getStatus()).toBe('running')
  77. })
  78. })
  79. describe('Delta Control Execution', () => {
  80. beforeEach(async () => {
  81. await controller.start()
  82. })
  83. it('should execute delta control with valid request', async () => {
  84. const request = {
  85. targetDelta: 0.0,
  86. accounts: ['account-1', 'account-2'],
  87. maxDeviation: 0.1,
  88. symbol: 'BTC-USD'
  89. }
  90. const response = await controller.executeDeltaControl(request)
  91. expect(response.success).toBe(true)
  92. expect(response.accountsProcessed).toEqual(['account-1', 'account-2'])
  93. expect(response.totalAdjustments).toBeGreaterThan(0)
  94. expect(response.finalDelta).toBeDefined()
  95. expect(mockHedgeService.executeHedge).toHaveBeenCalled()
  96. })
  97. it('should calculate net delta correctly', async () => {
  98. const request = {
  99. targetDelta: 0.0,
  100. accounts: ['account-1', 'account-2'],
  101. maxDeviation: 0.1,
  102. symbol: 'BTC-USD'
  103. }
  104. const response = await controller.executeDeltaControl(request)
  105. // 净Delta = 0.5 + (-0.3) = 0.2
  106. expect(response.initialDelta).toBe(0.2)
  107. expect(response.deltaDeviation).toBe(0.2) // 偏离目标0.0
  108. })
  109. it('should skip adjustment when delta is within tolerance', async () => {
  110. // 设置小的净仓位偏差
  111. mockAccountManager.getAccounts.mockReturnValue(new Map([
  112. ['account-1', { netPosition: 0.05, unrealizedPnl: 10 }],
  113. ['account-2', { netPosition: -0.03, unrealizedPnl: -5 }]
  114. ]))
  115. const request = {
  116. targetDelta: 0.0,
  117. accounts: ['account-1', 'account-2'],
  118. maxDeviation: 0.1, // 10% 容差
  119. symbol: 'BTC-USD'
  120. }
  121. const response = await controller.executeDeltaControl(request)
  122. expect(response.success).toBe(true)
  123. expect(response.adjustmentNeeded).toBe(false)
  124. expect(response.reason).toContain('在容差范围内')
  125. expect(mockHedgeService.executeHedge).not.toHaveBeenCalled()
  126. })
  127. it('should handle hedge execution failure gracefully', async () => {
  128. mockHedgeService.executeHedge.mockResolvedValue({
  129. success: false,
  130. error: 'Insufficient balance',
  131. executedOrders: [],
  132. totalCost: 0,
  133. executionTime: 0,
  134. netDeltaChange: 0
  135. })
  136. const request = {
  137. targetDelta: 0.0,
  138. accounts: ['account-1', 'account-2'],
  139. maxDeviation: 0.1,
  140. symbol: 'BTC-USD'
  141. }
  142. const response = await controller.executeDeltaControl(request)
  143. expect(response.success).toBe(false)
  144. expect(response.error).toContain('对冲执行失败')
  145. })
  146. })
  147. describe('Utilization Rebalancing', () => {
  148. beforeEach(async () => {
  149. await controller.start()
  150. })
  151. it('should execute utilization rebalancing successfully', async () => {
  152. const request = {
  153. targetUtilization: 0.75, // 75% 目标利用率
  154. accounts: ['account-1', 'account-2'],
  155. symbol: 'BTC-USD'
  156. }
  157. const response = await controller.executeUtilizationRebalance(request)
  158. expect(response.success).toBe(true)
  159. expect(response.accountsProcessed).toEqual(['account-1', 'account-2'])
  160. expect(response.rebalanceActions).toBeDefined()
  161. })
  162. it('should calculate utilization rates correctly', async () => {
  163. // 模拟风险服务返回特定利用率
  164. mockRiskService.getRiskMetrics
  165. .mockResolvedValueOnce({
  166. accountId: 'account-1',
  167. deltaDeviation: 0.5,
  168. utilizationRate: 0.85, // 85% 高利用率
  169. leverageRatio: 2,
  170. unrealizedPnl: 100,
  171. maxDrawdown: 200,
  172. timestamp: Date.now()
  173. })
  174. .mockResolvedValueOnce({
  175. accountId: 'account-2',
  176. deltaDeviation: 0.3,
  177. utilizationRate: 0.65, // 65% 低利用率
  178. leverageRatio: 1,
  179. unrealizedPnl: -50,
  180. maxDrawdown: 100,
  181. timestamp: Date.now()
  182. })
  183. const request = {
  184. targetUtilization: 0.75,
  185. accounts: ['account-1', 'account-2'],
  186. symbol: 'BTC-USD'
  187. }
  188. const response = await controller.executeUtilizationRebalance(request)
  189. expect(response.utilizationMetrics).toHaveLength(2)
  190. expect(response.utilizationMetrics[0].currentUtilization).toBe(0.85)
  191. expect(response.utilizationMetrics[1].currentUtilization).toBe(0.65)
  192. expect(response.averageUtilization).toBe(0.75) // (0.85 + 0.65) / 2
  193. })
  194. it('should identify accounts needing rebalancing', async () => {
  195. mockRiskService.getRiskMetrics
  196. .mockResolvedValueOnce({
  197. accountId: 'account-1',
  198. deltaDeviation: 0.5,
  199. utilizationRate: 0.95, // 需要降低
  200. leverageRatio: 3,
  201. unrealizedPnl: 200,
  202. maxDrawdown: 300,
  203. timestamp: Date.now()
  204. })
  205. .mockResolvedValueOnce({
  206. accountId: 'account-2',
  207. deltaDeviation: 0.2,
  208. utilizationRate: 0.45, // 需要提高
  209. leverageRatio: 1,
  210. unrealizedPnl: 50,
  211. maxDrawdown: 50,
  212. timestamp: Date.now()
  213. })
  214. const request = {
  215. targetUtilization: 0.75,
  216. accounts: ['account-1', 'account-2'],
  217. symbol: 'BTC-USD',
  218. tolerancePercent: 0.1 // 10% 容差
  219. }
  220. const response = await controller.executeUtilizationRebalance(request)
  221. expect(response.rebalanceActions).toHaveLength(2)
  222. const account1Action = response.rebalanceActions.find(a => a.accountId === 'account-1')
  223. const account2Action = response.rebalanceActions.find(a => a.accountId === 'account-2')
  224. expect(account1Action?.action).toBe('reduce') // 降低利用率
  225. expect(account2Action?.action).toBe('increase') // 提高利用率
  226. })
  227. it('should skip rebalancing when utilization is optimal', async () => {
  228. mockRiskService.getRiskMetrics.mockResolvedValue({
  229. accountId: 'test-account',
  230. deltaDeviation: 0.1,
  231. utilizationRate: 0.75, // 正好是目标利用率
  232. leverageRatio: 2,
  233. unrealizedPnl: 100,
  234. maxDrawdown: 150,
  235. timestamp: Date.now()
  236. })
  237. const request = {
  238. targetUtilization: 0.75,
  239. accounts: ['account-1', 'account-2'],
  240. symbol: 'BTC-USD',
  241. tolerancePercent: 0.05 // 5% 容差
  242. }
  243. const response = await controller.executeUtilizationRebalance(request)
  244. expect(response.success).toBe(true)
  245. expect(response.rebalanceNeeded).toBe(false)
  246. expect(response.reason).toContain('在目标范围内')
  247. })
  248. })
  249. describe('Risk Integration', () => {
  250. beforeEach(async () => {
  251. await controller.start()
  252. })
  253. it('should check risk metrics before executing operations', async () => {
  254. const request = {
  255. targetDelta: 0.0,
  256. accounts: ['account-1'],
  257. maxDeviation: 0.1,
  258. symbol: 'BTC-USD'
  259. }
  260. await controller.executeDeltaControl(request)
  261. expect(mockRiskService.getRiskMetrics).toHaveBeenCalledWith('account-1')
  262. })
  263. it('should abort operations when risk alerts are present', async () => {
  264. // 模拟活跃的风险警报
  265. mockRiskService.getActiveAlerts.mockReturnValue([
  266. {
  267. id: 'alert-1',
  268. type: 'delta-alert',
  269. severity: 'CRITICAL',
  270. accountId: 'account-1',
  271. message: '严重Delta偏差',
  272. timestamp: Date.now(),
  273. data: { deltaDeviation: 5.0 }
  274. }
  275. ])
  276. const request = {
  277. targetDelta: 0.0,
  278. accounts: ['account-1'],
  279. maxDeviation: 0.1,
  280. symbol: 'BTC-USD'
  281. }
  282. const response = await controller.executeDeltaControl(request)
  283. expect(response.success).toBe(false)
  284. expect(response.error).toContain('存在CRITICAL级别风险警报')
  285. expect(mockHedgeService.executeHedge).not.toHaveBeenCalled()
  286. })
  287. it('should update risk monitoring after successful operations', async () => {
  288. const request = {
  289. targetDelta: 0.0,
  290. accounts: ['account-1', 'account-2'],
  291. maxDeviation: 0.1,
  292. symbol: 'BTC-USD'
  293. }
  294. await controller.executeDeltaControl(request)
  295. expect(mockRiskService.updateAccountRisk).toHaveBeenCalled()
  296. })
  297. })
  298. describe('Error Handling and Edge Cases', () => {
  299. beforeEach(async () => {
  300. await controller.start()
  301. })
  302. it('should handle empty account list', async () => {
  303. const request = {
  304. targetDelta: 0.0,
  305. accounts: [],
  306. maxDeviation: 0.1,
  307. symbol: 'BTC-USD'
  308. }
  309. const response = await controller.executeDeltaControl(request)
  310. expect(response.success).toBe(false)
  311. expect(response.error).toContain('账户列表为空')
  312. })
  313. it('should handle invalid target delta values', async () => {
  314. const request = {
  315. targetDelta: NaN,
  316. accounts: ['account-1'],
  317. maxDeviation: 0.1,
  318. symbol: 'BTC-USD'
  319. }
  320. const response = await controller.executeDeltaControl(request)
  321. expect(response.success).toBe(false)
  322. expect(response.error).toContain('无效的目标Delta值')
  323. })
  324. it('should handle account manager failures', async () => {
  325. mockAccountManager.getAccounts.mockImplementation(() => {
  326. throw new Error('AccountManager connection failed')
  327. })
  328. const request = {
  329. targetDelta: 0.0,
  330. accounts: ['account-1'],
  331. maxDeviation: 0.1,
  332. symbol: 'BTC-USD'
  333. }
  334. const response = await controller.executeDeltaControl(request)
  335. expect(response.success).toBe(false)
  336. expect(response.error).toContain('获取账户状态失败')
  337. })
  338. it('should handle risk service unavailability', async () => {
  339. mockRiskService.getRiskMetrics.mockRejectedValue(new Error('Risk service unavailable'))
  340. const request = {
  341. targetUtilization: 0.75,
  342. accounts: ['account-1'],
  343. symbol: 'BTC-USD'
  344. }
  345. const response = await controller.executeUtilizationRebalance(request)
  346. expect(response.success).toBe(false)
  347. expect(response.error).toContain('风险评估失败')
  348. })
  349. })
  350. describe('Performance and Monitoring', () => {
  351. beforeEach(async () => {
  352. await controller.start()
  353. })
  354. it('should track operation execution time', async () => {
  355. const request = {
  356. targetDelta: 0.0,
  357. accounts: ['account-1', 'account-2'],
  358. maxDeviation: 0.1,
  359. symbol: 'BTC-USD'
  360. }
  361. const response = await controller.executeDeltaControl(request)
  362. expect(response.executionTime).toBeGreaterThan(0)
  363. expect(response.executionTime).toBeLessThan(10000) // 应该在10秒内完成
  364. })
  365. it('should provide detailed operation metrics', async () => {
  366. const request = {
  367. targetDelta: 0.0,
  368. accounts: ['account-1', 'account-2'],
  369. maxDeviation: 0.1,
  370. symbol: 'BTC-USD'
  371. }
  372. const response = await controller.executeDeltaControl(request)
  373. expect(response.operationId).toBeDefined()
  374. expect(response.timestamp).toBeDefined()
  375. expect(response.accountsProcessed).toBeDefined()
  376. expect(response.totalAdjustments).toBeDefined()
  377. })
  378. it('should handle concurrent operation requests', async () => {
  379. const request1 = {
  380. targetDelta: 0.0,
  381. accounts: ['account-1'],
  382. maxDeviation: 0.1,
  383. symbol: 'BTC-USD'
  384. }
  385. const request2 = {
  386. targetUtilization: 0.75,
  387. accounts: ['account-2'],
  388. symbol: 'BTC-USD'
  389. }
  390. const [response1, response2] = await Promise.all([
  391. controller.executeDeltaControl(request1),
  392. controller.executeUtilizationRebalance(request2)
  393. ])
  394. expect(response1.success).toBe(true)
  395. expect(response2.success).toBe(true)
  396. expect(response1.operationId).not.toBe(response2.operationId)
  397. })
  398. })
  399. })