test-risk-management.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /**
  2. * Integration tests for risk management
  3. * These tests MUST fail before implementation - TDD approach
  4. */
  5. import request from 'supertest';
  6. import { Express } from 'express';
  7. // Mock Express app - this will fail until implementation
  8. let app: Express;
  9. describe('Risk Management Integration Tests', () => {
  10. beforeAll(() => {
  11. // This will fail until we implement the API
  12. // app = require('../../src/app').default;
  13. });
  14. describe('Risk Monitoring and Alerts', () => {
  15. it('should trigger position limit alerts', async () => {
  16. // Create account with low position limit
  17. const accountData = {
  18. name: 'Low Limit Account',
  19. apiKey: 'low-limit-api-key',
  20. privateKey: 'low-limit-private-key-32-characters-long',
  21. address: '0x1234567890123456789012345678901234567890'
  22. };
  23. const accountResponse = await request(app)
  24. .post('/api/v1/accounts')
  25. .send(accountData)
  26. .expect(201);
  27. const account = accountResponse.body.data;
  28. // Set very low position limit
  29. await request(app)
  30. .put(`/api/v1/accounts/${account.id}`)
  31. .send({
  32. riskLimits: {
  33. maxPositionSize: 0.01, // Very low limit
  34. stopLossThreshold: 0.05,
  35. maxSlippage: 0.02
  36. }
  37. })
  38. .expect(200);
  39. // Create session that will exceed limit
  40. const sessionData = {
  41. name: 'Position Limit Test',
  42. strategyId: 'equal-volume-btc',
  43. accountIds: [account.id],
  44. targetVolume: 0.05, // Exceeds limit
  45. duration: 30000
  46. };
  47. const sessionResponse = await request(app)
  48. .post('/api/v1/sessions')
  49. .send(sessionData)
  50. .expect(201);
  51. const session = sessionResponse.body.data;
  52. // Start session
  53. await request(app)
  54. .post(`/api/v1/sessions/${session.id}/start`)
  55. .expect(200);
  56. // Wait for risk alert
  57. await new Promise(resolve => setTimeout(resolve, 5000));
  58. // Check for risk alerts
  59. const riskResponse = await request(app)
  60. .get(`/api/v1/sessions/${session.id}/risk`)
  61. .expect(200);
  62. expect(riskResponse.body.data.riskMetrics).toHaveProperty('alerts');
  63. expect(riskResponse.body.data.riskMetrics.alerts.length).toBeGreaterThan(0);
  64. const positionLimitAlert = riskResponse.body.data.riskMetrics.alerts.find(
  65. alert => alert.type === 'position_limit'
  66. );
  67. expect(positionLimitAlert).toBeDefined();
  68. expect(positionLimitAlert.severity).toBe('high');
  69. });
  70. it('should trigger stop loss alerts', async () => {
  71. // Create account
  72. const accountData = {
  73. name: 'Stop Loss Account',
  74. apiKey: 'stop-loss-api-key',
  75. privateKey: 'stop-loss-private-key-32-characters-long',
  76. address: '0x2345678901234567890123456789012345678901'
  77. };
  78. const accountResponse = await request(app)
  79. .post('/api/v1/accounts')
  80. .send(accountData)
  81. .expect(201);
  82. const account = accountResponse.body.data;
  83. // Set low stop loss threshold
  84. await request(app)
  85. .put(`/api/v1/accounts/${account.id}`)
  86. .send({
  87. riskLimits: {
  88. maxPositionSize: 0.1,
  89. stopLossThreshold: 0.02, // Very low threshold
  90. maxSlippage: 0.02
  91. }
  92. })
  93. .expect(200);
  94. // Create session
  95. const sessionData = {
  96. name: 'Stop Loss Test',
  97. strategyId: 'equal-volume-btc',
  98. accountIds: [account.id],
  99. targetVolume: 0.1,
  100. duration: 30000
  101. };
  102. const sessionResponse = await request(app)
  103. .post('/api/v1/sessions')
  104. .send(sessionData)
  105. .expect(201);
  106. const session = sessionResponse.body.data;
  107. // Start session
  108. await request(app)
  109. .post(`/api/v1/sessions/${session.id}/start`)
  110. .expect(200);
  111. // Wait for potential stop loss trigger
  112. await new Promise(resolve => setTimeout(resolve, 10000));
  113. // Check for stop loss alerts
  114. const riskResponse = await request(app)
  115. .get(`/api/v1/sessions/${session.id}/risk`)
  116. .expect(200);
  117. const alerts = riskResponse.body.data.riskMetrics.alerts || [];
  118. const stopLossAlert = alerts.find(alert => alert.type === 'stop_loss');
  119. if (stopLossAlert) {
  120. expect(stopLossAlert.severity).toBe('critical');
  121. expect(stopLossAlert.isResolved).toBe(false);
  122. }
  123. });
  124. it('should trigger slippage alerts', async () => {
  125. // Create account with low slippage tolerance
  126. const accountData = {
  127. name: 'Slippage Account',
  128. apiKey: 'slippage-api-key',
  129. privateKey: 'slippage-private-key-32-characters-long',
  130. address: '0x3456789012345678901234567890123456789012'
  131. };
  132. const accountResponse = await request(app)
  133. .post('/api/v1/accounts')
  134. .send(accountData)
  135. .expect(201);
  136. const account = accountResponse.body.data;
  137. // Set very low slippage tolerance
  138. await request(app)
  139. .put(`/api/v1/accounts/${account.id}`)
  140. .send({
  141. riskLimits: {
  142. maxPositionSize: 0.1,
  143. stopLossThreshold: 0.05,
  144. maxSlippage: 0.005 // Very low slippage tolerance
  145. }
  146. })
  147. .expect(200);
  148. // Create session
  149. const sessionData = {
  150. name: 'Slippage Test',
  151. strategyId: 'equal-volume-btc',
  152. accountIds: [account.id],
  153. targetVolume: 0.1,
  154. duration: 30000
  155. };
  156. const sessionResponse = await request(app)
  157. .post('/api/v1/sessions')
  158. .send(sessionData)
  159. .expect(201);
  160. const session = sessionResponse.body.data;
  161. // Start session
  162. await request(app)
  163. .post(`/api/v1/sessions/${session.id}/start`)
  164. .expect(200);
  165. // Wait for potential slippage alert
  166. await new Promise(resolve => setTimeout(resolve, 10000));
  167. // Check for slippage alerts
  168. const riskResponse = await request(app)
  169. .get(`/api/v1/sessions/${session.id}/risk`)
  170. .expect(200);
  171. const alerts = riskResponse.body.data.riskMetrics.alerts || [];
  172. const slippageAlert = alerts.find(alert => alert.type === 'slippage');
  173. if (slippageAlert) {
  174. expect(slippageAlert.severity).toBe('medium');
  175. expect(slippageAlert.value).toBeGreaterThan(0.005);
  176. }
  177. });
  178. it('should trigger balance low alerts', async () => {
  179. // Create account with low balance
  180. const accountData = {
  181. name: 'Low Balance Account',
  182. apiKey: 'low-balance-api-key',
  183. privateKey: 'low-balance-private-key-32-characters-long',
  184. address: '0x4567890123456789012345678901234567890123'
  185. };
  186. const accountResponse = await request(app)
  187. .post('/api/v1/accounts')
  188. .send(accountData)
  189. .expect(201);
  190. const account = accountResponse.body.data;
  191. // Simulate low balance by setting low balance threshold
  192. await request(app)
  193. .put(`/api/v1/accounts/${account.id}`)
  194. .send({
  195. riskLimits: {
  196. maxPositionSize: 0.1,
  197. stopLossThreshold: 0.05,
  198. maxSlippage: 0.02,
  199. minBalance: 1000 // High minimum balance requirement
  200. }
  201. })
  202. .expect(200);
  203. // Create session
  204. const sessionData = {
  205. name: 'Balance Test',
  206. strategyId: 'equal-volume-btc',
  207. accountIds: [account.id],
  208. targetVolume: 0.1,
  209. duration: 30000
  210. };
  211. const sessionResponse = await request(app)
  212. .post('/api/v1/sessions')
  213. .send(sessionData)
  214. .expect(201);
  215. const session = sessionResponse.body.data;
  216. // Start session
  217. await request(app)
  218. .post(`/api/v1/sessions/${session.id}/start`)
  219. .expect(200);
  220. // Wait for balance check
  221. await new Promise(resolve => setTimeout(resolve, 5000));
  222. // Check for balance alerts
  223. const accountRiskResponse = await request(app)
  224. .get(`/api/v1/accounts/${account.id}/risk`)
  225. .expect(200);
  226. const alerts = accountRiskResponse.body.data.riskMetrics.alerts || [];
  227. const balanceAlert = alerts.find(alert => alert.type === 'balance_low');
  228. if (balanceAlert) {
  229. expect(balanceAlert.severity).toBe('high');
  230. expect(balanceAlert.value).toBeLessThan(1000);
  231. }
  232. });
  233. it('should auto-stop session on critical risk breach', async () => {
  234. // Create account with very strict limits
  235. const accountData = {
  236. name: 'Auto Stop Account',
  237. apiKey: 'auto-stop-api-key',
  238. privateKey: 'auto-stop-private-key-32-characters-long',
  239. address: '0x5678901234567890123456789012345678901234'
  240. };
  241. const accountResponse = await request(app)
  242. .post('/api/v1/accounts')
  243. .send(accountData)
  244. .expect(201);
  245. const account = accountResponse.body.data;
  246. // Set very strict limits
  247. await request(app)
  248. .put(`/api/v1/accounts/${account.id}`)
  249. .send({
  250. riskLimits: {
  251. maxPositionSize: 0.001, // Extremely low
  252. stopLossThreshold: 0.01, // Very low
  253. maxSlippage: 0.001 // Very low
  254. }
  255. })
  256. .expect(200);
  257. // Create session that will breach limits
  258. const sessionData = {
  259. name: 'Auto Stop Test',
  260. strategyId: 'equal-volume-btc',
  261. accountIds: [account.id],
  262. targetVolume: 0.1, // Will breach limits
  263. duration: 60000
  264. };
  265. const sessionResponse = await request(app)
  266. .post('/api/v1/sessions')
  267. .send(sessionData)
  268. .expect(201);
  269. const session = sessionResponse.body.data;
  270. // Start session
  271. await request(app)
  272. .post(`/api/v1/sessions/${session.id}/start`)
  273. .expect(200);
  274. // Wait for auto-stop
  275. await new Promise(resolve => setTimeout(resolve, 10000));
  276. // Check if session was auto-stopped
  277. const statusResponse = await request(app)
  278. .get(`/api/v1/sessions/${session.id}/status`)
  279. .expect(200);
  280. const status = statusResponse.body.data.status;
  281. expect(['failed', 'cancelled']).toContain(status);
  282. // Verify risk alerts show auto-stop reason
  283. const riskResponse = await request(app)
  284. .get(`/api/v1/sessions/${session.id}/risk`)
  285. .expect(200);
  286. const alerts = riskResponse.body.data.riskMetrics.alerts || [];
  287. const criticalAlerts = alerts.filter(alert => alert.severity === 'critical');
  288. expect(criticalAlerts.length).toBeGreaterThan(0);
  289. });
  290. it('should calculate risk metrics correctly', async () => {
  291. // Create multiple accounts
  292. const accounts = [];
  293. for (let i = 1; i <= 3; i++) {
  294. const accountData = {
  295. name: `Risk Metrics Account ${i}`,
  296. apiKey: `risk-metrics-api-key-${i}`,
  297. privateKey: `risk-metrics-private-key-${i}-32-characters-long`,
  298. address: `0x${i.toString().padStart(40, '0')}`
  299. };
  300. const response = await request(app)
  301. .post('/api/v1/accounts')
  302. .send(accountData)
  303. .expect(201);
  304. accounts.push(response.body.data);
  305. }
  306. // Create session
  307. const sessionData = {
  308. name: 'Risk Metrics Test',
  309. strategyId: 'equal-volume-btc',
  310. accountIds: accounts.map(acc => acc.id),
  311. targetVolume: 1.5,
  312. duration: 45000
  313. };
  314. const sessionResponse = await request(app)
  315. .post('/api/v1/sessions')
  316. .send(sessionData)
  317. .expect(201);
  318. const session = sessionResponse.body.data;
  319. // Start session
  320. await request(app)
  321. .post(`/api/v1/sessions/${session.id}/start`)
  322. .expect(200);
  323. // Wait for some activity
  324. await new Promise(resolve => setTimeout(resolve, 20000));
  325. // Check risk metrics
  326. const riskResponse = await request(app)
  327. .get(`/api/v1/sessions/${session.id}/risk`)
  328. .expect(200);
  329. const riskMetrics = riskResponse.body.data.riskMetrics;
  330. expect(riskMetrics).toHaveProperty('totalExposure');
  331. expect(riskMetrics).toHaveProperty('maxDrawdown');
  332. expect(riskMetrics).toHaveProperty('currentDrawdown');
  333. expect(riskMetrics).toHaveProperty('volatility');
  334. expect(riskMetrics).toHaveProperty('sharpeRatio');
  335. expect(riskMetrics).toHaveProperty('calculatedAt');
  336. // Verify metrics are reasonable
  337. expect(riskMetrics.totalExposure).toBeGreaterThanOrEqual(0);
  338. expect(riskMetrics.maxDrawdown).toBeGreaterThanOrEqual(0);
  339. expect(riskMetrics.currentDrawdown).toBeGreaterThanOrEqual(0);
  340. expect(riskMetrics.volatility).toBeGreaterThanOrEqual(0);
  341. });
  342. it('should maintain position neutrality under risk pressure', async () => {
  343. // Create accounts with different risk profiles
  344. const accounts = [];
  345. for (let i = 1; i <= 4; i++) {
  346. const accountData = {
  347. name: `Neutrality Risk Account ${i}`,
  348. apiKey: `neutrality-risk-api-key-${i}`,
  349. privateKey: `neutrality-risk-private-key-${i}-32-characters-long`,
  350. address: `0x${i.toString().padStart(40, '0')}`
  351. };
  352. const response = await request(app)
  353. .post('/api/v1/accounts')
  354. .send(accountData)
  355. .expect(201);
  356. // Set different risk limits
  357. await request(app)
  358. .put(`/api/v1/accounts/${response.body.data.id}`)
  359. .send({
  360. riskLimits: {
  361. maxPositionSize: 0.05 + (i * 0.01),
  362. stopLossThreshold: 0.03 + (i * 0.005),
  363. maxSlippage: 0.015 + (i * 0.002)
  364. }
  365. })
  366. .expect(200);
  367. accounts.push(response.body.data);
  368. }
  369. // Create session
  370. const sessionData = {
  371. name: 'Neutrality Risk Test',
  372. strategyId: 'equal-volume-btc',
  373. accountIds: accounts.map(acc => acc.id),
  374. targetVolume: 1.0,
  375. duration: 60000
  376. };
  377. const sessionResponse = await request(app)
  378. .post('/api/v1/sessions')
  379. .send(sessionData)
  380. .expect(201);
  381. const session = sessionResponse.body.data;
  382. // Start session
  383. await request(app)
  384. .post(`/api/v1/sessions/${session.id}/start`)
  385. .expect(200);
  386. // Wait for completion
  387. await new Promise(resolve => setTimeout(resolve, 65000));
  388. // Verify position neutrality maintained despite risk management
  389. const positionsResponse = await request(app)
  390. .get('/api/v1/hedging/positions')
  391. .expect(200);
  392. const positions = positionsResponse.body.data;
  393. expect(positions.isNeutral).toBe(true);
  394. expect(positions.totalExposure).toBe(0);
  395. expect(positions.netPositions).toEqual({});
  396. });
  397. });
  398. });