test_risk_manager.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. "use strict";
  2. /**
  3. * Unit tests for RiskManager
  4. */
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. const RiskManager_1 = require("../../src/core/RiskManager");
  7. // Mock dependencies
  8. jest.mock('../../src/utils/Logger');
  9. describe('RiskManager', () => {
  10. let riskManager;
  11. let mockConfig;
  12. beforeEach(() => {
  13. mockConfig = {
  14. riskCheckInterval: 1000,
  15. maxBreaches: 1000,
  16. breachRetentionTime: 3600000
  17. };
  18. riskManager = new RiskManager_1.RiskManager(mockConfig);
  19. });
  20. afterEach(() => {
  21. jest.clearAllMocks();
  22. });
  23. describe('Initialization', () => {
  24. it('should initialize with correct configuration', async () => {
  25. await riskManager.initialize();
  26. const status = riskManager.getStatus();
  27. expect(status.isRunning).toBe(false);
  28. expect(status.totalBreaches).toBe(0);
  29. });
  30. it('should throw error if already initialized', async () => {
  31. await riskManager.initialize();
  32. await expect(riskManager.initialize()).rejects.toThrow('RiskManager is already initialized');
  33. });
  34. });
  35. describe('Risk Monitoring', () => {
  36. beforeEach(async () => {
  37. await riskManager.initialize();
  38. });
  39. it('should start risk monitoring', async () => {
  40. await riskManager.start();
  41. const status = riskManager.getStatus();
  42. expect(status.isRunning).toBe(true);
  43. });
  44. it('should stop risk monitoring', async () => {
  45. await riskManager.start();
  46. await riskManager.stop();
  47. const status = riskManager.getStatus();
  48. expect(status.isRunning).toBe(false);
  49. });
  50. it('should throw error when starting already running manager', async () => {
  51. await riskManager.start();
  52. await expect(riskManager.start()).rejects.toThrow();
  53. });
  54. it('should throw error when stopping non-running manager', async () => {
  55. await expect(riskManager.stop()).rejects.toThrow();
  56. });
  57. });
  58. describe('Risk Assessment', () => {
  59. beforeEach(async () => {
  60. await riskManager.initialize();
  61. });
  62. it('should assess position size risk', () => {
  63. const sessionId = 'test-session';
  64. const positionSize = 0.15; // 15% position size
  65. const maxPositionSize = 0.1; // 10% limit
  66. const riskLevel = riskManager.assessPositionSizeRisk(sessionId, positionSize, maxPositionSize);
  67. expect(riskLevel).toBe('high');
  68. });
  69. it('should assess portfolio loss risk', () => {
  70. const sessionId = 'test-session';
  71. const portfolioLoss = 0.06; // 6% loss
  72. const stopLossThreshold = 0.05; // 5% threshold
  73. const riskLevel = riskManager.assessPortfolioLossRisk(sessionId, portfolioLoss, stopLossThreshold);
  74. expect(riskLevel).toBe('high');
  75. });
  76. it('should assess slippage risk', () => {
  77. const sessionId = 'test-session';
  78. const slippage = 0.03; // 3% slippage
  79. const maxSlippage = 0.02; // 2% limit
  80. const riskLevel = riskManager.assessSlippageRisk(sessionId, slippage, maxSlippage);
  81. expect(riskLevel).toBe('high');
  82. });
  83. it('should assess market volatility risk', () => {
  84. const sessionId = 'test-session';
  85. const volatility = 0.08; // 8% volatility
  86. const maxVolatility = 0.05; // 5% limit
  87. const riskLevel = riskManager.assessMarketVolatilityRisk(sessionId, volatility, maxVolatility);
  88. expect(riskLevel).toBe('high');
  89. });
  90. it('should return low risk for acceptable values', () => {
  91. const sessionId = 'test-session';
  92. const positionRisk = riskManager.assessPositionSizeRisk(sessionId, 0.05, 0.1);
  93. const lossRisk = riskManager.assessPortfolioLossRisk(sessionId, 0.02, 0.05);
  94. const slippageRisk = riskManager.assessSlippageRisk(sessionId, 0.01, 0.02);
  95. const volatilityRisk = riskManager.assessMarketVolatilityRisk(sessionId, 0.03, 0.05);
  96. expect(positionRisk).toBe('low');
  97. expect(lossRisk).toBe('low');
  98. expect(slippageRisk).toBe('low');
  99. expect(volatilityRisk).toBe('low');
  100. });
  101. it('should return medium risk for borderline values', () => {
  102. const sessionId = 'test-session';
  103. const positionRisk = riskManager.assessPositionSizeRisk(sessionId, 0.08, 0.1);
  104. const lossRisk = riskManager.assessPortfolioLossRisk(sessionId, 0.04, 0.05);
  105. const slippageRisk = riskManager.assessSlippageRisk(sessionId, 0.018, 0.02);
  106. const volatilityRisk = riskManager.assessMarketVolatilityRisk(sessionId, 0.045, 0.05);
  107. expect(positionRisk).toBe('medium');
  108. expect(lossRisk).toBe('medium');
  109. expect(slippageRisk).toBe('medium');
  110. expect(volatilityRisk).toBe('medium');
  111. });
  112. });
  113. describe('Risk Breach Detection', () => {
  114. beforeEach(async () => {
  115. await riskManager.initialize();
  116. });
  117. it('should detect position size breach', () => {
  118. const sessionId = 'test-session';
  119. const positionSize = 0.15;
  120. const maxPositionSize = 0.1;
  121. const breach = riskManager.detectPositionSizeBreach(sessionId, positionSize, maxPositionSize);
  122. expect(breach).toBeDefined();
  123. expect(breach?.category).toBe('position_size');
  124. expect(breach?.severity).toBe('high');
  125. expect(breach?.sessionId).toBe(sessionId);
  126. });
  127. it('should detect portfolio loss breach', () => {
  128. const sessionId = 'test-session';
  129. const portfolioLoss = 0.06;
  130. const stopLossThreshold = 0.05;
  131. const breach = riskManager.detectPortfolioLossBreach(sessionId, portfolioLoss, stopLossThreshold);
  132. expect(breach).toBeDefined();
  133. expect(breach?.category).toBe('portfolio_loss');
  134. expect(breach?.severity).toBe('high');
  135. expect(breach?.sessionId).toBe(sessionId);
  136. });
  137. it('should detect slippage breach', () => {
  138. const sessionId = 'test-session';
  139. const slippage = 0.03;
  140. const maxSlippage = 0.02;
  141. const breach = riskManager.detectSlippageBreach(sessionId, slippage, maxSlippage);
  142. expect(breach).toBeDefined();
  143. expect(breach?.category).toBe('slippage');
  144. expect(breach?.severity).toBe('high');
  145. expect(breach?.sessionId).toBe(sessionId);
  146. });
  147. it('should detect market volatility breach', () => {
  148. const sessionId = 'test-session';
  149. const volatility = 0.08;
  150. const maxVolatility = 0.05;
  151. const breach = riskManager.detectMarketVolatilityBreach(sessionId, volatility, maxVolatility);
  152. expect(breach).toBeDefined();
  153. expect(breach?.category).toBe('market_volatility');
  154. expect(breach?.severity).toBe('high');
  155. expect(breach?.sessionId).toBe(sessionId);
  156. });
  157. it('should not detect breach for acceptable values', () => {
  158. const sessionId = 'test-session';
  159. const positionBreach = riskManager.detectPositionSizeBreach(sessionId, 0.05, 0.1);
  160. const lossBreach = riskManager.detectPortfolioLossBreach(sessionId, 0.02, 0.05);
  161. const slippageBreach = riskManager.detectSlippageBreach(sessionId, 0.01, 0.02);
  162. const volatilityBreach = riskManager.detectMarketVolatilityBreach(sessionId, 0.03, 0.05);
  163. expect(positionBreach).toBeNull();
  164. expect(lossBreach).toBeNull();
  165. expect(slippageBreach).toBeNull();
  166. expect(volatilityBreach).toBeNull();
  167. });
  168. });
  169. describe('Risk Breach Management', () => {
  170. beforeEach(async () => {
  171. await riskManager.initialize();
  172. });
  173. it('should add risk breach', () => {
  174. const breach = {
  175. id: 'test-breach',
  176. sessionId: 'test-session',
  177. category: 'position_size',
  178. severity: 'high',
  179. timestamp: new Date(),
  180. details: 'Position size exceeded limit',
  181. acknowledged: false
  182. };
  183. riskManager.addRiskBreach(breach);
  184. const breaches = riskManager.getSessionRiskBreaches('test-session');
  185. expect(breaches).toHaveLength(1);
  186. expect(breaches[0].id).toBe('test-breach');
  187. });
  188. it('should get session risk breaches', () => {
  189. const breach1 = {
  190. id: 'breach-1',
  191. sessionId: 'session-1',
  192. category: 'position_size',
  193. severity: 'high',
  194. timestamp: new Date(),
  195. details: 'Position size exceeded limit',
  196. acknowledged: false
  197. };
  198. const breach2 = {
  199. id: 'breach-2',
  200. sessionId: 'session-2',
  201. category: 'portfolio_loss',
  202. severity: 'medium',
  203. timestamp: new Date(),
  204. details: 'Portfolio loss exceeded threshold',
  205. acknowledged: false
  206. };
  207. riskManager.addRiskBreach(breach1);
  208. riskManager.addRiskBreach(breach2);
  209. const session1Breaches = riskManager.getSessionRiskBreaches('session-1');
  210. const session2Breaches = riskManager.getSessionRiskBreaches('session-2');
  211. expect(session1Breaches).toHaveLength(1);
  212. expect(session1Breaches[0].id).toBe('breach-1');
  213. expect(session2Breaches).toHaveLength(1);
  214. expect(session2Breaches[0].id).toBe('breach-2');
  215. });
  216. it('should acknowledge risk breach', () => {
  217. const breach = {
  218. id: 'test-breach',
  219. sessionId: 'test-session',
  220. category: 'position_size',
  221. severity: 'high',
  222. timestamp: new Date(),
  223. details: 'Position size exceeded limit',
  224. acknowledged: false
  225. };
  226. riskManager.addRiskBreach(breach);
  227. riskManager.acknowledgeRiskBreach('test-session', 'test-breach');
  228. const breaches = riskManager.getSessionRiskBreaches('test-session');
  229. expect(breaches[0].acknowledged).toBe(true);
  230. });
  231. it('should throw error when acknowledging non-existent breach', () => {
  232. expect(() => {
  233. riskManager.acknowledgeRiskBreach('test-session', 'non-existent');
  234. }).toThrow();
  235. });
  236. it('should get all risk breaches', () => {
  237. const breach1 = {
  238. id: 'breach-1',
  239. sessionId: 'session-1',
  240. category: 'position_size',
  241. severity: 'high',
  242. timestamp: new Date(),
  243. details: 'Position size exceeded limit',
  244. acknowledged: false
  245. };
  246. const breach2 = {
  247. id: 'breach-2',
  248. sessionId: 'session-2',
  249. category: 'portfolio_loss',
  250. severity: 'medium',
  251. timestamp: new Date(),
  252. details: 'Portfolio loss exceeded threshold',
  253. acknowledged: false
  254. };
  255. riskManager.addRiskBreach(breach1);
  256. riskManager.addRiskBreach(breach2);
  257. const allBreaches = riskManager.getAllRiskBreaches();
  258. expect(allBreaches).toHaveLength(2);
  259. });
  260. it('should get risk breaches by severity', () => {
  261. const breach1 = {
  262. id: 'breach-1',
  263. sessionId: 'session-1',
  264. category: 'position_size',
  265. severity: 'high',
  266. timestamp: new Date(),
  267. details: 'Position size exceeded limit',
  268. acknowledged: false
  269. };
  270. const breach2 = {
  271. id: 'breach-2',
  272. sessionId: 'session-2',
  273. category: 'portfolio_loss',
  274. severity: 'medium',
  275. timestamp: new Date(),
  276. details: 'Portfolio loss exceeded threshold',
  277. acknowledged: false
  278. };
  279. riskManager.addRiskBreach(breach1);
  280. riskManager.addRiskBreach(breach2);
  281. const highBreaches = riskManager.getRiskBreachesBySeverity('high');
  282. const mediumBreaches = riskManager.getRiskBreachesBySeverity('medium');
  283. expect(highBreaches).toHaveLength(1);
  284. expect(highBreaches[0].id).toBe('breach-1');
  285. expect(mediumBreaches).toHaveLength(1);
  286. expect(mediumBreaches[0].id).toBe('breach-2');
  287. });
  288. it('should get risk breaches by category', () => {
  289. const breach1 = {
  290. id: 'breach-1',
  291. sessionId: 'session-1',
  292. category: 'position_size',
  293. severity: 'high',
  294. timestamp: new Date(),
  295. details: 'Position size exceeded limit',
  296. acknowledged: false
  297. };
  298. const breach2 = {
  299. id: 'breach-2',
  300. sessionId: 'session-2',
  301. category: 'portfolio_loss',
  302. severity: 'medium',
  303. timestamp: new Date(),
  304. details: 'Portfolio loss exceeded threshold',
  305. acknowledged: false
  306. };
  307. riskManager.addRiskBreach(breach1);
  308. riskManager.addRiskBreach(breach2);
  309. const positionBreaches = riskManager.getRiskBreachesByCategory('position_size');
  310. const lossBreaches = riskManager.getRiskBreachesByCategory('portfolio_loss');
  311. expect(positionBreaches).toHaveLength(1);
  312. expect(positionBreaches[0].id).toBe('breach-1');
  313. expect(lossBreaches).toHaveLength(1);
  314. expect(lossBreaches[0].id).toBe('breach-2');
  315. });
  316. });
  317. describe('Risk Controls', () => {
  318. beforeEach(async () => {
  319. await riskManager.initialize();
  320. });
  321. it('should apply position size control', () => {
  322. const sessionId = 'test-session';
  323. const positionSize = 0.15;
  324. const maxPositionSize = 0.1;
  325. const controlledSize = riskManager.applyPositionSizeControl(sessionId, positionSize, maxPositionSize);
  326. expect(controlledSize).toBe(maxPositionSize);
  327. });
  328. it('should apply slippage control', () => {
  329. const sessionId = 'test-session';
  330. const slippage = 0.03;
  331. const maxSlippage = 0.02;
  332. const controlledSlippage = riskManager.applySlippageControl(sessionId, slippage, maxSlippage);
  333. expect(controlledSlippage).toBe(maxSlippage);
  334. });
  335. it('should not apply control for acceptable values', () => {
  336. const sessionId = 'test-session';
  337. const controlledSize = riskManager.applyPositionSizeControl(sessionId, 0.05, 0.1);
  338. const controlledSlippage = riskManager.applySlippageControl(sessionId, 0.01, 0.02);
  339. expect(controlledSize).toBe(0.05);
  340. expect(controlledSlippage).toBe(0.01);
  341. });
  342. });
  343. describe('Risk Statistics', () => {
  344. beforeEach(async () => {
  345. await riskManager.initialize();
  346. });
  347. it('should return correct status', () => {
  348. const status = riskManager.getStatus();
  349. expect(status).toBeDefined();
  350. expect(typeof status.isRunning).toBe('boolean');
  351. expect(typeof status.totalBreaches).toBe('number');
  352. expect(typeof status.activeBreaches).toBe('number');
  353. expect(typeof status.acknowledgedBreaches).toBe('number');
  354. });
  355. it('should return correct statistics', () => {
  356. const stats = riskManager.getStatistics();
  357. expect(stats).toBeDefined();
  358. expect(typeof stats.totalBreaches).toBe('number');
  359. expect(typeof stats.breachesByCategory).toBe('object');
  360. expect(typeof stats.breachesBySeverity).toBe('object');
  361. expect(typeof stats.acknowledgmentRate).toBe('number');
  362. });
  363. it('should calculate acknowledgment rate correctly', () => {
  364. const breach1 = {
  365. id: 'breach-1',
  366. sessionId: 'session-1',
  367. category: 'position_size',
  368. severity: 'high',
  369. timestamp: new Date(),
  370. details: 'Position size exceeded limit',
  371. acknowledged: true
  372. };
  373. const breach2 = {
  374. id: 'breach-2',
  375. sessionId: 'session-2',
  376. category: 'portfolio_loss',
  377. severity: 'medium',
  378. timestamp: new Date(),
  379. details: 'Portfolio loss exceeded threshold',
  380. acknowledged: false
  381. };
  382. riskManager.addRiskBreach(breach1);
  383. riskManager.addRiskBreach(breach2);
  384. const stats = riskManager.getStatistics();
  385. expect(stats.acknowledgmentRate).toBe(0.5); // 1 out of 2 acknowledged
  386. });
  387. });
  388. describe('Event Emission', () => {
  389. beforeEach(async () => {
  390. await riskManager.initialize();
  391. });
  392. it('should emit breach detected event', () => {
  393. const eventSpy = jest.fn();
  394. riskManager.on('breachDetected', eventSpy);
  395. const breach = {
  396. id: 'test-breach',
  397. sessionId: 'test-session',
  398. category: 'position_size',
  399. severity: 'high',
  400. timestamp: new Date(),
  401. details: 'Position size exceeded limit',
  402. acknowledged: false
  403. };
  404. riskManager.addRiskBreach(breach);
  405. expect(eventSpy).toHaveBeenCalledWith(breach);
  406. });
  407. it('should emit breach acknowledged event', () => {
  408. const breach = {
  409. id: 'test-breach',
  410. sessionId: 'test-session',
  411. category: 'position_size',
  412. severity: 'high',
  413. timestamp: new Date(),
  414. details: 'Position size exceeded limit',
  415. acknowledged: false
  416. };
  417. riskManager.addRiskBreach(breach);
  418. const eventSpy = jest.fn();
  419. riskManager.on('breachAcknowledged', eventSpy);
  420. riskManager.acknowledgeRiskBreach('test-session', 'test-breach');
  421. expect(eventSpy).toHaveBeenCalledWith(breach);
  422. });
  423. });
  424. describe('Error Handling', () => {
  425. it('should handle initialization errors gracefully', async () => {
  426. const invalidConfig = {
  427. riskCheckInterval: -1, // Invalid negative value
  428. maxBreaches: 1000,
  429. breachRetentionTime: 3600000
  430. };
  431. const invalidManager = new RiskManager_1.RiskManager(invalidConfig);
  432. await expect(invalidManager.initialize()).rejects.toThrow();
  433. });
  434. it('should handle invalid risk assessment parameters', () => {
  435. expect(() => {
  436. riskManager.assessPositionSizeRisk('', -1, 0.1);
  437. }).toThrow();
  438. });
  439. it('should handle invalid breach operations', () => {
  440. expect(() => {
  441. riskManager.acknowledgeRiskBreach('', '');
  442. }).toThrow();
  443. });
  444. });
  445. describe('Cleanup and Maintenance', () => {
  446. beforeEach(async () => {
  447. await riskManager.initialize();
  448. });
  449. it('should clean up old breaches', () => {
  450. const oldBreach = {
  451. id: 'old-breach',
  452. sessionId: 'test-session',
  453. category: 'position_size',
  454. severity: 'high',
  455. timestamp: new Date(Date.now() - 7200000), // 2 hours ago
  456. details: 'Old breach',
  457. acknowledged: true
  458. };
  459. const newBreach = {
  460. id: 'new-breach',
  461. sessionId: 'test-session',
  462. category: 'portfolio_loss',
  463. severity: 'medium',
  464. timestamp: new Date(),
  465. details: 'New breach',
  466. acknowledged: false
  467. };
  468. riskManager.addRiskBreach(oldBreach);
  469. riskManager.addRiskBreach(newBreach);
  470. riskManager.cleanupOldBreaches();
  471. const breaches = riskManager.getAllRiskBreaches();
  472. expect(breaches).toHaveLength(1);
  473. expect(breaches[0].id).toBe('new-breach');
  474. });
  475. it('should maintain breach count limit', () => {
  476. // Add more breaches than the limit
  477. for (let i = 0; i < 1500; i++) {
  478. const breach = {
  479. id: `breach-${i}`,
  480. sessionId: 'test-session',
  481. category: 'position_size',
  482. severity: 'high',
  483. timestamp: new Date(),
  484. details: `Breach ${i}`,
  485. acknowledged: false
  486. };
  487. riskManager.addRiskBreach(breach);
  488. }
  489. const breaches = riskManager.getAllRiskBreaches();
  490. expect(breaches.length).toBeLessThanOrEqual(1000); // Should not exceed maxBreaches
  491. });
  492. });
  493. });
  494. //# sourceMappingURL=test_risk_manager.js.map