hedging-workflows.integration.test.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. /**
  2. * Integration Test for Hedging Workflows
  3. *
  4. * Tests end-to-end hedging operations including account pool management,
  5. * cross-platform distribution, and delta-neutral strategies.
  6. */
  7. import { CredentialManager } from '@/core/credential-manager/CredentialManager'
  8. import { Platform } from '@/types/credential'
  9. import { TradingOperation } from '@/core/credential-manager/TradingIntegration'
  10. import { LoadBalanceStrategy } from '@/core/credential-manager/HedgingAccountPool'
  11. describe('Hedging Workflows Integration Test', () => {
  12. let credentialManager: CredentialManager
  13. beforeEach(async () => {
  14. const config = {
  15. accounts: [
  16. // Pacifica accounts for hedging
  17. {
  18. id: 'pac-hedge-1',
  19. name: 'Pacifica Hedge 1',
  20. platform: Platform.PACIFICA,
  21. enabled: true,
  22. credentials: {
  23. type: 'ed25519' as const,
  24. privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  25. }
  26. },
  27. {
  28. id: 'pac-hedge-2',
  29. name: 'Pacifica Hedge 2',
  30. platform: Platform.PACIFICA,
  31. enabled: true,
  32. credentials: {
  33. type: 'ed25519' as const,
  34. privateKey: 'a26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  35. }
  36. },
  37. {
  38. id: 'pac-backup-1',
  39. name: 'Pacifica Backup 1',
  40. platform: Platform.PACIFICA,
  41. enabled: true,
  42. credentials: {
  43. type: 'ed25519' as const,
  44. privateKey: 'b26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
  45. }
  46. },
  47. // Aster accounts for hedging
  48. {
  49. id: 'ast-hedge-1',
  50. name: 'Aster Hedge 1',
  51. platform: Platform.ASTER,
  52. enabled: true,
  53. credentials: {
  54. type: 'secp256k1' as const,
  55. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  56. }
  57. },
  58. {
  59. id: 'ast-hedge-2',
  60. name: 'Aster Hedge 2',
  61. platform: Platform.ASTER,
  62. enabled: true,
  63. credentials: {
  64. type: 'secp256k1' as const,
  65. privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  66. }
  67. },
  68. // Binance accounts for hedging
  69. {
  70. id: 'bnb-hedge-1',
  71. name: 'Binance Hedge 1',
  72. platform: Platform.BINANCE,
  73. enabled: true,
  74. credentials: {
  75. type: 'hmac' as const,
  76. apiKey: 'binance-api-key-1',
  77. secretKey: 'binance-secret-key-1'
  78. }
  79. },
  80. {
  81. id: 'bnb-hedge-2',
  82. name: 'Binance Hedge 2',
  83. platform: Platform.BINANCE,
  84. enabled: true,
  85. credentials: {
  86. type: 'hmac' as const,
  87. apiKey: 'binance-api-key-2',
  88. secretKey: 'binance-secret-key-2'
  89. }
  90. }
  91. ],
  92. hedging: {
  93. platforms: {
  94. [Platform.PACIFICA]: {
  95. enabled: true,
  96. primaryAccounts: ['pac-hedge-1', 'pac-hedge-2'],
  97. backupAccounts: ['pac-backup-1'],
  98. loadBalanceStrategy: LoadBalanceStrategy.ROUND_ROBIN,
  99. healthCheckInterval: 10000,
  100. failoverThreshold: 2
  101. },
  102. [Platform.ASTER]: {
  103. enabled: true,
  104. primaryAccounts: ['ast-hedge-1', 'ast-hedge-2'],
  105. backupAccounts: [],
  106. loadBalanceStrategy: LoadBalanceStrategy.WEIGHTED,
  107. healthCheckInterval: 10000,
  108. failoverThreshold: 2
  109. },
  110. [Platform.BINANCE]: {
  111. enabled: true,
  112. primaryAccounts: ['bnb-hedge-1', 'bnb-hedge-2'],
  113. backupAccounts: [],
  114. loadBalanceStrategy: LoadBalanceStrategy.LEAST_USED,
  115. healthCheckInterval: 10000,
  116. failoverThreshold: 2
  117. }
  118. },
  119. hedging: {
  120. enableCrossplatformBalancing: true,
  121. maxAccountsPerPlatform: 5,
  122. reservationTimeoutMs: 30000
  123. }
  124. }
  125. }
  126. credentialManager = new CredentialManager()
  127. await credentialManager.loadConfiguration(config)
  128. })
  129. afterEach(async () => {
  130. await credentialManager.shutdown()
  131. })
  132. describe('Basic Hedging Operations', () => {
  133. test('should distribute hedge orders across multiple platforms', async () => {
  134. const hedgeVolume = 100000 // $100k to hedge
  135. const platforms = [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE]
  136. const distribution = await credentialManager.getHedgingAccountDistribution(
  137. hedgeVolume,
  138. platforms
  139. )
  140. // Should distribute across all three platforms
  141. expect(distribution.size).toBe(3)
  142. expect(distribution.has(Platform.PACIFICA)).toBe(true)
  143. expect(distribution.has(Platform.ASTER)).toBe(true)
  144. expect(distribution.has(Platform.BINANCE)).toBe(true)
  145. // Each platform should have at least one account
  146. for (const [platform, accounts] of distribution) {
  147. expect(accounts.length).toBeGreaterThan(0)
  148. expect(accounts.every(a => a.platform === platform)).toBe(true)
  149. }
  150. })
  151. test('should handle cross-platform account selection for hedging', async () => {
  152. const platforms = [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE]
  153. const accountCount = 4
  154. const accounts = await credentialManager.selectAccountsAcrossPlatforms(
  155. platforms,
  156. accountCount
  157. )
  158. expect(accounts.length).toBeLessThanOrEqual(accountCount)
  159. // Should select from multiple platforms
  160. const selectedPlatforms = new Set(accounts.map(a => a.platform))
  161. expect(selectedPlatforms.size).toBeGreaterThan(1)
  162. // All selected accounts should be from requested platforms
  163. expect(accounts.every(a => platforms.includes(a.platform))).toBe(true)
  164. })
  165. test('should execute simultaneous hedge trades across platforms', async () => {
  166. const hedgeTrades = [
  167. {
  168. operation: TradingOperation.PLACE_ORDER,
  169. platform: Platform.PACIFICA,
  170. parameters: {
  171. symbol: 'SOL/USDC',
  172. side: 'buy' as const,
  173. quantity: 100,
  174. type: 'market' as const
  175. }
  176. },
  177. {
  178. operation: TradingOperation.PLACE_ORDER,
  179. platform: Platform.ASTER,
  180. parameters: {
  181. symbol: 'ETH/USDC',
  182. side: 'sell' as const,
  183. quantity: 50,
  184. type: 'market' as const
  185. }
  186. },
  187. {
  188. operation: TradingOperation.PLACE_ORDER,
  189. platform: Platform.BINANCE,
  190. parameters: {
  191. symbol: 'BTCUSDT',
  192. side: 'buy' as const,
  193. quantity: 0.1,
  194. type: 'limit' as const,
  195. price: 50000
  196. }
  197. }
  198. ]
  199. const startTime = Date.now()
  200. const results = await credentialManager.signBatchTradingRequests(hedgeTrades)
  201. const duration = Date.now() - startTime
  202. // All trades should succeed
  203. expect(results).toHaveLength(3)
  204. expect(results.every(r => r.success)).toBe(true)
  205. // Should complete within reasonable time for hedging (critical for arbitrage)
  206. expect(duration).toBeLessThan(200)
  207. // Each result should use different platform
  208. const resultPlatforms = results.map(r => r.selectedAccount.platform)
  209. expect(resultPlatforms).toContain(Platform.PACIFICA)
  210. expect(resultPlatforms).toContain(Platform.ASTER)
  211. expect(resultPlatforms).toContain(Platform.BINANCE)
  212. })
  213. })
  214. describe('Load Balancing Strategies', () => {
  215. test('should implement round-robin strategy for Pacifica', async () => {
  216. const requests = Array.from({ length: 6 }, () => ({
  217. operation: TradingOperation.GET_BALANCE,
  218. platform: Platform.PACIFICA,
  219. parameters: {}
  220. }))
  221. const results = []
  222. for (const request of requests) {
  223. const result = await credentialManager.signTradingRequest(request)
  224. results.push(result)
  225. }
  226. const accountIds = results.map(r => r.selectedAccount.accountId)
  227. // Should cycle through primary accounts in round-robin fashion
  228. expect(accountIds[0]).toBe('pac-hedge-1')
  229. expect(accountIds[1]).toBe('pac-hedge-2')
  230. expect(accountIds[2]).toBe('pac-hedge-1')
  231. expect(accountIds[3]).toBe('pac-hedge-2')
  232. })
  233. test('should implement weighted strategy for Aster', async () => {
  234. const requests = Array.from({ length: 10 }, () => ({
  235. operation: TradingOperation.GET_POSITIONS,
  236. platform: Platform.ASTER,
  237. parameters: {}
  238. }))
  239. const results = []
  240. for (const request of requests) {
  241. const result = await credentialManager.signTradingRequest(request)
  242. results.push(result)
  243. }
  244. const accountIds = results.map(r => r.selectedAccount.accountId)
  245. const accountUsage = accountIds.reduce((acc, id) => {
  246. acc[id] = (acc[id] || 0) + 1
  247. return acc
  248. }, {} as Record<string, number>)
  249. // Both accounts should be used (weighted distribution)
  250. expect(Object.keys(accountUsage).length).toBeGreaterThan(1)
  251. expect(accountUsage['ast-hedge-1']).toBeGreaterThan(0)
  252. expect(accountUsage['ast-hedge-2']).toBeGreaterThan(0)
  253. })
  254. test('should implement least-used strategy for Binance', async () => {
  255. // First, make some requests to create usage history
  256. const initialRequests = [
  257. {
  258. operation: TradingOperation.GET_BALANCE,
  259. platform: Platform.BINANCE,
  260. parameters: {}
  261. }
  262. ]
  263. await credentialManager.signTradingRequest(initialRequests[0])
  264. // Now make more requests - should prefer the less-used account
  265. const requests = Array.from({ length: 4 }, () => ({
  266. operation: TradingOperation.GET_POSITIONS,
  267. platform: Platform.BINANCE,
  268. parameters: {}
  269. }))
  270. const results = []
  271. for (const request of requests) {
  272. const result = await credentialManager.signTradingRequest(request)
  273. results.push(result)
  274. }
  275. const accountIds = results.map(r => r.selectedAccount.accountId)
  276. // Should use both accounts (least-used balancing)
  277. const uniqueAccounts = new Set(accountIds)
  278. expect(uniqueAccounts.size).toBeGreaterThan(1)
  279. })
  280. })
  281. describe('Failover and Recovery', () => {
  282. test('should failover to backup accounts when primary accounts fail', async () => {
  283. // Mark primary Pacifica accounts as failed
  284. credentialManager.updateAccountAfterTrade('pac-hedge-1', {
  285. success: false,
  286. error: 'Connection timeout',
  287. timestamp: new Date()
  288. })
  289. credentialManager.updateAccountAfterTrade('pac-hedge-2', {
  290. success: false,
  291. error: 'API error',
  292. timestamp: new Date()
  293. })
  294. // Should failover to backup account
  295. const request = {
  296. operation: TradingOperation.PLACE_ORDER,
  297. platform: Platform.PACIFICA,
  298. parameters: {
  299. symbol: 'SOL/USDC',
  300. side: 'buy' as const,
  301. quantity: 10
  302. }
  303. }
  304. const result = await credentialManager.signTradingRequest(request)
  305. expect(result.success).toBe(true)
  306. expect(result.selectedAccount.accountId).toBe('pac-backup-1')
  307. expect(result.selectedAccount.isPrimary).toBe(false)
  308. })
  309. test('should recover primary accounts after successful operations', async () => {
  310. // Mark account as failed
  311. credentialManager.updateAccountAfterTrade('pac-hedge-1', {
  312. success: false,
  313. error: 'Temporary failure',
  314. timestamp: new Date()
  315. })
  316. // Simulate successful recovery
  317. credentialManager.updateAccountAfterTrade('pac-hedge-1', {
  318. success: true,
  319. tradeId: 'recovery-trade-123',
  320. timestamp: new Date()
  321. })
  322. // Account should be available again
  323. const healthCheck = await credentialManager.checkAccountHealth(['pac-hedge-1'])
  324. const health = healthCheck.get('pac-hedge-1')
  325. expect(health?.isHealthy).toBe(true)
  326. expect(health?.consecutiveFailures).toBe(0)
  327. })
  328. test('should handle complete platform failure gracefully', async () => {
  329. // Disable all Pacifica accounts
  330. for (const accountId of ['pac-hedge-1', 'pac-hedge-2', 'pac-backup-1']) {
  331. credentialManager.updateAccountAfterTrade(accountId, {
  332. success: false,
  333. error: 'Platform maintenance',
  334. timestamp: new Date()
  335. })
  336. }
  337. // Request should fail gracefully for unavailable platform
  338. const request = {
  339. operation: TradingOperation.PLACE_ORDER,
  340. platform: Platform.PACIFICA,
  341. parameters: {
  342. symbol: 'SOL/USDC',
  343. side: 'buy' as const,
  344. quantity: 10
  345. }
  346. }
  347. await expect(credentialManager.signTradingRequest(request))
  348. .rejects.toThrow('No available accounts')
  349. })
  350. })
  351. describe('Account Reservation for Hedging', () => {
  352. test('should reserve accounts for complex hedging strategy', async () => {
  353. const accountsToReserve = ['pac-hedge-1', 'ast-hedge-1', 'bnb-hedge-1']
  354. const reservation = await credentialManager.reserveAccountsForTrading(
  355. accountsToReserve,
  356. 30000 // 30 second reservation
  357. )
  358. expect(reservation.success).toBe(true)
  359. expect(reservation.reservedAccounts).toEqual(expect.arrayContaining(accountsToReserve))
  360. // Reserved accounts should not be selected for other operations
  361. const pacificaRequest = {
  362. operation: TradingOperation.GET_BALANCE,
  363. platform: Platform.PACIFICA,
  364. parameters: {}
  365. }
  366. const result = await credentialManager.signTradingRequest(pacificaRequest)
  367. // Should use non-reserved account
  368. expect(result.selectedAccount.accountId).not.toBe('pac-hedge-1')
  369. expect(result.selectedAccount.accountId).toBe('pac-hedge-2')
  370. })
  371. test('should handle concurrent reservation requests', async () => {
  372. const reservation1Promise = credentialManager.reserveAccountsForTrading(
  373. ['pac-hedge-1', 'ast-hedge-1'],
  374. 20000
  375. )
  376. const reservation2Promise = credentialManager.reserveAccountsForTrading(
  377. ['pac-hedge-2', 'bnb-hedge-1'],
  378. 20000
  379. )
  380. const [reservation1, reservation2] = await Promise.all([
  381. reservation1Promise,
  382. reservation2Promise
  383. ])
  384. expect(reservation1.success).toBe(true)
  385. expect(reservation2.success).toBe(true)
  386. // No overlap in reserved accounts
  387. const reserved1 = new Set(reservation1.reservedAccounts)
  388. const reserved2 = new Set(reservation2.reservedAccounts)
  389. const intersection = new Set([...reserved1].filter(x => reserved2.has(x)))
  390. expect(intersection.size).toBe(0)
  391. })
  392. test('should auto-release expired reservations', async () => {
  393. const shortReservation = await credentialManager.reserveAccountsForTrading(
  394. ['pac-hedge-1'],
  395. 100 // 100ms reservation
  396. )
  397. expect(shortReservation.success).toBe(true)
  398. // Wait for expiration
  399. await new Promise(resolve => setTimeout(resolve, 200))
  400. // Account should be available again
  401. const request = {
  402. operation: TradingOperation.GET_BALANCE,
  403. platform: Platform.PACIFICA,
  404. parameters: {}
  405. }
  406. const result = await credentialManager.signTradingRequest(request)
  407. // Should be able to select the previously reserved account
  408. expect(result.success).toBe(true)
  409. // Note: Due to round-robin, might select either account, so we just verify success
  410. })
  411. })
  412. describe('Delta-Neutral Hedging Scenarios', () => {
  413. test('should execute delta-neutral strategy across platforms', async () => {
  414. // Simulate a delta-neutral hedging strategy:
  415. // 1. Buy SOL on Pacifica (long position)
  416. // 2. Sell SOL equivalent on Aster (short position)
  417. // 3. Hedge with BTC position on Binance
  418. const deltaNeutralTrades = [
  419. {
  420. operation: TradingOperation.PLACE_ORDER,
  421. platform: Platform.PACIFICA,
  422. parameters: {
  423. symbol: 'SOL/USDC',
  424. side: 'buy' as const,
  425. quantity: 100,
  426. type: 'market' as const
  427. }
  428. },
  429. {
  430. operation: TradingOperation.PLACE_ORDER,
  431. platform: Platform.ASTER,
  432. parameters: {
  433. symbol: 'SOL/USDC',
  434. side: 'sell' as const,
  435. quantity: 100,
  436. type: 'market' as const
  437. }
  438. },
  439. {
  440. operation: TradingOperation.PLACE_ORDER,
  441. platform: Platform.BINANCE,
  442. parameters: {
  443. symbol: 'BTCUSDT',
  444. side: 'buy' as const,
  445. quantity: 0.05,
  446. type: 'limit' as const,
  447. price: 50000
  448. }
  449. }
  450. ]
  451. const startTime = Date.now()
  452. const results = await credentialManager.signBatchTradingRequests(deltaNeutralTrades)
  453. const executionTime = Date.now() - startTime
  454. // All trades should execute successfully
  455. expect(results).toHaveLength(3)
  456. expect(results.every(r => r.success)).toBe(true)
  457. // Should execute quickly for arbitrage opportunities
  458. expect(executionTime).toBeLessThan(150)
  459. // Verify each platform was used
  460. const platforms = results.map(r => r.selectedAccount.platform)
  461. expect(platforms).toContain(Platform.PACIFICA)
  462. expect(platforms).toContain(Platform.ASTER)
  463. expect(platforms).toContain(Platform.BINANCE)
  464. // Verify proper signature formats for each platform
  465. const pacificaResult = results.find(r => r.selectedAccount.platform === Platform.PACIFICA)
  466. const asterResult = results.find(r => r.selectedAccount.platform === Platform.ASTER)
  467. const binanceResult = results.find(r => r.selectedAccount.platform === Platform.BINANCE)
  468. expect(pacificaResult?.platformSignature.format).toBe('base64')
  469. expect(asterResult?.platformSignature.format).toBe('hex')
  470. expect(binanceResult?.platformSignature.format).toBe('hex')
  471. })
  472. test('should handle partial hedge execution failures', async () => {
  473. // Mark one account as failed to simulate partial execution
  474. credentialManager.updateAccountAfterTrade('ast-hedge-1', {
  475. success: false,
  476. error: 'Insufficient balance',
  477. timestamp: new Date()
  478. })
  479. credentialManager.updateAccountAfterTrade('ast-hedge-2', {
  480. success: false,
  481. error: 'Market closed',
  482. timestamp: new Date()
  483. })
  484. const hedgeTrades = [
  485. {
  486. operation: TradingOperation.PLACE_ORDER,
  487. platform: Platform.PACIFICA,
  488. parameters: {
  489. symbol: 'SOL/USDC',
  490. side: 'buy' as const,
  491. quantity: 50
  492. }
  493. },
  494. {
  495. operation: TradingOperation.PLACE_ORDER,
  496. platform: Platform.ASTER,
  497. parameters: {
  498. symbol: 'SOL/USDC',
  499. side: 'sell' as const,
  500. quantity: 50
  501. }
  502. }
  503. ]
  504. const results = await Promise.allSettled([
  505. credentialManager.signTradingRequest(hedgeTrades[0]),
  506. credentialManager.signTradingRequest(hedgeTrades[1])
  507. ])
  508. // Pacifica should succeed
  509. expect(results[0].status).toBe('fulfilled')
  510. if (results[0].status === 'fulfilled') {
  511. expect(results[0].value.success).toBe(true)
  512. }
  513. // Aster should fail (no available accounts)
  514. expect(results[1].status).toBe('rejected')
  515. })
  516. })
  517. describe('High-Frequency Trading Support', () => {
  518. test('should handle high-frequency hedge requests', async () => {
  519. const requestCount = 50
  520. const requests = Array.from({ length: requestCount }, (_, i) => ({
  521. operation: TradingOperation.GET_BALANCE,
  522. platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3],
  523. parameters: {}
  524. }))
  525. const startTime = Date.now()
  526. const results = await Promise.all(
  527. requests.map(req => credentialManager.signTradingRequest(req))
  528. )
  529. const totalTime = Date.now() - startTime
  530. expect(results).toHaveLength(requestCount)
  531. expect(results.every(r => r.success)).toBe(true)
  532. // Should handle high frequency efficiently
  533. expect(totalTime).toBeLessThan(500) // 50 requests in 500ms
  534. expect(totalTime / requestCount).toBeLessThan(10) // Average < 10ms per request
  535. })
  536. test('should maintain account distribution under load', async () => {
  537. const requestCount = 30
  538. const requests = Array.from({ length: requestCount }, () => ({
  539. operation: TradingOperation.GET_POSITIONS,
  540. platform: Platform.PACIFICA,
  541. parameters: {}
  542. }))
  543. const results = await Promise.all(
  544. requests.map(req => credentialManager.signTradingRequest(req))
  545. )
  546. const accountUsage = results.reduce((acc, result) => {
  547. const accountId = result.selectedAccount.accountId
  548. acc[accountId] = (acc[accountId] || 0) + 1
  549. return acc
  550. }, {} as Record<string, number>)
  551. // Should distribute load across available accounts
  552. expect(Object.keys(accountUsage).length).toBeGreaterThan(1)
  553. // With round-robin, distribution should be relatively even
  554. const usageCounts = Object.values(accountUsage)
  555. const maxUsage = Math.max(...usageCounts)
  556. const minUsage = Math.min(...usageCounts)
  557. expect(maxUsage - minUsage).toBeLessThanOrEqual(2) // Allow small variance
  558. })
  559. })
  560. })