test_risk_breach_handling.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. /**
  2. * Integration test for risk breach handling
  3. * Tests the complete flow of detecting, managing, and resolving risk breaches
  4. */
  5. import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
  6. import { HedgingManager } from '../../src/core/HedgingManager';
  7. import { RiskManager } from '../../src/core/RiskManager';
  8. import { RiskBreach, BreachType, BreachSeverity } from '../../src/types/hedging';
  9. describe('Risk Breach Handling Integration', () => {
  10. let hedgingManager: HedgingManager;
  11. let riskManager: RiskManager;
  12. let testSessionId: string;
  13. beforeAll(async () => {
  14. // Initialize hedging manager
  15. hedgingManager = new HedgingManager({
  16. accounts: './config/accounts.json',
  17. hedging: './config/hedging-config.json',
  18. marketData: './config/market-data-config.json'
  19. });
  20. // Initialize risk manager
  21. riskManager = new RiskManager({
  22. riskCheckInterval: 1000,
  23. alertThresholds: {
  24. positionSize: 0.08,
  25. portfolioLoss: 0.03,
  26. slippage: 0.015
  27. },
  28. maxConcurrentBreaches: 10
  29. });
  30. await hedgingManager.initialize();
  31. await riskManager.initialize();
  32. });
  33. afterAll(async () => {
  34. if (hedgingManager) {
  35. await hedgingManager.shutdown();
  36. }
  37. if (riskManager) {
  38. await riskManager.shutdown();
  39. }
  40. });
  41. beforeEach(async () => {
  42. // Create a test session for each test
  43. const sessionRequest = {
  44. name: 'Risk Breach Test Session',
  45. accountIds: ['account-1', 'account-2'],
  46. volumeTarget: 10000,
  47. strategy: {
  48. symbol: 'ETH/USD',
  49. volumeDistribution: 'equal' as const,
  50. priceRange: { min: 0.001, max: 0.01 },
  51. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  52. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  53. orderTypes: { primary: 'limit' as const, fallback: 'market' as const }
  54. }
  55. };
  56. try {
  57. const session = await hedgingManager.createSession(sessionRequest);
  58. testSessionId = session.id;
  59. } catch (error) {
  60. testSessionId = 'mock-session-id';
  61. }
  62. });
  63. describe('Risk Breach Detection', () => {
  64. it('should detect position size breaches', async () => {
  65. try {
  66. // Mock position size breach scenario
  67. const breach = await riskManager.checkPositionSizeRisk(testSessionId, 'account-1', 0.12);
  68. expect(breach).toBeDefined();
  69. expect(breach.breachType).toBe('position_size_exceeded');
  70. expect(breach.severity).toBe('critical');
  71. expect(breach.sessionId).toBe(testSessionId);
  72. expect(breach.accountId).toBe('account-1');
  73. expect(breach.details.currentValue).toBe(0.12);
  74. expect(breach.details.limitValue).toBe(0.1);
  75. expect(breach.details.percentage).toBe(120);
  76. expect(breach.resolved).toBe(false);
  77. expect(breach.timestamp).toBeInstanceOf(Date);
  78. } catch (error) {
  79. // This test should fail initially since RiskManager doesn't exist yet
  80. expect(error.message).toContain('RiskManager');
  81. }
  82. });
  83. it('should detect portfolio loss breaches', async () => {
  84. try {
  85. // Mock portfolio loss breach scenario
  86. const breach = await riskManager.checkPortfolioLossRisk(testSessionId, -0.06);
  87. expect(breach).toBeDefined();
  88. expect(breach.breachType).toBe('portfolio_loss_exceeded');
  89. expect(breach.severity).toBe('critical');
  90. expect(breach.sessionId).toBe(testSessionId);
  91. expect(breach.accountId).toBeUndefined(); // Portfolio-level breach
  92. expect(breach.details.currentValue).toBe(-0.06);
  93. expect(breach.details.limitValue).toBe(-0.05);
  94. expect(breach.details.percentage).toBe(120);
  95. expect(breach.resolved).toBe(false);
  96. } catch (error) {
  97. // This test should fail initially since RiskManager doesn't exist yet
  98. expect(error.message).toContain('RiskManager');
  99. }
  100. });
  101. it('should detect slippage breaches', async () => {
  102. try {
  103. // Mock slippage breach scenario
  104. const breach = await riskManager.checkSlippageRisk(testSessionId, 'account-1', 0.025);
  105. expect(breach).toBeDefined();
  106. expect(breach.breachType).toBe('slippage_exceeded');
  107. expect(breach.severity).toBe('warning');
  108. expect(breach.sessionId).toBe(testSessionId);
  109. expect(breach.accountId).toBe('account-1');
  110. expect(breach.details.currentValue).toBe(0.025);
  111. expect(breach.details.limitValue).toBe(0.02);
  112. expect(breach.details.percentage).toBe(125);
  113. expect(breach.resolved).toBe(false);
  114. } catch (error) {
  115. // This test should fail initially since RiskManager doesn't exist yet
  116. expect(error.message).toContain('RiskManager');
  117. }
  118. });
  119. it('should detect margin insufficient breaches', async () => {
  120. try {
  121. // Mock margin insufficient breach scenario
  122. const breach = await riskManager.checkMarginRisk(testSessionId, 'account-1', 0.05);
  123. expect(breach).toBeDefined();
  124. expect(breach.breachType).toBe('margin_insufficient');
  125. expect(breach.severity).toBe('critical');
  126. expect(breach.sessionId).toBe(testSessionId);
  127. expect(breach.accountId).toBe('account-1');
  128. expect(breach.details.currentValue).toBe(0.05);
  129. expect(breach.details.limitValue).toBe(0.1);
  130. expect(breach.details.percentage).toBe(50);
  131. expect(breach.resolved).toBe(false);
  132. } catch (error) {
  133. // This test should fail initially since RiskManager doesn't exist yet
  134. expect(error.message).toContain('RiskManager');
  135. }
  136. });
  137. it('should detect account liquidation risk', async () => {
  138. try {
  139. // Mock liquidation risk breach scenario
  140. const breach = await riskManager.checkLiquidationRisk(testSessionId, 'account-1', 0.02);
  141. expect(breach).toBeDefined();
  142. expect(breach.breachType).toBe('account_liquidation_risk');
  143. expect(breach.severity).toBe('critical');
  144. expect(breach.sessionId).toBe(testSessionId);
  145. expect(breach.accountId).toBe('account-1');
  146. expect(breach.details.currentValue).toBe(0.02);
  147. expect(breach.details.limitValue).toBe(0.05);
  148. expect(breach.details.percentage).toBe(40);
  149. expect(breach.resolved).toBe(false);
  150. } catch (error) {
  151. // This test should fail initially since RiskManager doesn't exist yet
  152. expect(error.message).toContain('RiskManager');
  153. }
  154. });
  155. });
  156. describe('Risk Breach Management', () => {
  157. let testBreachId: string;
  158. beforeEach(async () => {
  159. try {
  160. // Create a test breach
  161. const breach = await riskManager.checkPositionSizeRisk(testSessionId, 'account-1', 0.12);
  162. testBreachId = breach.id;
  163. } catch (error) {
  164. testBreachId = 'mock-breach-id';
  165. }
  166. });
  167. it('should store and retrieve risk breaches', async () => {
  168. try {
  169. const breach = await riskManager.getBreach(testBreachId);
  170. expect(breach).toBeDefined();
  171. expect(breach.id).toBe(testBreachId);
  172. expect(breach.sessionId).toBe(testSessionId);
  173. expect(breach.breachType).toBe('position_size_exceeded');
  174. expect(breach.severity).toBe('critical');
  175. expect(breach.resolved).toBe(false);
  176. } catch (error) {
  177. // This test should fail initially since RiskManager doesn't exist yet
  178. expect(error.message).toContain('RiskManager');
  179. }
  180. });
  181. it('should list active breaches for a session', async () => {
  182. try {
  183. const breaches = await riskManager.getActiveBreaches(testSessionId);
  184. expect(Array.isArray(breaches)).toBe(true);
  185. expect(breaches.length).toBeGreaterThan(0);
  186. const testBreach = breaches.find(b => b.id === testBreachId);
  187. expect(testBreach).toBeDefined();
  188. expect(testBreach.resolved).toBe(false);
  189. } catch (error) {
  190. // This test should fail initially since RiskManager doesn't exist yet
  191. expect(error.message).toContain('RiskManager');
  192. }
  193. });
  194. it('should filter breaches by type', async () => {
  195. try {
  196. const positionBreaches = await riskManager.getBreachesByType(testSessionId, 'position_size_exceeded');
  197. expect(Array.isArray(positionBreaches)).toBe(true);
  198. positionBreaches.forEach(breach => {
  199. expect(breach.breachType).toBe('position_size_exceeded');
  200. });
  201. } catch (error) {
  202. // This test should fail initially since RiskManager doesn't exist yet
  203. expect(error.message).toContain('RiskManager');
  204. }
  205. });
  206. it('should filter breaches by severity', async () => {
  207. try {
  208. const criticalBreaches = await riskManager.getBreachesBySeverity(testSessionId, 'critical');
  209. expect(Array.isArray(criticalBreaches)).toBe(true);
  210. criticalBreaches.forEach(breach => {
  211. expect(breach.severity).toBe('critical');
  212. });
  213. } catch (error) {
  214. // This test should fail initially since RiskManager doesn't exist yet
  215. expect(error.message).toContain('RiskManager');
  216. }
  217. });
  218. });
  219. describe('Risk Breach Resolution', () => {
  220. let testBreachId: string;
  221. beforeEach(async () => {
  222. try {
  223. // Create a test breach
  224. const breach = await riskManager.checkPositionSizeRisk(testSessionId, 'account-1', 0.12);
  225. testBreachId = breach.id;
  226. } catch (error) {
  227. testBreachId = 'mock-breach-id';
  228. }
  229. });
  230. it('should resolve a risk breach', async () => {
  231. try {
  232. const resolvedBreach = await riskManager.resolveBreach(testBreachId, 'Position reduced below limit');
  233. expect(resolvedBreach).toBeDefined();
  234. expect(resolvedBreach.id).toBe(testBreachId);
  235. expect(resolvedBreach.resolved).toBe(true);
  236. expect(resolvedBreach.resolvedAt).toBeDefined();
  237. expect(resolvedBreach.resolvedAt).toBeInstanceOf(Date);
  238. expect(resolvedBreach.action).toBe('Position reduced below limit');
  239. } catch (error) {
  240. // This test should fail initially since RiskManager doesn't exist yet
  241. expect(error.message).toContain('RiskManager');
  242. }
  243. });
  244. it('should acknowledge a risk breach without resolving', async () => {
  245. try {
  246. const acknowledgedBreach = await riskManager.acknowledgeBreach(testBreachId, 'ignore', 'Monitoring situation');
  247. expect(acknowledgedBreach).toBeDefined();
  248. expect(acknowledgedBreach.id).toBe(testBreachId);
  249. expect(acknowledgedBreach.resolved).toBe(false);
  250. expect(acknowledgedBreach.action).toBe('ignore');
  251. } catch (error) {
  252. // This test should fail initially since RiskManager doesn't exist yet
  253. expect(error.message).toContain('RiskManager');
  254. }
  255. });
  256. it('should prevent operations on resolved breaches', async () => {
  257. try {
  258. // First resolve the breach
  259. await riskManager.resolveBreach(testBreachId, 'Position reduced');
  260. // Try to resolve again
  261. await riskManager.resolveBreach(testBreachId, 'Another action');
  262. fail('Should have rejected resolving an already resolved breach');
  263. } catch (error) {
  264. expect(error.message).toContain('already resolved');
  265. }
  266. });
  267. });
  268. describe('Risk Monitoring', () => {
  269. it('should continuously monitor risk metrics', async () => {
  270. const riskEvents: string[] = [];
  271. try {
  272. riskManager.on('breachDetected', (breach) => {
  273. riskEvents.push('breachDetected');
  274. expect(breach.sessionId).toBe(testSessionId);
  275. });
  276. riskManager.on('breachResolved', (breach) => {
  277. riskEvents.push('breachResolved');
  278. expect(breach.sessionId).toBe(testSessionId);
  279. });
  280. // Start monitoring
  281. await riskManager.startMonitoring(testSessionId);
  282. // Wait for monitoring to detect risks
  283. await new Promise(resolve => setTimeout(resolve, 2000));
  284. // Stop monitoring
  285. await riskManager.stopMonitoring(testSessionId);
  286. expect(riskEvents.length).toBeGreaterThan(0);
  287. } catch (error) {
  288. // This test should fail initially since RiskManager doesn't exist yet
  289. expect(error.message).toContain('RiskManager');
  290. }
  291. });
  292. it('should calculate overall risk level', async () => {
  293. try {
  294. const riskLevel = await riskManager.calculateOverallRisk(testSessionId);
  295. expect(riskLevel).toBeDefined();
  296. expect(['low', 'medium', 'high']).toContain(riskLevel);
  297. } catch (error) {
  298. // This test should fail initially since RiskManager doesn't exist yet
  299. expect(error.message).toContain('RiskManager');
  300. }
  301. });
  302. it('should generate risk status report', async () => {
  303. try {
  304. const riskStatus = await riskManager.getRiskStatus(testSessionId);
  305. expect(riskStatus).toBeDefined();
  306. expect(riskStatus.sessionId).toBe(testSessionId);
  307. expect(riskStatus.overallRisk).toBeDefined();
  308. expect(riskStatus.accountRisks).toBeDefined();
  309. expect(Array.isArray(riskStatus.accountRisks)).toBe(true);
  310. expect(riskStatus.portfolioRisk).toBeDefined();
  311. expect(riskStatus.activeBreaches).toBeDefined();
  312. expect(Array.isArray(riskStatus.activeBreaches)).toBe(true);
  313. } catch (error) {
  314. // This test should fail initially since RiskManager doesn't exist yet
  315. expect(error.message).toContain('RiskManager');
  316. }
  317. });
  318. });
  319. describe('Risk Breach Notifications', () => {
  320. it('should emit breach detection events', async () => {
  321. const events: string[] = [];
  322. try {
  323. riskManager.on('breachDetected', (breach) => {
  324. events.push('breachDetected');
  325. expect(breach).toBeDefined();
  326. expect(breach.id).toBeDefined();
  327. expect(breach.sessionId).toBe(testSessionId);
  328. });
  329. // Trigger a breach
  330. await riskManager.checkPositionSizeRisk(testSessionId, 'account-1', 0.12);
  331. expect(events).toContain('breachDetected');
  332. } catch (error) {
  333. // This test should fail initially since RiskManager doesn't exist yet
  334. expect(error.message).toContain('RiskManager');
  335. }
  336. });
  337. it('should emit breach resolution events', async () => {
  338. const events: string[] = [];
  339. let testBreachId: string;
  340. try {
  341. riskManager.on('breachResolved', (breach) => {
  342. events.push('breachResolved');
  343. expect(breach).toBeDefined();
  344. expect(breach.id).toBe(testBreachId);
  345. });
  346. // Create and resolve a breach
  347. const breach = await riskManager.checkPositionSizeRisk(testSessionId, 'account-1', 0.12);
  348. testBreachId = breach.id;
  349. await riskManager.resolveBreach(testBreachId, 'Position reduced');
  350. expect(events).toContain('breachResolved');
  351. } catch (error) {
  352. // This test should fail initially since RiskManager doesn't exist yet
  353. expect(error.message).toContain('RiskManager');
  354. }
  355. });
  356. });
  357. describe('Error Handling', () => {
  358. it('should handle non-existent breach operations gracefully', async () => {
  359. const nonExistentBreachId = 'non-existent-breach-id';
  360. try {
  361. await riskManager.getBreach(nonExistentBreachId);
  362. fail('Should have thrown error for non-existent breach');
  363. } catch (error) {
  364. expect(error.message).toContain('not found');
  365. }
  366. });
  367. it('should handle invalid breach resolution attempts', async () => {
  368. const nonExistentBreachId = 'non-existent-breach-id';
  369. try {
  370. await riskManager.resolveBreach(nonExistentBreachId, 'Test resolution');
  371. fail('Should have thrown error for non-existent breach');
  372. } catch (error) {
  373. expect(error.message).toContain('not found');
  374. }
  375. });
  376. it('should handle monitoring errors gracefully', async () => {
  377. try {
  378. await riskManager.startMonitoring('non-existent-session');
  379. fail('Should have thrown error for non-existent session');
  380. } catch (error) {
  381. expect(error.message).toContain('session');
  382. }
  383. });
  384. });
  385. });