test_RiskMonitoringService.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'
  2. import { RiskMonitoringService } from '../../src/services/RiskMonitoringService.js'
  3. import { MonitoringEventManager } from '../../src/models/MonitoringEvent.js'
  4. /**
  5. * RiskMonitoringService 单元测试
  6. * 测试风险监控服务的核心功能
  7. */
  8. describe('RiskMonitoringService Unit Tests', () => {
  9. let riskService: RiskMonitoringService
  10. let mockEventManager: jest.Mocked<MonitoringEventManager>
  11. beforeEach(() => {
  12. // 创建模拟的事件管理器
  13. mockEventManager = {
  14. addEvent: jest.fn(),
  15. getRecentEvents: jest.fn().mockReturnValue([]),
  16. getEventsByType: jest.fn().mockReturnValue([]),
  17. clearOldEvents: jest.fn(),
  18. } as any
  19. riskService = new RiskMonitoringService(mockEventManager)
  20. })
  21. afterEach(async () => {
  22. await riskService.stop()
  23. })
  24. describe('Service Lifecycle', () => {
  25. it('should initialize with stopped status', () => {
  26. expect(riskService.getStatus()).toBe('stopped')
  27. })
  28. it('should start monitoring successfully', async () => {
  29. await riskService.start()
  30. expect(riskService.getStatus()).toBe('running')
  31. })
  32. it('should stop monitoring successfully', async () => {
  33. await riskService.start()
  34. await riskService.stop()
  35. expect(riskService.getStatus()).toBe('stopped')
  36. })
  37. })
  38. describe('Risk Metrics Calculation', () => {
  39. beforeEach(async () => {
  40. await riskService.start()
  41. })
  42. it('should return null for non-existent account', async () => {
  43. const metrics = await riskService.getRiskMetrics('non-existent-account')
  44. expect(metrics).toBeNull()
  45. })
  46. it('should calculate risk metrics for valid account state', async () => {
  47. // 更新账户状态
  48. await riskService.updateAccountRisk('test-account', {
  49. netPosition: 0.5, // 0.5 BTC净仓位
  50. unrealizedPnl: 100, // $100 未实现盈亏
  51. leverage: 2, // 2x杠杆
  52. entryPrice: 50000, // $50,000入场价
  53. currentPrice: 51000, // $51,000当前价格
  54. })
  55. const metrics = await riskService.getRiskMetrics('test-account')
  56. expect(metrics).not.toBeNull()
  57. expect(metrics!.accountId).toBe('test-account')
  58. expect(metrics!.deltaDeviation).toBe(0.5) // 净仓位偏差
  59. expect(metrics!.utilizationRate).toBeGreaterThan(0)
  60. expect(metrics!.leverageRatio).toBe(2)
  61. expect(metrics!.unrealizedPnl).toBe(100)
  62. })
  63. it('should track maximum drawdown correctly', async () => {
  64. await riskService.updateAccountRisk('test-account', {
  65. netPosition: 1.0,
  66. unrealizedPnl: -500, // 负的P&L
  67. leverage: 3,
  68. })
  69. const metrics = await riskService.getRiskMetrics('test-account')
  70. expect(metrics!.maxDrawdown).toBe(500) // 最大回撤为正值
  71. })
  72. })
  73. describe('Risk Alert Generation', () => {
  74. beforeEach(async () => {
  75. await riskService.start()
  76. })
  77. it('should generate delta alert for high position deviation', async () => {
  78. await riskService.updateAccountRisk('test-account', {
  79. netPosition: 2.5, // 高净仓位偏差
  80. unrealizedPnl: 0,
  81. leverage: 1,
  82. })
  83. const alerts = riskService.getActiveAlerts('test-account')
  84. const deltaAlert = alerts.find(alert => alert.type === 'delta-alert')
  85. expect(deltaAlert).toBeDefined()
  86. expect(deltaAlert!.severity).toBe('WARN')
  87. })
  88. it('should generate utilization alert for high usage', async () => {
  89. await riskService.updateAccountRisk('test-account', {
  90. netPosition: 0.1,
  91. unrealizedPnl: 0,
  92. leverage: 1,
  93. utilizationRate: 0.85, // 85% 高利用率
  94. })
  95. const alerts = riskService.getActiveAlerts('test-account')
  96. const utilizationAlert = alerts.find(alert => alert.type === 'utilization-alert')
  97. expect(utilizationAlert).toBeDefined()
  98. expect(utilizationAlert!.severity).toBe('WARN')
  99. })
  100. it('should generate critical alert for excessive drawdown', async () => {
  101. await riskService.updateAccountRisk('test-account', {
  102. netPosition: 0.5,
  103. unrealizedPnl: -2500, // 高回撤
  104. leverage: 5,
  105. })
  106. const alerts = riskService.getActiveAlerts('test-account')
  107. const criticalAlert = alerts.find(alert => alert.severity === 'CRITICAL')
  108. expect(criticalAlert).toBeDefined()
  109. })
  110. })
  111. describe('Historical Risk Tracking', () => {
  112. beforeEach(async () => {
  113. await riskService.start()
  114. })
  115. it('should maintain risk metrics history', async () => {
  116. // 添加第一个风险指标
  117. await riskService.updateAccountRisk('test-account', {
  118. netPosition: 0.5,
  119. unrealizedPnl: 100,
  120. leverage: 2,
  121. })
  122. // 等待一小段时间
  123. await new Promise(resolve => setTimeout(resolve, 10))
  124. // 添加第二个风险指标
  125. await riskService.updateAccountRisk('test-account', {
  126. netPosition: 0.3,
  127. unrealizedPnl: 150,
  128. leverage: 2,
  129. })
  130. const history = riskService.getRiskHistory('test-account')
  131. expect(history.length).toBeGreaterThanOrEqual(2)
  132. expect(history[0].deltaDeviation).toBe(0.5)
  133. expect(history[history.length - 1].deltaDeviation).toBe(0.3)
  134. })
  135. it('should limit risk history to maximum size', async () => {
  136. // 添加大量风险指标 (超过限制)
  137. for (let i = 0; i < 1100; i++) {
  138. await riskService.updateAccountRisk('test-account', {
  139. netPosition: i * 0.001,
  140. unrealizedPnl: i,
  141. leverage: 1,
  142. })
  143. }
  144. const history = riskService.getRiskHistory('test-account')
  145. expect(history.length).toBeLessThanOrEqual(1000) // 最大1000条历史记录
  146. })
  147. })
  148. describe('Event Integration', () => {
  149. beforeEach(async () => {
  150. await riskService.start()
  151. })
  152. it('should log risk events to monitoring system', async () => {
  153. await riskService.updateAccountRisk('test-account', {
  154. netPosition: 2.0, // 触发风险警报
  155. unrealizedPnl: -1000,
  156. leverage: 5,
  157. })
  158. // 验证事件被记录
  159. expect(mockEventManager.addEvent).toHaveBeenCalled()
  160. const eventCall = mockEventManager.addEvent.mock.calls[0][0]
  161. expect(eventCall.type).toBe('delta-alert')
  162. expect(eventCall.accountId).toBe('test-account')
  163. })
  164. it('should include detailed risk data in events', async () => {
  165. await riskService.updateAccountRisk('test-account', {
  166. netPosition: 1.5,
  167. unrealizedPnl: -800,
  168. leverage: 4,
  169. entryPrice: 50000,
  170. currentPrice: 49000,
  171. })
  172. expect(mockEventManager.addEvent).toHaveBeenCalled()
  173. const eventCall = mockEventManager.addEvent.mock.calls[0][0]
  174. expect(eventCall.data).toMatchObject({
  175. deltaDeviation: 1.5,
  176. unrealizedPnl: -800,
  177. leverageRatio: 4,
  178. maxDrawdown: 800,
  179. })
  180. })
  181. })
  182. describe('Multi-Account Risk Management', () => {
  183. beforeEach(async () => {
  184. await riskService.start()
  185. })
  186. it('should handle multiple accounts independently', async () => {
  187. await riskService.updateAccountRisk('account-1', {
  188. netPosition: 0.5,
  189. unrealizedPnl: 100,
  190. leverage: 2,
  191. })
  192. await riskService.updateAccountRisk('account-2', {
  193. netPosition: -0.3,
  194. unrealizedPnl: -50,
  195. leverage: 1,
  196. })
  197. const metrics1 = await riskService.getRiskMetrics('account-1')
  198. const metrics2 = await riskService.getRiskMetrics('account-2')
  199. expect(metrics1!.deltaDeviation).toBe(0.5)
  200. expect(metrics2!.deltaDeviation).toBe(0.3)
  201. expect(metrics1!.unrealizedPnl).toBe(100)
  202. expect(metrics2!.unrealizedPnl).toBe(-50)
  203. })
  204. it('should return all active alerts across accounts', async () => {
  205. await riskService.updateAccountRisk('account-1', {
  206. netPosition: 2.0, // 触发警报
  207. unrealizedPnl: 0,
  208. leverage: 1,
  209. })
  210. await riskService.updateAccountRisk('account-2', {
  211. netPosition: 1.8, // 触发警报
  212. unrealizedPnl: 0,
  213. leverage: 1,
  214. })
  215. const allAlerts = riskService.getActiveAlerts()
  216. expect(allAlerts.length).toBeGreaterThanOrEqual(2)
  217. const account1Alerts = riskService.getActiveAlerts('account-1')
  218. const account2Alerts = riskService.getActiveAlerts('account-2')
  219. expect(account1Alerts.length).toBeGreaterThan(0)
  220. expect(account2Alerts.length).toBeGreaterThan(0)
  221. })
  222. })
  223. describe('Performance and Cleanup', () => {
  224. beforeEach(async () => {
  225. await riskService.start()
  226. })
  227. it('should clean up old alerts automatically', async () => {
  228. // 创建过期警报
  229. await riskService.updateAccountRisk('test-account', {
  230. netPosition: 2.0, // 触发警报
  231. unrealizedPnl: 0,
  232. leverage: 1,
  233. })
  234. // 获取初始警报数量
  235. const initialAlerts = riskService.getActiveAlerts()
  236. expect(initialAlerts.length).toBeGreaterThan(0)
  237. // 手动触发清理 (模拟时间流逝)
  238. await new Promise(resolve => setTimeout(resolve, 100))
  239. // 验证服务正在运行清理过程
  240. expect(riskService.getStatus()).toBe('running')
  241. })
  242. it('should handle concurrent risk updates safely', async () => {
  243. const promises = []
  244. // 并发更新同一账户的风险状态
  245. for (let i = 0; i < 10; i++) {
  246. promises.push(
  247. riskService.updateAccountRisk('test-account', {
  248. netPosition: i * 0.1,
  249. unrealizedPnl: i * 10,
  250. leverage: 1,
  251. })
  252. )
  253. }
  254. await Promise.all(promises)
  255. const metrics = await riskService.getRiskMetrics('test-account')
  256. expect(metrics).not.toBeNull()
  257. expect(metrics!.accountId).toBe('test-account')
  258. })
  259. })
  260. })