priceConvergenceManager.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. import { EventEmitter } from 'events'
  2. import { logger } from '../../utils/logger.js'
  3. import { PacificaProxyClient } from '../../exchanges/pacifica/PacificaProxyClient.js'
  4. import { OrderCreatePayload } from '../../exchanges/pacifica/OrdersAdapter.js'
  5. /**
  6. * 价格收敛管理器 - 管理两个账户的开仓价格逐步接近,并自动设置止盈止损
  7. */
  8. export class PriceConvergenceManager extends EventEmitter {
  9. private accountPairs: Map<string, AccountPair> = new Map()
  10. private clients: Map<string, PacificaProxyClient> = new Map()
  11. private config: ConvergenceConfig
  12. private isActive: boolean = false
  13. private monitoringInterval?: NodeJS.Timeout
  14. constructor(config?: Partial<ConvergenceConfig>) {
  15. super()
  16. this.config = {
  17. maxPriceDeviation: config?.maxPriceDeviation || 0.005, // 0.5% 最大价格偏差
  18. convergenceStepPercent: config?.convergenceStepPercent || 0.001, // 0.1% 收敛步长
  19. minOrderSize: config?.minOrderSize || 0.001, // 最小订单量
  20. maxOrderSize: config?.maxOrderSize || 1.0, // 最大订单量
  21. checkIntervalMs: config?.checkIntervalMs || 3000, // 3秒检查间隔
  22. takeProfitPercent: config?.takeProfitPercent || 0.02, // 2% 止盈
  23. stopLossPercent: config?.stopLossPercent || 0.01, // 1% 止损
  24. maxDailyOrders: config?.maxDailyOrders || 50, // 每日最大订单数
  25. enableTrailingStop: config?.enableTrailingStop || false, // 启用追踪止损
  26. trailingStopPercent: config?.trailingStopPercent || 0.005, // 0.5% 追踪止损
  27. }
  28. }
  29. /**
  30. * 添加账户对
  31. */
  32. addAccountPair(pairId: string, account1Config: AccountConfig, account2Config: AccountConfig, symbol: string): void {
  33. // 创建交易客户端
  34. const client1 = new PacificaProxyClient({
  35. account: account1Config.account,
  36. privateKey: account1Config.privateKey,
  37. agentWallet: account1Config.agentWallet,
  38. agentPrivateKey: account1Config.agentPrivateKey,
  39. })
  40. const client2 = new PacificaProxyClient({
  41. account: account2Config.account,
  42. privateKey: account2Config.privateKey,
  43. agentWallet: account2Config.agentWallet,
  44. agentPrivateKey: account2Config.agentPrivateKey,
  45. })
  46. this.clients.set(account1Config.account, client1)
  47. this.clients.set(account2Config.account, client2)
  48. const accountPair: AccountPair = {
  49. id: pairId,
  50. account1: account1Config,
  51. account2: account2Config,
  52. symbol,
  53. account1Position: null,
  54. account2Position: null,
  55. targetPriceDeviation: 0,
  56. currentPriceDeviation: 0,
  57. lastUpdate: 0,
  58. dailyOrderCount: 0,
  59. lastOrderDate: '',
  60. isConverged: false,
  61. stopLossOrders: new Map(),
  62. takeProfitOrders: new Map(),
  63. }
  64. this.accountPairs.set(pairId, accountPair)
  65. logger.info('添加账户对', {
  66. pairId,
  67. account1: account1Config.account,
  68. account2: account2Config.account,
  69. symbol,
  70. })
  71. }
  72. /**
  73. * 开始价格收敛监控
  74. */
  75. startConvergenceMonitoring(): void {
  76. if (this.isActive) return
  77. this.isActive = true
  78. this.monitoringInterval = setInterval(() => {
  79. this.performConvergenceCheck()
  80. }, this.config.checkIntervalMs)
  81. logger.info('价格收敛监控已启动', {
  82. interval: this.config.checkIntervalMs,
  83. pairs: Array.from(this.accountPairs.keys()),
  84. })
  85. this.emit('convergenceMonitoringStarted')
  86. }
  87. /**
  88. * 停止价格收敛监控
  89. */
  90. stopConvergenceMonitoring(): void {
  91. if (!this.isActive) return
  92. this.isActive = false
  93. if (this.monitoringInterval) {
  94. clearInterval(this.monitoringInterval)
  95. this.monitoringInterval = undefined
  96. }
  97. logger.info('价格收敛监控已停止')
  98. this.emit('convergenceMonitoringStopped')
  99. }
  100. /**
  101. * 执行收敛检查
  102. */
  103. private async performConvergenceCheck(): Promise<void> {
  104. for (const [pairId, pair] of this.accountPairs) {
  105. try {
  106. await this.updatePositions(pair)
  107. await this.checkAndExecuteConvergence(pair)
  108. } catch (error: any) {
  109. logger.error('收敛检查失败', {
  110. pairId,
  111. error: error.message,
  112. })
  113. }
  114. }
  115. }
  116. /**
  117. * 更新账户仓位信息
  118. */
  119. private async updatePositions(pair: AccountPair): Promise<void> {
  120. try {
  121. const client1 = this.clients.get(pair.account1.account)!
  122. const client2 = this.clients.get(pair.account2.account)!
  123. const [positions1, positions2] = await Promise.all([client1.getPositions(), client2.getPositions()])
  124. // 查找目标交易对的仓位
  125. pair.account1Position = this.findPositionBySymbol(positions1, pair.symbol)
  126. pair.account2Position = this.findPositionBySymbol(positions2, pair.symbol)
  127. pair.lastUpdate = Date.now()
  128. // 计算当前价格偏差
  129. if (pair.account1Position && pair.account2Position) {
  130. const price1 = pair.account1Position.averagePrice || pair.account1Position.entryPrice
  131. const price2 = pair.account2Position.averagePrice || pair.account2Position.entryPrice
  132. if (price1 && price2) {
  133. pair.currentPriceDeviation = Math.abs(price1 - price2) / ((price1 + price2) / 2)
  134. pair.isConverged = pair.currentPriceDeviation <= this.config.maxPriceDeviation
  135. }
  136. }
  137. logger.debug('更新仓位信息', {
  138. pairId: pair.id,
  139. account1Position: pair.account1Position ? 'Found' : 'None',
  140. account2Position: pair.account2Position ? 'Found' : 'None',
  141. currentDeviation: pair.currentPriceDeviation.toFixed(4),
  142. isConverged: pair.isConverged,
  143. })
  144. } catch (error: any) {
  145. logger.error('更新仓位信息失败', {
  146. pairId: pair.id,
  147. error: error.message,
  148. })
  149. }
  150. }
  151. /**
  152. * 检查并执行收敛操作
  153. */
  154. private async checkAndExecuteConvergence(pair: AccountPair): Promise<void> {
  155. // 检查是否需要收敛
  156. if (pair.isConverged) {
  157. return // 已收敛,无需操作
  158. }
  159. // 检查每日订单限制
  160. const today = new Date().toDateString()
  161. if (pair.lastOrderDate !== today) {
  162. pair.dailyOrderCount = 0
  163. pair.lastOrderDate = today
  164. }
  165. if (pair.dailyOrderCount >= this.config.maxDailyOrders) {
  166. logger.warn('达到每日订单限制', {
  167. pairId: pair.id,
  168. dailyCount: pair.dailyOrderCount,
  169. maxDaily: this.config.maxDailyOrders,
  170. })
  171. return
  172. }
  173. // 执行收敛交易
  174. await this.executeConvergenceTrade(pair)
  175. }
  176. /**
  177. * 执行收敛交易
  178. */
  179. private async executeConvergenceTrade(pair: AccountPair): Promise<void> {
  180. try {
  181. // 获取当前市场价格
  182. const marketPrice = await this.getCurrentMarketPrice(pair.symbol)
  183. if (!marketPrice) {
  184. logger.warn('无法获取市场价格', { symbol: pair.symbol })
  185. return
  186. }
  187. // 计算收敛交易参数
  188. const tradeParams = this.calculateConvergenceTradeParams(pair, marketPrice)
  189. if (!tradeParams) {
  190. logger.debug('无需收敛交易', { pairId: pair.id })
  191. return
  192. }
  193. // 执行交易
  194. const results = await this.executeTrades(pair, tradeParams, marketPrice)
  195. // 设置止盈止损
  196. if (results.some(r => r.success)) {
  197. await this.setStopLossAndTakeProfit(pair, marketPrice)
  198. }
  199. // 更新统计
  200. pair.dailyOrderCount += results.filter(r => r.success).length
  201. logger.info('收敛交易执行完成', {
  202. pairId: pair.id,
  203. successful: results.filter(r => r.success).length,
  204. failed: results.filter(r => !r.success).length,
  205. marketPrice,
  206. })
  207. this.emit('convergenceTradeExecuted', {
  208. pairId: pair.id,
  209. results,
  210. tradeParams,
  211. marketPrice,
  212. })
  213. } catch (error: any) {
  214. logger.error('收敛交易执行失败', {
  215. pairId: pair.id,
  216. error: error.message,
  217. })
  218. }
  219. }
  220. /**
  221. * 计算收敛交易参数
  222. */
  223. private calculateConvergenceTradeParams(pair: AccountPair, marketPrice: number): ConvergenceTradeParams | null {
  224. const pos1 = pair.account1Position
  225. const pos2 = pair.account2Position
  226. // 如果都没有仓位,创建初始仓位
  227. if (!pos1 && !pos2) {
  228. const orderSize = this.config.minOrderSize
  229. return {
  230. account1Trade: {
  231. action: 'open_long',
  232. size: orderSize,
  233. expectedPrice: marketPrice * (1 - this.config.convergenceStepPercent),
  234. },
  235. account2Trade: {
  236. action: 'open_short',
  237. size: orderSize,
  238. expectedPrice: marketPrice * (1 + this.config.convergenceStepPercent),
  239. },
  240. convergenceType: 'initial_hedge',
  241. }
  242. }
  243. // 如果价格偏差过大,执行收敛交易
  244. if (pair.currentPriceDeviation > this.config.maxPriceDeviation) {
  245. if (pos1 && pos2) {
  246. const price1 = pos1.averagePrice || pos1.entryPrice
  247. const price2 = pos2.averagePrice || pos2.entryPrice
  248. if (!price1 || !price2) return null
  249. // 计算收敛方向:价格高的账户减仓,价格低的账户加仓
  250. const adjustmentSize = Math.min(
  251. this.config.maxOrderSize,
  252. Math.max(this.config.minOrderSize, Math.abs(pos1.size - pos2.size) * 0.1),
  253. )
  254. if (price1 > price2) {
  255. return {
  256. account1Trade: {
  257. action: pos1.size > 0 ? 'reduce_long' : 'reduce_short',
  258. size: adjustmentSize,
  259. expectedPrice: marketPrice,
  260. },
  261. account2Trade: {
  262. action: pos2.size > 0 ? 'increase_long' : 'increase_short',
  263. size: adjustmentSize,
  264. expectedPrice: marketPrice,
  265. },
  266. convergenceType: 'price_adjustment',
  267. }
  268. } else {
  269. return {
  270. account1Trade: {
  271. action: pos1.size > 0 ? 'increase_long' : 'increase_short',
  272. size: adjustmentSize,
  273. expectedPrice: marketPrice,
  274. },
  275. account2Trade: {
  276. action: pos2.size > 0 ? 'reduce_long' : 'reduce_short',
  277. size: adjustmentSize,
  278. expectedPrice: marketPrice,
  279. },
  280. convergenceType: 'price_adjustment',
  281. }
  282. }
  283. }
  284. }
  285. return null
  286. }
  287. /**
  288. * 执行交易
  289. */
  290. private async executeTrades(
  291. pair: AccountPair,
  292. tradeParams: ConvergenceTradeParams,
  293. marketPrice: number,
  294. ): Promise<TradeResult[]> {
  295. const results: TradeResult[] = []
  296. // 执行账户1的交易
  297. if (tradeParams.account1Trade) {
  298. try {
  299. const result = await this.executeAccountTrade(
  300. pair.account1,
  301. tradeParams.account1Trade,
  302. pair.symbol,
  303. marketPrice,
  304. )
  305. results.push({ account: pair.account1.account, ...result })
  306. } catch (error: any) {
  307. results.push({
  308. account: pair.account1.account,
  309. success: false,
  310. error: error.message,
  311. })
  312. }
  313. }
  314. // 执行账户2的交易
  315. if (tradeParams.account2Trade) {
  316. try {
  317. const result = await this.executeAccountTrade(
  318. pair.account2,
  319. tradeParams.account2Trade,
  320. pair.symbol,
  321. marketPrice,
  322. )
  323. results.push({ account: pair.account2.account, ...result })
  324. } catch (error: any) {
  325. results.push({
  326. account: pair.account2.account,
  327. success: false,
  328. error: error.message,
  329. })
  330. }
  331. }
  332. return results
  333. }
  334. /**
  335. * 执行单个账户的交易
  336. */
  337. private async executeAccountTrade(
  338. account: AccountConfig,
  339. trade: TradeAction,
  340. symbol: string,
  341. marketPrice: number,
  342. ): Promise<Omit<TradeResult, 'account'>> {
  343. const client = this.clients.get(account.account)!
  344. let side: 'bid' | 'ask'
  345. let reduceOnly = false
  346. switch (trade.action) {
  347. case 'open_long':
  348. case 'increase_long':
  349. side = 'bid'
  350. break
  351. case 'open_short':
  352. case 'increase_short':
  353. side = 'ask'
  354. break
  355. case 'reduce_long':
  356. side = 'ask'
  357. reduceOnly = true
  358. break
  359. case 'reduce_short':
  360. side = 'bid'
  361. reduceOnly = true
  362. break
  363. default:
  364. throw new Error(`不支持的交易动作: ${trade.action}`)
  365. }
  366. const payload: OrderCreatePayload = {
  367. account: account.account,
  368. symbol,
  369. amount: trade.size.toString(),
  370. side,
  371. reduceOnly,
  372. slippagePercent: '0.5',
  373. }
  374. logger.info('执行账户交易', {
  375. account: account.account,
  376. action: trade.action,
  377. size: trade.size,
  378. side,
  379. reduceOnly,
  380. expectedPrice: trade.expectedPrice,
  381. marketPrice,
  382. })
  383. const result = await client.createMarketOrder(payload)
  384. return {
  385. success: result.success || false,
  386. orderId: result.orderId || result.order_id,
  387. executedPrice: marketPrice, // 市价单,使用市场价格
  388. executedSize: trade.size,
  389. }
  390. }
  391. /**
  392. * 设置止盈止损
  393. */
  394. private async setStopLossAndTakeProfit(pair: AccountPair, marketPrice: number): Promise<void> {
  395. try {
  396. // 为每个账户设置止盈止损
  397. await Promise.all([
  398. this.setAccountStopOrders(pair.account1, pair.symbol, marketPrice),
  399. this.setAccountStopOrders(pair.account2, pair.symbol, marketPrice),
  400. ])
  401. logger.info('止盈止损设置完成', {
  402. pairId: pair.id,
  403. marketPrice,
  404. takeProfitPercent: this.config.takeProfitPercent,
  405. stopLossPercent: this.config.stopLossPercent,
  406. })
  407. } catch (error: any) {
  408. logger.error('设置止盈止损失败', {
  409. pairId: pair.id,
  410. error: error.message,
  411. })
  412. }
  413. }
  414. /**
  415. * 为单个账户设置止盈止损单
  416. */
  417. private async setAccountStopOrders(account: AccountConfig, symbol: string, marketPrice: number): Promise<void> {
  418. const client = this.clients.get(account.account)!
  419. // 获取当前仓位
  420. const positions = await client.getPositions()
  421. const position = this.findPositionBySymbol(positions, symbol)
  422. if (!position || position.size === 0) return
  423. const isLong = position.size > 0
  424. const positionSize = Math.abs(position.size)
  425. // 计算止盈止损价格
  426. const takeProfitPrice = isLong
  427. ? marketPrice * (1 + this.config.takeProfitPercent)
  428. : marketPrice * (1 - this.config.takeProfitPercent)
  429. const stopLossPrice = isLong
  430. ? marketPrice * (1 - this.config.stopLossPercent)
  431. : marketPrice * (1 + this.config.stopLossPercent)
  432. // 设置止盈单
  433. try {
  434. const takeProfitPayload: OrderCreatePayload = {
  435. account: account.account,
  436. symbol,
  437. amount: positionSize.toString(),
  438. side: isLong ? 'ask' : 'bid',
  439. reduceOnly: true,
  440. orderType: 'limit',
  441. price: takeProfitPrice.toString(),
  442. slippagePercent: '0.1',
  443. }
  444. const takeProfitResult = await client.createLimitOrder(takeProfitPayload)
  445. logger.info('止盈单设置成功', {
  446. account: account.account,
  447. orderId: takeProfitResult.orderId,
  448. price: takeProfitPrice,
  449. size: positionSize,
  450. })
  451. } catch (error: any) {
  452. logger.error('设置止盈单失败', {
  453. account: account.account,
  454. error: error.message,
  455. })
  456. }
  457. // 设置止损单
  458. try {
  459. const stopLossPayload: OrderCreatePayload = {
  460. account: account.account,
  461. symbol,
  462. amount: positionSize.toString(),
  463. side: isLong ? 'ask' : 'bid',
  464. reduceOnly: true,
  465. orderType: 'stop_market',
  466. stopPrice: stopLossPrice.toString(),
  467. slippagePercent: '1.0', // 止损允许更大滑点
  468. }
  469. const stopLossResult = await client.createStopOrder(stopLossPayload)
  470. logger.info('止损单设置成功', {
  471. account: account.account,
  472. orderId: stopLossResult.orderId,
  473. stopPrice: stopLossPrice,
  474. size: positionSize,
  475. })
  476. } catch (error: any) {
  477. logger.error('设置止损单失败', {
  478. account: account.account,
  479. error: error.message,
  480. })
  481. }
  482. }
  483. /**
  484. * 获取当前市场价格
  485. */
  486. private async getCurrentMarketPrice(symbol: string): Promise<number | null> {
  487. try {
  488. // 使用第一个可用的客户端获取价格
  489. const client = this.clients.values().next().value as PacificaProxyClient
  490. if (!client) return null
  491. const ticker = await client.getTicker(symbol)
  492. return ticker?.price || null
  493. } catch (error: any) {
  494. logger.error('获取市场价格失败', {
  495. symbol,
  496. error: error.message,
  497. })
  498. return null
  499. }
  500. }
  501. /**
  502. * 根据交易对查找仓位
  503. */
  504. private findPositionBySymbol(positions: any[], symbol: string): Position | null {
  505. const position = positions.find(p => p.symbol === symbol || p.market === symbol)
  506. if (!position) return null
  507. return {
  508. symbol: position.symbol || position.market,
  509. size: parseFloat(position.size || position.amount || '0'),
  510. side: position.side || (parseFloat(position.size || position.amount || '0') > 0 ? 'long' : 'short'),
  511. entryPrice: parseFloat(position.entryPrice || position.avgPrice || '0'),
  512. averagePrice: parseFloat(position.averagePrice || position.avgPrice || position.entryPrice || '0'),
  513. markPrice: parseFloat(position.markPrice || position.lastPrice || '0'),
  514. unrealizedPnl: parseFloat(position.unrealizedPnl || position.pnl || '0'),
  515. }
  516. }
  517. /**
  518. * 获取账户对状态
  519. */
  520. getAccountPairStatus(pairId: string): AccountPairStatus | null {
  521. const pair = this.accountPairs.get(pairId)
  522. if (!pair) return null
  523. return {
  524. pairId: pair.id,
  525. symbol: pair.symbol,
  526. account1: pair.account1.account,
  527. account2: pair.account2.account,
  528. currentPriceDeviation: pair.currentPriceDeviation,
  529. maxAllowedDeviation: this.config.maxPriceDeviation,
  530. isConverged: pair.isConverged,
  531. dailyOrderCount: pair.dailyOrderCount,
  532. lastUpdate: pair.lastUpdate,
  533. account1Position: pair.account1Position,
  534. account2Position: pair.account2Position,
  535. }
  536. }
  537. /**
  538. * 获取所有账户对状态
  539. */
  540. getAllAccountPairStatuses(): AccountPairStatus[] {
  541. return Array.from(this.accountPairs.keys())
  542. .map(pairId => this.getAccountPairStatus(pairId)!)
  543. .filter(Boolean)
  544. }
  545. }
  546. // 类型定义
  547. export interface AccountConfig {
  548. account: string
  549. privateKey: string
  550. agentWallet?: string
  551. agentPrivateKey?: string
  552. }
  553. export interface AccountPair {
  554. id: string
  555. account1: AccountConfig
  556. account2: AccountConfig
  557. symbol: string
  558. account1Position: Position | null
  559. account2Position: Position | null
  560. targetPriceDeviation: number
  561. currentPriceDeviation: number
  562. lastUpdate: number
  563. dailyOrderCount: number
  564. lastOrderDate: string
  565. isConverged: boolean
  566. stopLossOrders: Map<string, string> // accountId -> orderId
  567. takeProfitOrders: Map<string, string> // accountId -> orderId
  568. }
  569. export interface Position {
  570. symbol: string
  571. size: number
  572. side: 'long' | 'short'
  573. entryPrice: number
  574. averagePrice: number
  575. markPrice: number
  576. unrealizedPnl: number
  577. }
  578. export interface ConvergenceConfig {
  579. maxPriceDeviation: number // 最大允许价格偏差
  580. convergenceStepPercent: number // 收敛步长百分比
  581. minOrderSize: number // 最小订单量
  582. maxOrderSize: number // 最大订单量
  583. checkIntervalMs: number // 检查间隔
  584. takeProfitPercent: number // 止盈百分比
  585. stopLossPercent: number // 止损百分比
  586. maxDailyOrders: number // 每日最大订单数
  587. enableTrailingStop: boolean // 启用追踪止损
  588. trailingStopPercent: number // 追踪止损百分比
  589. }
  590. export interface TradeAction {
  591. action: 'open_long' | 'open_short' | 'increase_long' | 'increase_short' | 'reduce_long' | 'reduce_short'
  592. size: number
  593. expectedPrice: number
  594. }
  595. export interface ConvergenceTradeParams {
  596. account1Trade?: TradeAction
  597. account2Trade?: TradeAction
  598. convergenceType: 'initial_hedge' | 'price_adjustment' | 'rebalance'
  599. }
  600. export interface TradeResult {
  601. account: string
  602. success: boolean
  603. orderId?: string
  604. executedPrice?: number
  605. executedSize?: number
  606. error?: string
  607. }
  608. export interface AccountPairStatus {
  609. pairId: string
  610. symbol: string
  611. account1: string
  612. account2: string
  613. currentPriceDeviation: number
  614. maxAllowedDeviation: number
  615. isConverged: boolean
  616. dailyOrderCount: number
  617. lastUpdate: number
  618. account1Position: Position | null
  619. account2Position: Position | null
  620. }
  621. export const priceConvergenceManager = new PriceConvergenceManager()