test_hedging_strategy_engine.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. /**
  2. * Unit tests for HedgingStrategyEngine
  3. */
  4. import { HedgingStrategyEngine } from '../../src/core/HedgingStrategyEngine';
  5. import { HedgingStrategy, HedgingOrder, OrderDetails } from '../../src/types/hedging';
  6. import { Logger } from '../../src/utils/Logger';
  7. // Mock dependencies
  8. jest.mock('../../src/utils/Logger');
  9. describe('HedgingStrategyEngine', () => {
  10. let strategyEngine: HedgingStrategyEngine;
  11. let mockConfig: any;
  12. beforeEach(() => {
  13. mockConfig = {
  14. maxConcurrentStrategies: 5,
  15. strategyTimeout: 60000,
  16. orderGenerationInterval: 5000
  17. };
  18. strategyEngine = new HedgingStrategyEngine(mockConfig);
  19. });
  20. afterEach(() => {
  21. jest.clearAllMocks();
  22. });
  23. describe('Initialization', () => {
  24. it('should initialize with correct configuration', async () => {
  25. await strategyEngine.initialize();
  26. const status = strategyEngine.getStatus();
  27. expect(status.isRunning).toBe(false);
  28. expect(status.totalStrategies).toBe(0);
  29. });
  30. it('should throw error if already initialized', async () => {
  31. await strategyEngine.initialize();
  32. await expect(strategyEngine.initialize()).rejects.toThrow('HedgingStrategyEngine is already initialized');
  33. });
  34. });
  35. describe('Strategy Management', () => {
  36. beforeEach(async () => {
  37. await strategyEngine.initialize();
  38. });
  39. it('should add a hedging strategy', () => {
  40. const strategy: HedgingStrategy = {
  41. id: 'test-strategy',
  42. symbol: 'ETH/USD',
  43. volumeDistribution: 'equal',
  44. priceRange: { min: 0.001, max: 0.01 },
  45. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  46. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  47. orderTypes: { primary: 'limit', fallback: 'market' },
  48. createdAt: new Date(),
  49. updatedAt: new Date()
  50. };
  51. strategyEngine.addStrategy(strategy);
  52. const retrievedStrategy = strategyEngine.getStrategy('test-strategy');
  53. expect(retrievedStrategy).toBeDefined();
  54. expect(retrievedStrategy?.id).toBe('test-strategy');
  55. });
  56. it('should remove a hedging strategy', () => {
  57. const strategy: HedgingStrategy = {
  58. id: 'remove-strategy',
  59. symbol: 'ETH/USD',
  60. volumeDistribution: 'equal',
  61. priceRange: { min: 0.001, max: 0.01 },
  62. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  63. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  64. orderTypes: { primary: 'limit', fallback: 'market' },
  65. createdAt: new Date(),
  66. updatedAt: new Date()
  67. };
  68. strategyEngine.addStrategy(strategy);
  69. strategyEngine.removeStrategy('remove-strategy');
  70. const retrievedStrategy = strategyEngine.getStrategy('remove-strategy');
  71. expect(retrievedStrategy).toBeNull();
  72. });
  73. it('should update a hedging strategy', () => {
  74. const strategy: HedgingStrategy = {
  75. id: 'update-strategy',
  76. symbol: 'ETH/USD',
  77. volumeDistribution: 'equal',
  78. priceRange: { min: 0.001, max: 0.01 },
  79. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  80. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  81. orderTypes: { primary: 'limit', fallback: 'market' },
  82. createdAt: new Date(),
  83. updatedAt: new Date()
  84. };
  85. strategyEngine.addStrategy(strategy);
  86. const updatedStrategy = {
  87. ...strategy,
  88. symbol: 'BTC/USD',
  89. updatedAt: new Date()
  90. };
  91. strategyEngine.updateStrategy(updatedStrategy);
  92. const retrievedStrategy = strategyEngine.getStrategy('update-strategy');
  93. expect(retrievedStrategy?.symbol).toBe('BTC/USD');
  94. });
  95. it('should get all strategies', () => {
  96. const strategy1: HedgingStrategy = {
  97. id: 'strategy-1',
  98. symbol: 'ETH/USD',
  99. volumeDistribution: 'equal',
  100. priceRange: { min: 0.001, max: 0.01 },
  101. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  102. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  103. orderTypes: { primary: 'limit', fallback: 'market' },
  104. createdAt: new Date(),
  105. updatedAt: new Date()
  106. };
  107. const strategy2: HedgingStrategy = {
  108. id: 'strategy-2',
  109. symbol: 'BTC/USD',
  110. volumeDistribution: 'weighted',
  111. priceRange: { min: 0.002, max: 0.02 },
  112. timing: { minInterval: 60, maxInterval: 180, orderSize: { min: 200, max: 1000 } },
  113. riskLimits: { maxPositionSize: 0.15, stopLossThreshold: 0.08, maxSlippage: 0.03 },
  114. orderTypes: { primary: 'market', fallback: 'limit' },
  115. createdAt: new Date(),
  116. updatedAt: new Date()
  117. };
  118. strategyEngine.addStrategy(strategy1);
  119. strategyEngine.addStrategy(strategy2);
  120. const allStrategies = strategyEngine.getAllStrategies();
  121. expect(allStrategies).toHaveLength(2);
  122. expect(allStrategies.map(s => s.id)).toContain('strategy-1');
  123. expect(allStrategies.map(s => s.id)).toContain('strategy-2');
  124. });
  125. });
  126. describe('Order Generation', () => {
  127. beforeEach(async () => {
  128. await strategyEngine.initialize();
  129. });
  130. it('should generate hedging orders', () => {
  131. const strategy: HedgingStrategy = {
  132. id: 'order-strategy',
  133. symbol: 'ETH/USD',
  134. volumeDistribution: 'equal',
  135. priceRange: { min: 0.001, max: 0.01 },
  136. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  137. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  138. orderTypes: { primary: 'limit', fallback: 'market' },
  139. createdAt: new Date(),
  140. updatedAt: new Date()
  141. };
  142. const accounts = ['account1', 'account2'];
  143. const currentPrice = 2500;
  144. const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice);
  145. expect(orders).toBeDefined();
  146. expect(Array.isArray(orders)).toBe(true);
  147. expect(orders.length).toBeGreaterThan(0);
  148. });
  149. it('should generate orders with correct structure', () => {
  150. const strategy: HedgingStrategy = {
  151. id: 'structure-strategy',
  152. symbol: 'ETH/USD',
  153. volumeDistribution: 'equal',
  154. priceRange: { min: 0.001, max: 0.01 },
  155. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  156. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  157. orderTypes: { primary: 'limit', fallback: 'market' },
  158. createdAt: new Date(),
  159. updatedAt: new Date()
  160. };
  161. const accounts = ['account1', 'account2'];
  162. const currentPrice = 2500;
  163. const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice);
  164. if (orders.length > 0) {
  165. const order = orders[0];
  166. expect(order.id).toBeDefined();
  167. expect(order.sessionId).toBeDefined();
  168. expect(order.strategyId).toBe('structure-strategy');
  169. expect(order.orderPair).toBeDefined();
  170. expect(order.orderPair.buyOrder).toBeDefined();
  171. expect(order.orderPair.sellOrder).toBeDefined();
  172. expect(order.status).toBe('pending');
  173. }
  174. });
  175. it('should respect volume distribution', () => {
  176. const strategy: HedgingStrategy = {
  177. id: 'volume-strategy',
  178. symbol: 'ETH/USD',
  179. volumeDistribution: 'equal',
  180. priceRange: { min: 0.001, max: 0.01 },
  181. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  182. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  183. orderTypes: { primary: 'limit', fallback: 'market' },
  184. createdAt: new Date(),
  185. updatedAt: new Date()
  186. };
  187. const accounts = ['account1', 'account2', 'account3'];
  188. const currentPrice = 2500;
  189. const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice);
  190. // With equal distribution, each account should get similar order sizes
  191. if (orders.length > 0) {
  192. const order = orders[0];
  193. const buySize = order.orderPair.buyOrder.size;
  194. const sellSize = order.orderPair.sellOrder.size;
  195. expect(buySize).toBe(sellSize); // Buy and sell should be equal for neutrality
  196. }
  197. });
  198. it('should respect price range', () => {
  199. const strategy: HedgingStrategy = {
  200. id: 'price-strategy',
  201. symbol: 'ETH/USD',
  202. volumeDistribution: 'equal',
  203. priceRange: { min: 0.001, max: 0.01 },
  204. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  205. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  206. orderTypes: { primary: 'limit', fallback: 'market' },
  207. createdAt: new Date(),
  208. updatedAt: new Date()
  209. };
  210. const accounts = ['account1', 'account2'];
  211. const currentPrice = 2500;
  212. const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice);
  213. if (orders.length > 0) {
  214. const order = orders[0];
  215. const buyPrice = order.orderPair.buyOrder.price;
  216. const sellPrice = order.orderPair.sellOrder.price;
  217. if (buyPrice && sellPrice) {
  218. const minPrice = currentPrice * (1 - strategy.priceRange.max);
  219. const maxPrice = currentPrice * (1 + strategy.priceRange.max);
  220. expect(buyPrice).toBeGreaterThanOrEqual(minPrice);
  221. expect(buyPrice).toBeLessThanOrEqual(maxPrice);
  222. expect(sellPrice).toBeGreaterThanOrEqual(minPrice);
  223. expect(sellPrice).toBeLessThanOrEqual(maxPrice);
  224. }
  225. }
  226. });
  227. it('should respect order size limits', () => {
  228. const strategy: HedgingStrategy = {
  229. id: 'size-strategy',
  230. symbol: 'ETH/USD',
  231. volumeDistribution: 'equal',
  232. priceRange: { min: 0.001, max: 0.01 },
  233. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  234. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  235. orderTypes: { primary: 'limit', fallback: 'market' },
  236. createdAt: new Date(),
  237. updatedAt: new Date()
  238. };
  239. const accounts = ['account1', 'account2'];
  240. const currentPrice = 2500;
  241. const orders = strategyEngine.generateHedgingOrders(strategy, accounts, currentPrice);
  242. if (orders.length > 0) {
  243. const order = orders[0];
  244. const buySize = order.orderPair.buyOrder.size;
  245. const sellSize = order.orderPair.sellOrder.size;
  246. expect(buySize).toBeGreaterThanOrEqual(strategy.timing.orderSize.min);
  247. expect(buySize).toBeLessThanOrEqual(strategy.timing.orderSize.max);
  248. expect(sellSize).toBeGreaterThanOrEqual(strategy.timing.orderSize.min);
  249. expect(sellSize).toBeLessThanOrEqual(strategy.timing.orderSize.max);
  250. }
  251. });
  252. });
  253. describe('Strategy Execution', () => {
  254. beforeEach(async () => {
  255. await strategyEngine.initialize();
  256. });
  257. it('should execute a hedging strategy', async () => {
  258. const strategy: HedgingStrategy = {
  259. id: 'execute-strategy',
  260. symbol: 'ETH/USD',
  261. volumeDistribution: 'equal',
  262. priceRange: { min: 0.001, max: 0.01 },
  263. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  264. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  265. orderTypes: { primary: 'limit', fallback: 'market' },
  266. createdAt: new Date(),
  267. updatedAt: new Date()
  268. };
  269. const accounts = ['account1', 'account2'];
  270. const currentPrice = 2500;
  271. const result = await strategyEngine.executeStrategy(strategy, accounts, currentPrice);
  272. expect(result).toBeDefined();
  273. expect(result.success).toBeDefined();
  274. expect(typeof result.success).toBe('boolean');
  275. });
  276. it('should handle strategy execution failure', async () => {
  277. const invalidStrategy: HedgingStrategy = {
  278. id: 'invalid-strategy',
  279. symbol: '', // Invalid empty symbol
  280. volumeDistribution: 'equal',
  281. priceRange: { min: 0.001, max: 0.01 },
  282. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  283. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  284. orderTypes: { primary: 'limit', fallback: 'market' },
  285. createdAt: new Date(),
  286. updatedAt: new Date()
  287. };
  288. const accounts = ['account1', 'account2'];
  289. const currentPrice = 2500;
  290. const result = await strategyEngine.executeStrategy(invalidStrategy, accounts, currentPrice);
  291. expect(result.success).toBe(false);
  292. expect(result.error).toBeDefined();
  293. });
  294. it('should execute strategy with retry mechanism', async () => {
  295. const strategy: HedgingStrategy = {
  296. id: 'retry-strategy',
  297. symbol: 'ETH/USD',
  298. volumeDistribution: 'equal',
  299. priceRange: { min: 0.001, max: 0.01 },
  300. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  301. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  302. orderTypes: { primary: 'limit', fallback: 'market' },
  303. createdAt: new Date(),
  304. updatedAt: new Date()
  305. };
  306. const accounts = ['account1', 'account2'];
  307. const currentPrice = 2500;
  308. // Mock retry scenario
  309. jest.spyOn(strategyEngine as any, 'executeStrategyWithRetry')
  310. .mockResolvedValueOnce({ success: false, error: 'Network error' })
  311. .mockResolvedValueOnce({ success: true, orders: [] });
  312. const result = await strategyEngine.executeStrategy(strategy, accounts, currentPrice);
  313. expect(result).toBeDefined();
  314. });
  315. });
  316. describe('Strategy Validation', () => {
  317. beforeEach(async () => {
  318. await strategyEngine.initialize();
  319. });
  320. it('should validate hedging strategy', () => {
  321. const validStrategy: HedgingStrategy = {
  322. id: 'valid-strategy',
  323. symbol: 'ETH/USD',
  324. volumeDistribution: 'equal',
  325. priceRange: { min: 0.001, max: 0.01 },
  326. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  327. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  328. orderTypes: { primary: 'limit', fallback: 'market' },
  329. createdAt: new Date(),
  330. updatedAt: new Date()
  331. };
  332. const isValid = strategyEngine.validateStrategy(validStrategy);
  333. expect(isValid).toBe(true);
  334. });
  335. it('should reject invalid hedging strategy', () => {
  336. const invalidStrategy: HedgingStrategy = {
  337. id: 'invalid-strategy',
  338. symbol: '', // Invalid empty symbol
  339. volumeDistribution: 'equal',
  340. priceRange: { min: 0.001, max: 0.01 },
  341. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  342. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  343. orderTypes: { primary: 'limit', fallback: 'market' },
  344. createdAt: new Date(),
  345. updatedAt: new Date()
  346. };
  347. const isValid = strategyEngine.validateStrategy(invalidStrategy);
  348. expect(isValid).toBe(false);
  349. });
  350. it('should validate price range', () => {
  351. const strategy: HedgingStrategy = {
  352. id: 'price-range-strategy',
  353. symbol: 'ETH/USD',
  354. volumeDistribution: 'equal',
  355. priceRange: { min: 0.001, max: 0.01 },
  356. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  357. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  358. orderTypes: { primary: 'limit', fallback: 'market' },
  359. createdAt: new Date(),
  360. updatedAt: new Date()
  361. };
  362. const isValid = strategyEngine.validatePriceRange(strategy.priceRange);
  363. expect(isValid).toBe(true);
  364. });
  365. it('should reject invalid price range', () => {
  366. const invalidPriceRange = { min: 0.01, max: 0.001 }; // min > max
  367. const isValid = strategyEngine.validatePriceRange(invalidPriceRange);
  368. expect(isValid).toBe(false);
  369. });
  370. it('should validate timing configuration', () => {
  371. const strategy: HedgingStrategy = {
  372. id: 'timing-strategy',
  373. symbol: 'ETH/USD',
  374. volumeDistribution: 'equal',
  375. priceRange: { min: 0.001, max: 0.01 },
  376. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  377. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  378. orderTypes: { primary: 'limit', fallback: 'market' },
  379. createdAt: new Date(),
  380. updatedAt: new Date()
  381. };
  382. const isValid = strategyEngine.validateTimingConfig(strategy.timing);
  383. expect(isValid).toBe(true);
  384. });
  385. it('should reject invalid timing configuration', () => {
  386. const invalidTiming = { minInterval: 120, maxInterval: 30, orderSize: { min: 500, max: 100 } }; // min > max
  387. const isValid = strategyEngine.validateTimingConfig(invalidTiming);
  388. expect(isValid).toBe(false);
  389. });
  390. });
  391. describe('Market Adaptation', () => {
  392. beforeEach(async () => {
  393. await strategyEngine.initialize();
  394. });
  395. it('should adapt strategy to market conditions', () => {
  396. const strategy: HedgingStrategy = {
  397. id: 'adapt-strategy',
  398. symbol: 'ETH/USD',
  399. volumeDistribution: 'equal',
  400. priceRange: { min: 0.001, max: 0.01 },
  401. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  402. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  403. orderTypes: { primary: 'limit', fallback: 'market' },
  404. createdAt: new Date(),
  405. updatedAt: new Date()
  406. };
  407. const marketConditions = {
  408. volatility: 0.08,
  409. volume: 1000000,
  410. spread: 0.001
  411. };
  412. const adaptedStrategy = strategyEngine.adaptStrategyToMarket(strategy, marketConditions);
  413. expect(adaptedStrategy).toBeDefined();
  414. expect(adaptedStrategy.id).toBe(strategy.id);
  415. });
  416. it('should adjust order size based on volatility', () => {
  417. const strategy: HedgingStrategy = {
  418. id: 'volatility-strategy',
  419. symbol: 'ETH/USD',
  420. volumeDistribution: 'equal',
  421. priceRange: { min: 0.001, max: 0.01 },
  422. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  423. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  424. orderTypes: { primary: 'limit', fallback: 'market' },
  425. createdAt: new Date(),
  426. updatedAt: new Date()
  427. };
  428. const highVolatilityConditions = {
  429. volatility: 0.15,
  430. volume: 1000000,
  431. spread: 0.001
  432. };
  433. const adaptedStrategy = strategyEngine.adaptStrategyToMarket(strategy, highVolatilityConditions);
  434. // High volatility should result in smaller order sizes
  435. expect(adaptedStrategy.timing.orderSize.max).toBeLessThanOrEqual(strategy.timing.orderSize.max);
  436. });
  437. it('should adjust price range based on spread', () => {
  438. const strategy: HedgingStrategy = {
  439. id: 'spread-strategy',
  440. symbol: 'ETH/USD',
  441. volumeDistribution: 'equal',
  442. priceRange: { min: 0.001, max: 0.01 },
  443. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  444. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  445. orderTypes: { primary: 'limit', fallback: 'market' },
  446. createdAt: new Date(),
  447. updatedAt: new Date()
  448. };
  449. const wideSpreadConditions = {
  450. volatility: 0.05,
  451. volume: 1000000,
  452. spread: 0.005
  453. };
  454. const adaptedStrategy = strategyEngine.adaptStrategyToMarket(strategy, wideSpreadConditions);
  455. // Wide spread should result in wider price range
  456. expect(adaptedStrategy.priceRange.max).toBeGreaterThanOrEqual(strategy.priceRange.max);
  457. });
  458. });
  459. describe('Statistics and Monitoring', () => {
  460. beforeEach(async () => {
  461. await strategyEngine.initialize();
  462. });
  463. it('should return correct status', () => {
  464. const status = strategyEngine.getStatus();
  465. expect(status).toBeDefined();
  466. expect(typeof status.isRunning).toBe('boolean');
  467. expect(typeof status.totalStrategies).toBe('number');
  468. expect(typeof status.activeStrategies).toBe('number');
  469. });
  470. it('should return correct statistics', () => {
  471. const stats = strategyEngine.getStatistics();
  472. expect(stats).toBeDefined();
  473. expect(typeof stats.totalStrategies).toBe('number');
  474. expect(typeof stats.totalOrders).toBe('number');
  475. expect(typeof stats.successfulOrders).toBe('number');
  476. expect(typeof stats.failedOrders).toBe('number');
  477. expect(typeof stats.successRate).toBe('number');
  478. expect(typeof stats.averageExecutionTime).toBe('number');
  479. });
  480. it('should calculate success rate correctly', async () => {
  481. const strategy1: HedgingStrategy = {
  482. id: 'success-strategy',
  483. symbol: 'ETH/USD',
  484. volumeDistribution: 'equal',
  485. priceRange: { min: 0.001, max: 0.01 },
  486. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  487. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  488. orderTypes: { primary: 'limit', fallback: 'market' },
  489. createdAt: new Date(),
  490. updatedAt: new Date()
  491. };
  492. const strategy2: HedgingStrategy = {
  493. id: 'fail-strategy',
  494. symbol: '', // Invalid symbol
  495. volumeDistribution: 'equal',
  496. priceRange: { min: 0.001, max: 0.01 },
  497. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  498. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  499. orderTypes: { primary: 'limit', fallback: 'market' },
  500. createdAt: new Date(),
  501. updatedAt: new Date()
  502. };
  503. const accounts = ['account1', 'account2'];
  504. const currentPrice = 2500;
  505. await strategyEngine.executeStrategy(strategy1, accounts, currentPrice);
  506. await strategyEngine.executeStrategy(strategy2, accounts, currentPrice);
  507. const stats = strategyEngine.getStatistics();
  508. expect(stats.successRate).toBe(0.5); // 1 out of 2 successful
  509. });
  510. });
  511. describe('Event Emission', () => {
  512. beforeEach(async () => {
  513. await strategyEngine.initialize();
  514. });
  515. it('should emit strategy added event', () => {
  516. const strategy: HedgingStrategy = {
  517. id: 'event-strategy',
  518. symbol: 'ETH/USD',
  519. volumeDistribution: 'equal',
  520. priceRange: { min: 0.001, max: 0.01 },
  521. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  522. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  523. orderTypes: { primary: 'limit', fallback: 'market' },
  524. createdAt: new Date(),
  525. updatedAt: new Date()
  526. };
  527. const eventSpy = jest.fn();
  528. strategyEngine.on('strategyAdded', eventSpy);
  529. strategyEngine.addStrategy(strategy);
  530. expect(eventSpy).toHaveBeenCalledWith(strategy);
  531. });
  532. it('should emit strategy executed event', async () => {
  533. const strategy: HedgingStrategy = {
  534. id: 'execute-event-strategy',
  535. symbol: 'ETH/USD',
  536. volumeDistribution: 'equal',
  537. priceRange: { min: 0.001, max: 0.01 },
  538. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  539. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  540. orderTypes: { primary: 'limit', fallback: 'market' },
  541. createdAt: new Date(),
  542. updatedAt: new Date()
  543. };
  544. const accounts = ['account1', 'account2'];
  545. const currentPrice = 2500;
  546. const eventSpy = jest.fn();
  547. strategyEngine.on('strategyExecuted', eventSpy);
  548. await strategyEngine.executeStrategy(strategy, accounts, currentPrice);
  549. expect(eventSpy).toHaveBeenCalled();
  550. });
  551. });
  552. describe('Error Handling', () => {
  553. it('should handle initialization errors gracefully', async () => {
  554. const invalidConfig = {
  555. maxConcurrentStrategies: -1, // Invalid negative value
  556. strategyTimeout: 60000,
  557. orderGenerationInterval: 5000
  558. };
  559. const invalidEngine = new HedgingStrategyEngine(invalidConfig);
  560. await expect(invalidEngine.initialize()).rejects.toThrow();
  561. });
  562. it('should handle strategy execution errors gracefully', async () => {
  563. await strategyEngine.initialize();
  564. const invalidStrategy: HedgingStrategy = {
  565. id: 'error-strategy',
  566. symbol: 'ETH/USD',
  567. volumeDistribution: 'equal',
  568. priceRange: { min: 0.001, max: 0.01 },
  569. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  570. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  571. orderTypes: { primary: 'limit', fallback: 'market' },
  572. createdAt: new Date(),
  573. updatedAt: new Date()
  574. };
  575. const accounts: string[] = []; // Empty accounts array
  576. const currentPrice = 2500;
  577. const result = await strategyEngine.executeStrategy(invalidStrategy, accounts, currentPrice);
  578. expect(result.success).toBe(false);
  579. expect(result.error).toBeDefined();
  580. });
  581. });
  582. describe('Lifecycle Management', () => {
  583. it('should start and stop the engine', async () => {
  584. await strategyEngine.initialize();
  585. await strategyEngine.start();
  586. const status = strategyEngine.getStatus();
  587. expect(status.isRunning).toBe(true);
  588. await strategyEngine.stop();
  589. const stoppedStatus = strategyEngine.getStatus();
  590. expect(stoppedStatus.isRunning).toBe(false);
  591. });
  592. it('should throw error when starting already running engine', async () => {
  593. await strategyEngine.initialize();
  594. await strategyEngine.start();
  595. await expect(strategyEngine.start()).rejects.toThrow();
  596. });
  597. it('should throw error when stopping non-running engine', async () => {
  598. await strategyEngine.initialize();
  599. await expect(strategyEngine.stop()).rejects.toThrow();
  600. });
  601. });
  602. });