SamePlatformHedgingManager.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. import { Config } from '../../config/simpleEnv.js'
  2. import { httpClient } from '../../utils/httpClient.js'
  3. import { logger } from '../../utils/logger.js'
  4. import { PacificaProxyClient } from '../../exchanges/pacifica/PacificaProxyClient.js'
  5. /**
  6. * 同一平台对冲管理器
  7. * 支持在同一交易所内使用不同账户进行对冲操作,通过代理实现网络隔离
  8. */
  9. export class SamePlatformHedgingManager {
  10. constructor(exchange, riskLimits) {
  11. this.exchange = exchange
  12. this.accounts = new Map()
  13. this.hedgePairs = new Map()
  14. this.clients = new Map()
  15. // 初始化风险限制
  16. this.riskLimits = {
  17. maxPositionSize: riskLimits?.maxPositionSize || 1.0,
  18. maxTotalExposure: riskLimits?.maxTotalExposure || 5.0,
  19. maxAccountBalance: riskLimits?.maxAccountBalance || 10000,
  20. minAccountBalance: riskLimits?.minAccountBalance || 100,
  21. maxDailyTrades: riskLimits?.maxDailyTrades || 100,
  22. maxSlippage: riskLimits?.maxSlippage || 0.02,
  23. emergencyStopLoss: riskLimits?.emergencyStopLoss || 0.05,
  24. enabled: riskLimits?.enabled ?? true,
  25. }
  26. }
  27. /**
  28. * 添加对冲账户
  29. */
  30. addAccount(accountId, config) {
  31. const account = {
  32. id: accountId,
  33. exchange: this.exchange,
  34. config,
  35. positions: new Map(),
  36. lastUpdate: 0,
  37. proxyConfig: this.getProxyForAccount(accountId),
  38. }
  39. this.accounts.set(accountId, account)
  40. // 创建对应的交易客户端
  41. this.createTradingClient(accountId, config)
  42. logger.info(`添加${this.exchange}对冲账户`, {
  43. accountId,
  44. hasProxy: !!account.proxyConfig,
  45. exchange: this.exchange,
  46. })
  47. }
  48. /**
  49. * 创建对冲对
  50. */
  51. createHedgePair(pairId, longAccountId, shortAccountId, symbol, targetRatio = 1.0) {
  52. if (!this.accounts.has(longAccountId) || !this.accounts.has(shortAccountId)) {
  53. throw new Error(`账户不存在: ${longAccountId} 或 ${shortAccountId}`)
  54. }
  55. const pair = {
  56. id: pairId,
  57. longAccountId,
  58. shortAccountId,
  59. symbol,
  60. targetRatio,
  61. currentLongPosition: 0,
  62. currentShortPosition: 0,
  63. netExposure: 0,
  64. lastRebalance: 0,
  65. isActive: true,
  66. }
  67. this.hedgePairs.set(pairId, pair)
  68. logger.info(`创建${this.exchange}对冲对`, {
  69. pairId,
  70. longAccount: longAccountId,
  71. shortAccount: shortAccountId,
  72. symbol,
  73. targetRatio,
  74. })
  75. }
  76. /**
  77. * 创建交易客户端
  78. */
  79. createTradingClient(accountId, config) {
  80. if (this.exchange === 'pacifica') {
  81. const client = new PacificaProxyClient({
  82. account: config.account,
  83. privateKey: config.privateKey,
  84. agentWallet: config.agentWallet,
  85. agentPrivateKey: config.agentPrivateKey,
  86. })
  87. this.clients.set(accountId, client)
  88. } else if (this.exchange === 'aster') {
  89. // TODO: 实现Aster客户端
  90. logger.warn('Aster交易客户端暂未实现', { accountId })
  91. } else if (this.exchange === 'binance') {
  92. // TODO: 实现Binance客户端
  93. logger.warn('Binance交易客户端暂未实现', { accountId })
  94. }
  95. }
  96. /**
  97. * 获取交易客户端
  98. */
  99. getTradingClient(accountId) {
  100. const client = this.clients.get(accountId)
  101. if (!client) {
  102. throw new Error(`未找到账户${accountId}的交易客户端`)
  103. }
  104. return client
  105. }
  106. /**
  107. * 获取账户专用代理配置
  108. */
  109. getProxyForAccount(accountId) {
  110. // 支持为不同账户配置不同的代理会话
  111. // 这样可以模拟不同的网络来源,减少被识别为同一用户的风险
  112. if (!Config.proxy.isConfigured(this.exchange)) {
  113. return undefined
  114. }
  115. // 为每个账户生成唯一的会话标识
  116. const accountSpecificProxy = Config.proxy.getUrl(this.exchange)
  117. if (!accountSpecificProxy) return undefined
  118. // 如果配置了会话管理,为每个账户创建不同的会话
  119. if (this.exchange === 'aster' && Config.proxy.aster.sessionPrefix()) {
  120. // 为不同账户生成不同的会话后缀
  121. const accountSuffix = this.generateAccountSuffix(accountId)
  122. // 这里可以扩展为每个账户使用不同的会话配置
  123. return accountSpecificProxy // 暂时返回相同的URL,未来可以优化
  124. }
  125. return accountSpecificProxy
  126. }
  127. /**
  128. * 为账户生成唯一后缀
  129. */
  130. generateAccountSuffix(accountId) {
  131. // 基于账户ID生成稳定的后缀
  132. let hash = 0
  133. for (let i = 0; i < accountId.length; i++) {
  134. const char = accountId.charCodeAt(i)
  135. hash = (hash << 5) - hash + char
  136. hash = hash & hash // 转换为32位整数
  137. }
  138. return Math.abs(hash).toString(36).substring(0, 6)
  139. }
  140. /**
  141. * 更新账户仓位
  142. */
  143. async updateAccountPositions(accountId) {
  144. const account = this.accounts.get(accountId)
  145. if (!account) throw new Error(`账户不存在: ${accountId}`)
  146. try {
  147. // 使用账户专用代理请求仓位信息
  148. const positions = await this.fetchPositions(account)
  149. // 更新仓位缓存
  150. account.positions.clear()
  151. positions.forEach(pos => {
  152. account.positions.set(pos.symbol, pos)
  153. })
  154. account.lastUpdate = Date.now()
  155. logger.debug(`更新${this.exchange}账户仓位`, {
  156. accountId,
  157. positionCount: positions.length,
  158. proxy: account.proxyConfig ? '启用' : '禁用',
  159. })
  160. } catch (error) {
  161. logger.error(`更新账户仓位失败`, {
  162. exchange: this.exchange,
  163. accountId,
  164. error: error.message,
  165. })
  166. throw error
  167. }
  168. }
  169. /**
  170. * 获取仓位信息(根据交易所实现)
  171. */
  172. async fetchPositions(account) {
  173. const baseUrl = this.getExchangeApiUrl()
  174. const endpoint = this.getPositionsEndpoint()
  175. const response = await httpClient.get(`${baseUrl}${endpoint}`, {
  176. exchange: this.exchange,
  177. accountId: account.id,
  178. timeout: 10000,
  179. retries: 2,
  180. headers: this.buildAuthHeaders(account),
  181. })
  182. if (!response.ok) {
  183. throw new Error(`获取仓位失败: ${response.status}`)
  184. }
  185. return this.parsePositions(response.data)
  186. }
  187. /**
  188. * 获取交易所API基础URL
  189. */
  190. getExchangeApiUrl() {
  191. switch (this.exchange) {
  192. case 'aster':
  193. return Config.aster.httpBase
  194. case 'pacifica':
  195. return Config.pacifica.baseUrl
  196. case 'binance':
  197. return Config.binance.baseUrl
  198. default:
  199. throw new Error(`不支持的交易所: ${this.exchange}`)
  200. }
  201. }
  202. /**
  203. * 获取仓位查询端点
  204. */
  205. getPositionsEndpoint() {
  206. switch (this.exchange) {
  207. case 'aster':
  208. return '/api/v1/positions'
  209. case 'pacifica':
  210. return '/api/v1/positions'
  211. case 'binance':
  212. return '/fapi/v2/positionRisk'
  213. default:
  214. throw new Error(`不支持的交易所: ${this.exchange}`)
  215. }
  216. }
  217. /**
  218. * 构建认证头(根据交易所实现)
  219. */
  220. buildAuthHeaders(account) {
  221. // 这里需要根据具体交易所的认证方式实现
  222. // 暂时返回空对象,实际使用时需要具体实现
  223. return {}
  224. }
  225. /**
  226. * 解析仓位数据(根据交易所格式实现)
  227. */
  228. parsePositions(data) {
  229. // 这里需要根据具体交易所的返回格式实现
  230. // 暂时返回空数组,实际使用时需要具体实现
  231. return []
  232. }
  233. /**
  234. * 计算对冲对的净敞口
  235. */
  236. calculateNetExposure(pairId) {
  237. const pair = this.hedgePairs.get(pairId)
  238. if (!pair) throw new Error(`对冲对不存在: ${pairId}`)
  239. const longAccount = this.accounts.get(pair.longAccountId)
  240. const shortAccount = this.accounts.get(pair.shortAccountId)
  241. if (!longAccount || !shortAccount) {
  242. throw new Error(`对冲对账户不完整: ${pairId}`)
  243. }
  244. const longPosition = longAccount.positions.get(pair.symbol)
  245. const shortPosition = shortAccount.positions.get(pair.symbol)
  246. const longSize = longPosition?.size || 0
  247. const shortSize = shortPosition?.size || 0
  248. pair.currentLongPosition = longSize
  249. pair.currentShortPosition = shortSize
  250. pair.netExposure = longSize + shortSize // 注意:空头为负数
  251. return pair.netExposure
  252. }
  253. /**
  254. * 执行对冲再平衡
  255. */
  256. async rebalanceHedgePair(pairId, tolerance = 0.01) {
  257. const pair = this.hedgePairs.get(pairId)
  258. if (!pair || !pair.isActive) return false
  259. // 更新仓位信息
  260. await Promise.all([
  261. this.updateAccountPositions(pair.longAccountId),
  262. this.updateAccountPositions(pair.shortAccountId),
  263. ])
  264. const netExposure = this.calculateNetExposure(pairId)
  265. // 获取账户状态进行资金利用率分析
  266. const longPosition = this.accountPositions.get(pair.longAccountId)?.get(pair.symbol) || 0
  267. const shortPosition = this.accountPositions.get(pair.shortAccountId)?.get(pair.symbol) || 0
  268. // 多维度平衡检查
  269. const exposureDiff = Math.abs(netExposure)
  270. const positionImbalance = Math.abs(Math.abs(longPosition) - Math.abs(shortPosition))
  271. console.log(`🔍 [平衡检查] 净敞口: ${netExposure.toFixed(4)}, 仓位不平衡: ${positionImbalance.toFixed(4)}`)
  272. // 更严格的平衡标准
  273. const needsRebalance = exposureDiff > tolerance || positionImbalance > 0.002
  274. if (!needsRebalance) {
  275. logger.debug(`对冲对无需再平衡`, {
  276. pairId,
  277. netExposure,
  278. positionImbalance,
  279. tolerance,
  280. exchange: this.exchange,
  281. })
  282. return false
  283. }
  284. console.log(
  285. `🚨 [需要平衡] 净敞口: ${exposureDiff.toFixed(4)} > ${tolerance} 或仓位不平衡: ${positionImbalance.toFixed(
  286. 4,
  287. )} > 0.002`,
  288. )
  289. // 执行再平衡交易
  290. try {
  291. const success = await this.executeRebalanceTrades(pair, netExposure)
  292. if (success) {
  293. pair.lastRebalance = Date.now()
  294. logger.info(`对冲再平衡成功`, {
  295. pairId,
  296. netExposure,
  297. exchange: this.exchange,
  298. })
  299. }
  300. return success
  301. } catch (error) {
  302. logger.error(`对冲再平衡失败`, {
  303. pairId,
  304. error: error.message,
  305. exchange: this.exchange,
  306. })
  307. return false
  308. }
  309. }
  310. /**
  311. * 执行再平衡交易 - 改进版:真正的双向平衡
  312. */
  313. async executeRebalanceTrades(pair, netExposure) {
  314. console.log(`🔄 [再平衡] 开始双向平衡,当前净敞口: ${netExposure.toFixed(4)} BTC`)
  315. // 获取两个账户的当前状态
  316. const longPosition = this.accountPositions.get(pair.longAccountId)?.get(pair.symbol) || 0
  317. const shortPosition = this.accountPositions.get(pair.shortAccountId)?.get(pair.symbol) || 0
  318. console.log(`📊 [再平衡] 当前仓位 - 多头账户: ${longPosition.toFixed(4)}, 空头账户: ${shortPosition.toFixed(4)}`)
  319. // 计算理想的平衡状态:两个账户仓位大小相等,方向相反
  320. const totalAbsPosition = Math.abs(longPosition) + Math.abs(shortPosition)
  321. const idealPositionSize = totalAbsPosition / 2
  322. if (idealPositionSize < 0.0005) {
  323. console.log(`ℹ️ [再平衡] 仓位太小,无需平衡`)
  324. return false
  325. }
  326. // 计算每个账户需要的调整量
  327. const longAdjustment = idealPositionSize - Math.abs(longPosition)
  328. const shortAdjustment = idealPositionSize - Math.abs(shortPosition)
  329. console.log(`🎯 [再平衡] 理想仓位: ${idealPositionSize.toFixed(4)} BTC`)
  330. console.log(`📈 [再平衡] 调整量 - 多头: ${longAdjustment.toFixed(4)}, 空头: ${shortAdjustment.toFixed(4)}`)
  331. const minOrderSize = 0.0003 // 降低最小订单量
  332. const orders = []
  333. // 生成双向调整订单
  334. if (Math.abs(longAdjustment) >= minOrderSize) {
  335. if (longAdjustment > 0) {
  336. // 多头账户需要增加多头仓位
  337. orders.push({
  338. accountId: pair.longAccountId,
  339. side: 'bid',
  340. amount: Math.abs(longAdjustment),
  341. reason: '再平衡-增加多头仓位',
  342. })
  343. } else {
  344. // 多头账户需要减少仓位
  345. orders.push({
  346. accountId: pair.longAccountId,
  347. side: 'ask',
  348. amount: Math.abs(longAdjustment),
  349. reason: '再平衡-减少多头仓位',
  350. })
  351. }
  352. }
  353. if (Math.abs(shortAdjustment) >= minOrderSize) {
  354. if (shortAdjustment > 0) {
  355. // 空头账户需要增加空头仓位
  356. orders.push({
  357. accountId: pair.shortAccountId,
  358. side: 'ask',
  359. amount: Math.abs(shortAdjustment),
  360. reason: '再平衡-增加空头仓位',
  361. })
  362. } else {
  363. // 空头账户需要减少仓位
  364. orders.push({
  365. accountId: pair.shortAccountId,
  366. side: 'bid',
  367. amount: Math.abs(shortAdjustment),
  368. reason: '再平衡-减少空头仓位',
  369. })
  370. }
  371. }
  372. if (orders.length === 0) {
  373. console.log(`ℹ️ [再平衡] 调整量都太小,跳过再平衡`)
  374. return false
  375. }
  376. // 执行双向调整订单
  377. try {
  378. console.log(`🔄 [再平衡] 执行${orders.length}个调整订单`)
  379. for (const order of orders) {
  380. console.log(`📝 [再平衡] ${order.accountId}: ${order.side} ${order.amount.toFixed(4)} BTC - ${order.reason}`)
  381. await this.executeHedgeOrder(order.accountId, pair.symbol, order.amount, order.side)
  382. // 短暂延迟避免过快执行
  383. await new Promise(resolve => setTimeout(resolve, 200))
  384. }
  385. console.log(`✅ [再平衡] 双向平衡完成`)
  386. return true
  387. } catch (error) {
  388. logger.error(`执行双向再平衡交易失败`, {
  389. pair: pair.id,
  390. netExposure,
  391. orders,
  392. error: error.message,
  393. exchange: this.exchange,
  394. })
  395. return false
  396. }
  397. }
  398. /**
  399. * 风险检查
  400. */
  401. async checkRiskLimits(accountId, symbol, amount, side) {
  402. if (!this.riskLimits.enabled) {
  403. return { allowed: true }
  404. }
  405. try {
  406. // 检查订单量限制
  407. if (amount > this.riskLimits.maxPositionSize) {
  408. return {
  409. allowed: false,
  410. reason: `订单量${amount}超过单个仓位最大限制${this.riskLimits.maxPositionSize}`,
  411. }
  412. }
  413. // 检查账户余额
  414. const client = this.getTradingClient(accountId)
  415. const balances = await client.getBalances()
  416. if (balances && Array.isArray(balances)) {
  417. const usdBalance = balances.find(b => b.asset === 'USDT' || b.asset === 'USD' || b.asset === 'USDC')
  418. if (usdBalance) {
  419. const balance = parseFloat(usdBalance.free || usdBalance.available || '0')
  420. if (balance < this.riskLimits.minAccountBalance) {
  421. return {
  422. allowed: false,
  423. reason: `账户余额${balance}低于最小要求${this.riskLimits.minAccountBalance}`,
  424. }
  425. }
  426. }
  427. }
  428. // 检查总敞口
  429. const totalExposure = await this.calculateTotalExposure(accountId)
  430. const newExposure = side === 'bid' ? totalExposure + amount : totalExposure - amount
  431. if (Math.abs(newExposure) > this.riskLimits.maxTotalExposure) {
  432. return {
  433. allowed: false,
  434. reason: `新的总敞口${Math.abs(newExposure)}将超过最大限制${this.riskLimits.maxTotalExposure}`,
  435. }
  436. }
  437. logger.info('风险检查通过', {
  438. accountId,
  439. symbol,
  440. amount,
  441. side,
  442. currentExposure: totalExposure,
  443. newExposure,
  444. })
  445. return { allowed: true }
  446. } catch (error) {
  447. logger.error('风险检查失败', {
  448. accountId,
  449. symbol,
  450. error: error.message,
  451. })
  452. return {
  453. allowed: false,
  454. reason: `风险检查失败: ${error.message}`,
  455. }
  456. }
  457. }
  458. /**
  459. * 计算账户总敞口
  460. */
  461. async calculateTotalExposure(accountId) {
  462. try {
  463. const client = this.getTradingClient(accountId)
  464. const positions = await client.getPositions()
  465. let totalExposure = 0
  466. if (positions && Array.isArray(positions)) {
  467. for (const position of positions) {
  468. const size = parseFloat(position.size || position.amount || '0')
  469. totalExposure += Math.abs(size)
  470. }
  471. }
  472. return totalExposure
  473. } catch (error) {
  474. logger.error('计算总敞口失败', {
  475. accountId,
  476. error: error.message,
  477. })
  478. return 0
  479. }
  480. }
  481. /**
  482. * 紧急止损检查
  483. */
  484. async checkEmergencyStopLoss(accountId) {
  485. try {
  486. const client = this.getTradingClient(accountId)
  487. const positions = await client.getPositions()
  488. if (!positions || !Array.isArray(positions)) {
  489. return false
  490. }
  491. for (const position of positions) {
  492. const pnlPercent = parseFloat(position.pnlPercent || position.unrealizedPnlPercent || '0')
  493. if (Math.abs(pnlPercent) > this.riskLimits.emergencyStopLoss) {
  494. logger.warn('触发紧急止损', {
  495. accountId,
  496. symbol: position.symbol,
  497. pnlPercent,
  498. emergencyStopLoss: this.riskLimits.emergencyStopLoss,
  499. })
  500. // 执行紧急平仓
  501. await this.emergencyClosePosition(accountId, position)
  502. return true
  503. }
  504. }
  505. return false
  506. } catch (error) {
  507. logger.error('紧急止损检查失败', {
  508. accountId,
  509. error: error.message,
  510. })
  511. return false
  512. }
  513. }
  514. /**
  515. * 紧急平仓
  516. */
  517. async emergencyClosePosition(accountId, position) {
  518. try {
  519. const client = this.getTradingClient(accountId)
  520. const account = this.accounts.get(accountId)
  521. if (!account) {
  522. throw new Error(`账户${accountId}不存在`)
  523. }
  524. const size = Math.abs(parseFloat(position.size || position.amount || '0'))
  525. const isLong = parseFloat(position.size || position.amount || '0') > 0
  526. const side = isLong ? 'ask' : 'bid' // 平多头用卖单,平空头用买单
  527. const payload = {
  528. account: account.config.account || '',
  529. symbol: position.symbol,
  530. amount: size.toString(),
  531. side,
  532. reduceOnly: true,
  533. slippagePercent: '1.0', // 紧急情况允许更大滑点
  534. }
  535. logger.warn('执行紧急平仓', {
  536. accountId,
  537. symbol: position.symbol,
  538. size,
  539. side,
  540. isLong,
  541. })
  542. await client.createMarketOrder(payload)
  543. logger.info('紧急平仓执行成功', {
  544. accountId,
  545. symbol: position.symbol,
  546. size,
  547. side,
  548. })
  549. } catch (error) {
  550. logger.error('紧急平仓失败', {
  551. accountId,
  552. position: position.symbol,
  553. error: error.message,
  554. })
  555. }
  556. }
  557. /**
  558. * 执行对冲订单
  559. */
  560. async executeHedgeOrder(accountId, symbol, amount, side) {
  561. const client = this.getTradingClient(accountId)
  562. const account = this.accounts.get(accountId)
  563. if (!account) {
  564. throw new Error(`账户${accountId}不存在`)
  565. }
  566. // 执行风险检查
  567. const riskCheck = await this.checkRiskLimits(accountId, symbol, amount, side)
  568. if (!riskCheck.allowed) {
  569. throw new Error(`风险控制拒绝: ${riskCheck.reason}`)
  570. }
  571. // 执行紧急止损检查
  572. const emergencyTriggered = await this.checkEmergencyStopLoss(accountId)
  573. if (emergencyTriggered) {
  574. throw new Error(`触发紧急止损,暂停交易`)
  575. }
  576. if (this.exchange === 'pacifica') {
  577. const payload = {
  578. account: account.config.account || '',
  579. symbol,
  580. amount: amount.toString(),
  581. side,
  582. reduceOnly: false,
  583. slippagePercent: '0.5', // 0.5%滑点
  584. }
  585. logger.info(`执行Pacifica对冲订单`, {
  586. accountId,
  587. symbol,
  588. amount,
  589. side,
  590. proxy: !!account.proxyConfig,
  591. })
  592. // 使用市价单确保成交
  593. const result = await client.createMarketOrder(payload)
  594. logger.info(`Pacifica对冲订单执行成功`, {
  595. accountId,
  596. symbol,
  597. side,
  598. orderId: result.orderId || result.order_id,
  599. success: result.success,
  600. })
  601. return result
  602. } else {
  603. throw new Error(`交易所${this.exchange}的下单功能暂未实现`)
  604. }
  605. }
  606. /**
  607. * 批量执行对冲交易
  608. */
  609. async executeBatchHedge(orders) {
  610. const results = []
  611. logger.info(`开始批量对冲执行`, {
  612. orderCount: orders.length,
  613. exchange: this.exchange,
  614. })
  615. for (const order of orders) {
  616. try {
  617. const result = await this.executeHedgeOrder(order.accountId, order.symbol, order.amount, order.side)
  618. results.push({
  619. success: true,
  620. orderId: result.orderId || result.order_id,
  621. })
  622. } catch (error) {
  623. logger.error(`批量对冲订单执行失败`, {
  624. accountId: order.accountId,
  625. symbol: order.symbol,
  626. error: error.message,
  627. })
  628. results.push({
  629. success: false,
  630. error: error.message,
  631. })
  632. }
  633. }
  634. logger.info(`批量对冲执行完成`, {
  635. total: orders.length,
  636. successful: results.filter(r => r.success).length,
  637. failed: results.filter(r => !r.success).length,
  638. exchange: this.exchange,
  639. })
  640. return results
  641. }
  642. /**
  643. * 获取所有对冲对状态
  644. */
  645. getHedgePairStatuses() {
  646. const statuses = []
  647. for (const [pairId, pair] of this.hedgePairs) {
  648. const netExposure = this.calculateNetExposure(pairId)
  649. statuses.push({
  650. pairId,
  651. symbol: pair.symbol,
  652. longAccount: pair.longAccountId,
  653. shortAccount: pair.shortAccountId,
  654. longPosition: pair.currentLongPosition,
  655. shortPosition: pair.currentShortPosition,
  656. netExposure,
  657. targetRatio: pair.targetRatio,
  658. isActive: pair.isActive,
  659. lastRebalance: pair.lastRebalance,
  660. exchange: this.exchange,
  661. })
  662. }
  663. return statuses
  664. }
  665. /**
  666. * 停用对冲对
  667. */
  668. deactivateHedgePair(pairId) {
  669. const pair = this.hedgePairs.get(pairId)
  670. if (pair) {
  671. pair.isActive = false
  672. logger.info(`停用对冲对`, { pairId, exchange: this.exchange })
  673. }
  674. }
  675. /**
  676. * 激活对冲对
  677. */
  678. activateHedgePair(pairId) {
  679. const pair = this.hedgePairs.get(pairId)
  680. if (pair) {
  681. pair.isActive = true
  682. logger.info(`激活对冲对`, { pairId, exchange: this.exchange })
  683. }
  684. }
  685. }