stopLossManager.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  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 StopLossManager extends EventEmitter {
  9. private stopOrders: Map<string, StopOrder> = new Map()
  10. private clients: Map<string, PacificaProxyClient> = new Map()
  11. private config: StopLossConfig
  12. private isActive: boolean = false
  13. private monitoringInterval?: NodeJS.Timeout
  14. constructor(config?: Partial<StopLossConfig>) {
  15. super()
  16. this.config = {
  17. checkIntervalMs: config?.checkIntervalMs || 2000, // 2秒检查间隔
  18. defaultStopLossPercent: config?.defaultStopLossPercent || 0.01, // 1% 止损
  19. defaultTakeProfitPercent: config?.defaultTakeProfitPercent || 0.02, // 2% 止盈
  20. trailingStopPercent: config?.trailingStopPercent || 0.005, // 0.5% 追踪止损
  21. partialClosePercent: config?.partialClosePercent || 0.5, // 50% 部分平仓
  22. enableBreakEvenStop: config?.enableBreakEvenStop || true, // 启用盈亏平衡止损
  23. breakEvenTriggerPercent: config?.breakEvenTriggerPercent || 0.01, // 1% 触发盈亏平衡
  24. enableScaleOut: config?.enableScaleOut || true, // 启用分批止盈
  25. scaleOutLevels: config?.scaleOutLevels || [0.01, 0.02, 0.03], // 分批止盈点位
  26. maxSlippagePercent: config?.maxSlippagePercent || 0.01, // 最大滑点
  27. }
  28. }
  29. /**
  30. * 注册交易客户端
  31. */
  32. registerClient(account: string, client: PacificaProxyClient): void {
  33. this.clients.set(account, client)
  34. logger.info('注册止损管理客户端', { account })
  35. }
  36. /**
  37. * 为仓位设置止盈止损
  38. */
  39. async setStopLossAndTakeProfit(
  40. account: string,
  41. symbol: string,
  42. positionSize: number,
  43. entryPrice: number,
  44. isLong: boolean,
  45. customConfig?: Partial<StopOrderConfig>,
  46. ): Promise<string> {
  47. const orderId = this.generateOrderId(account, symbol)
  48. const stopOrder: StopOrder = {
  49. id: orderId,
  50. account,
  51. symbol,
  52. originalSize: Math.abs(positionSize),
  53. remainingSize: Math.abs(positionSize),
  54. entryPrice,
  55. isLong,
  56. status: 'active',
  57. createdAt: Date.now(),
  58. lastUpdate: Date.now(),
  59. config: {
  60. stopLossPercent: customConfig?.stopLossPercent || this.config.defaultStopLossPercent,
  61. takeProfitPercent: customConfig?.takeProfitPercent || this.config.defaultTakeProfitPercent,
  62. enableTrailingStop: customConfig?.enableTrailingStop || false,
  63. trailingStopPercent: customConfig?.trailingStopPercent || this.config.trailingStopPercent,
  64. enablePartialClose: customConfig?.enablePartialClose || false,
  65. partialClosePercent: customConfig?.partialClosePercent || this.config.partialClosePercent,
  66. stopType: customConfig?.stopType || 'fixed',
  67. },
  68. trailingStopPrice: null,
  69. highestPrice: entryPrice,
  70. lowestPrice: entryPrice,
  71. executedOrders: [],
  72. partialCloseCount: 0,
  73. }
  74. // 计算初始止损止盈价格
  75. this.updateStopPrices(stopOrder, entryPrice)
  76. this.stopOrders.set(orderId, stopOrder)
  77. logger.info('设置止盈止损', {
  78. orderId,
  79. account,
  80. symbol,
  81. positionSize,
  82. entryPrice,
  83. isLong,
  84. stopLossPrice: stopOrder.stopLossPrice,
  85. takeProfitPrice: stopOrder.takeProfitPrice,
  86. })
  87. this.emit('stopOrderCreated', stopOrder)
  88. return orderId
  89. }
  90. /**
  91. * 启动止损监控
  92. */
  93. startMonitoring(): void {
  94. if (this.isActive) return
  95. this.isActive = true
  96. this.monitoringInterval = setInterval(() => {
  97. this.performStopChecks()
  98. }, this.config.checkIntervalMs)
  99. logger.info('止损监控已启动', {
  100. interval: this.config.checkIntervalMs,
  101. activeOrders: this.stopOrders.size,
  102. })
  103. this.emit('monitoringStarted')
  104. }
  105. /**
  106. * 停止止损监控
  107. */
  108. stopMonitoring(): void {
  109. if (!this.isActive) return
  110. this.isActive = false
  111. if (this.monitoringInterval) {
  112. clearInterval(this.monitoringInterval)
  113. this.monitoringInterval = undefined
  114. }
  115. logger.info('止损监控已停止')
  116. this.emit('monitoringStopped')
  117. }
  118. /**
  119. * 执行止损检查
  120. */
  121. private async performStopChecks(): Promise<void> {
  122. const activeOrders = Array.from(this.stopOrders.values()).filter(order => order.status === 'active')
  123. for (const order of activeOrders) {
  124. try {
  125. await this.checkStopOrder(order)
  126. } catch (error: any) {
  127. logger.error('止损检查失败', {
  128. orderId: order.id,
  129. account: order.account,
  130. symbol: order.symbol,
  131. error: error.message,
  132. })
  133. }
  134. }
  135. }
  136. /**
  137. * 检查单个止损订单
  138. */
  139. private async checkStopOrder(order: StopOrder): Promise<void> {
  140. const client = this.clients.get(order.account)
  141. if (!client) {
  142. logger.warn('未找到交易客户端', { account: order.account })
  143. return
  144. }
  145. // 获取当前价格
  146. const currentPrice = await this.getCurrentPrice(client, order.symbol)
  147. if (!currentPrice) return
  148. // 更新价格追踪
  149. this.updatePriceTracking(order, currentPrice)
  150. // 检查是否触发止损或止盈
  151. const triggerResult = this.checkTriggers(order, currentPrice)
  152. if (triggerResult.shouldTrigger) {
  153. await this.executeTrigger(order, triggerResult, currentPrice)
  154. }
  155. order.lastUpdate = Date.now()
  156. }
  157. /**
  158. * 更新价格追踪
  159. */
  160. private updatePriceTracking(order: StopOrder, currentPrice: number): void {
  161. // 更新最高价和最低价
  162. if (currentPrice > order.highestPrice) {
  163. order.highestPrice = currentPrice
  164. }
  165. if (currentPrice < order.lowestPrice) {
  166. order.lowestPrice = currentPrice
  167. }
  168. // 更新追踪止损价格
  169. if (order.config.enableTrailingStop) {
  170. this.updateTrailingStop(order, currentPrice)
  171. }
  172. // 更新盈亏平衡止损
  173. if (this.config.enableBreakEvenStop) {
  174. this.updateBreakEvenStop(order, currentPrice)
  175. }
  176. // 更新止损止盈价格
  177. this.updateStopPrices(order, currentPrice)
  178. }
  179. /**
  180. * 更新追踪止损
  181. */
  182. private updateTrailingStop(order: StopOrder, currentPrice: number): void {
  183. if (!order.config.enableTrailingStop) return
  184. const trailingPercent = order.config.trailingStopPercent || this.config.trailingStopPercent
  185. if (order.isLong) {
  186. // 多头:追踪止损价格跟随最高价下移
  187. const newTrailingStop = order.highestPrice * (1 - trailingPercent)
  188. if (!order.trailingStopPrice || newTrailingStop > order.trailingStopPrice) {
  189. order.trailingStopPrice = newTrailingStop
  190. logger.debug('更新追踪止损', {
  191. orderId: order.id,
  192. newTrailingStop,
  193. highestPrice: order.highestPrice,
  194. currentPrice,
  195. })
  196. }
  197. } else {
  198. // 空头:追踪止损价格跟随最低价上移
  199. const newTrailingStop = order.lowestPrice * (1 + trailingPercent)
  200. if (!order.trailingStopPrice || newTrailingStop < order.trailingStopPrice) {
  201. order.trailingStopPrice = newTrailingStop
  202. logger.debug('更新追踪止损', {
  203. orderId: order.id,
  204. newTrailingStop,
  205. lowestPrice: order.lowestPrice,
  206. currentPrice,
  207. })
  208. }
  209. }
  210. }
  211. /**
  212. * 更新盈亏平衡止损
  213. */
  214. private updateBreakEvenStop(order: StopOrder, currentPrice: number): void {
  215. const triggerPercent = this.config.breakEvenTriggerPercent
  216. const profitPercent = order.isLong
  217. ? (currentPrice - order.entryPrice) / order.entryPrice
  218. : (order.entryPrice - currentPrice) / order.entryPrice
  219. // 如果盈利超过触发阈值,将止损移动到盈亏平衡点
  220. if (profitPercent >= triggerPercent && order.config.stopType === 'fixed') {
  221. order.config.stopType = 'break_even'
  222. logger.info('激活盈亏平衡止损', {
  223. orderId: order.id,
  224. profitPercent: profitPercent.toFixed(4),
  225. entryPrice: order.entryPrice,
  226. currentPrice,
  227. })
  228. }
  229. }
  230. /**
  231. * 更新止损止盈价格
  232. */
  233. private updateStopPrices(order: StopOrder, referencePrice?: number): void {
  234. const price = referencePrice || order.entryPrice
  235. if (order.config.stopType === 'break_even') {
  236. // 盈亏平衡止损
  237. order.stopLossPrice = order.entryPrice
  238. } else if (order.config.enableTrailingStop && order.trailingStopPrice) {
  239. // 追踪止损
  240. order.stopLossPrice = order.trailingStopPrice
  241. } else {
  242. // 固定止损
  243. const stopLossPercent = order.config.stopLossPercent
  244. order.stopLossPrice = order.isLong
  245. ? order.entryPrice * (1 - stopLossPercent)
  246. : order.entryPrice * (1 + stopLossPercent)
  247. }
  248. // 止盈价格
  249. const takeProfitPercent = order.config.takeProfitPercent
  250. order.takeProfitPrice = order.isLong
  251. ? order.entryPrice * (1 + takeProfitPercent)
  252. : order.entryPrice * (1 - takeProfitPercent)
  253. }
  254. /**
  255. * 检查触发条件
  256. */
  257. private checkTriggers(order: StopOrder, currentPrice: number): TriggerResult {
  258. // 检查止损
  259. if (order.stopLossPrice) {
  260. const stopLossTriggered = order.isLong ? currentPrice <= order.stopLossPrice : currentPrice >= order.stopLossPrice
  261. if (stopLossTriggered) {
  262. return {
  263. shouldTrigger: true,
  264. triggerType: 'stop_loss',
  265. triggerPrice: order.stopLossPrice,
  266. closePercent: 1.0, // 全部平仓
  267. }
  268. }
  269. }
  270. // 检查止盈
  271. if (order.takeProfitPrice) {
  272. const takeProfitTriggered = order.isLong
  273. ? currentPrice >= order.takeProfitPrice
  274. : currentPrice <= order.takeProfitPrice
  275. if (takeProfitTriggered) {
  276. const closePercent = order.config.enablePartialClose
  277. ? order.config.partialClosePercent || this.config.partialClosePercent
  278. : 1.0
  279. return {
  280. shouldTrigger: true,
  281. triggerType: 'take_profit',
  282. triggerPrice: order.takeProfitPrice,
  283. closePercent,
  284. }
  285. }
  286. }
  287. // 检查分批止盈
  288. if (this.config.enableScaleOut) {
  289. const scaleOutTrigger = this.checkScaleOutTriggers(order, currentPrice)
  290. if (scaleOutTrigger.shouldTrigger) {
  291. return scaleOutTrigger
  292. }
  293. }
  294. return { shouldTrigger: false }
  295. }
  296. /**
  297. * 检查分批止盈触发
  298. */
  299. private checkScaleOutTriggers(order: StopOrder, currentPrice: number): TriggerResult {
  300. const scaleOutLevels = this.config.scaleOutLevels
  301. for (let i = 0; i < scaleOutLevels.length; i++) {
  302. const level = scaleOutLevels[i]
  303. const targetPrice = order.isLong ? order.entryPrice * (1 + level) : order.entryPrice * (1 - level)
  304. const triggered = order.isLong ? currentPrice >= targetPrice : currentPrice <= targetPrice
  305. if (triggered && !order.scaleOutExecuted?.includes(i)) {
  306. if (!order.scaleOutExecuted) {
  307. order.scaleOutExecuted = []
  308. }
  309. return {
  310. shouldTrigger: true,
  311. triggerType: 'scale_out',
  312. triggerPrice: targetPrice,
  313. closePercent: 0.25, // 25% 分批平仓
  314. scaleOutLevel: i,
  315. }
  316. }
  317. }
  318. return { shouldTrigger: false }
  319. }
  320. /**
  321. * 执行触发操作
  322. */
  323. private async executeTrigger(order: StopOrder, trigger: TriggerResult, currentPrice: number): Promise<void> {
  324. const client = this.clients.get(order.account)
  325. if (!client) return
  326. const closeSize = order.remainingSize * trigger.closePercent
  327. const side = order.isLong ? 'ask' : 'bid' // 平仓方向
  328. try {
  329. const payload: OrderCreatePayload = {
  330. account: order.account,
  331. symbol: order.symbol,
  332. amount: closeSize.toString(),
  333. side,
  334. reduceOnly: true,
  335. slippagePercent: (this.config.maxSlippagePercent * 100).toString(),
  336. }
  337. let result
  338. if (trigger.triggerType === 'stop_loss') {
  339. // 止损使用市价单确保成交
  340. result = await client.createMarketOrder(payload)
  341. } else {
  342. // 止盈可以使用限价单获得更好价格
  343. payload.orderType = 'limit'
  344. payload.price = trigger.triggerPrice?.toString()
  345. result = await client.createLimitOrder(payload)
  346. }
  347. // 记录执行结果
  348. const execution: StopExecution = {
  349. orderId: result.orderId || result.order_id || '',
  350. triggerType: trigger.triggerType,
  351. executedSize: closeSize,
  352. executedPrice: currentPrice,
  353. timestamp: Date.now(),
  354. scaleOutLevel: trigger.scaleOutLevel,
  355. }
  356. order.executedOrders.push(execution)
  357. order.remainingSize -= closeSize
  358. // 更新分批执行状态
  359. if (trigger.triggerType === 'scale_out' && trigger.scaleOutLevel !== undefined) {
  360. if (!order.scaleOutExecuted) {
  361. order.scaleOutExecuted = []
  362. }
  363. order.scaleOutExecuted.push(trigger.scaleOutLevel)
  364. order.partialCloseCount++
  365. }
  366. // 检查是否完全平仓
  367. if (order.remainingSize <= 0.0001) {
  368. order.status = 'completed'
  369. } else if (trigger.triggerType === 'take_profit' && order.config.enablePartialClose) {
  370. // 部分止盈后,调整剩余止盈价格
  371. this.adjustRemainingTakeProfit(order)
  372. }
  373. logger.info('触发执行成功', {
  374. orderId: order.id,
  375. triggerType: trigger.triggerType,
  376. executedSize: closeSize,
  377. remainingSize: order.remainingSize,
  378. executedPrice: currentPrice,
  379. resultOrderId: result.orderId || result.order_id,
  380. })
  381. this.emit('stopTriggered', {
  382. order,
  383. execution,
  384. trigger,
  385. currentPrice,
  386. })
  387. } catch (error: any) {
  388. logger.error('触发执行失败', {
  389. orderId: order.id,
  390. triggerType: trigger.triggerType,
  391. error: error.message,
  392. })
  393. this.emit('stopTriggerFailed', {
  394. order,
  395. trigger,
  396. error: error.message,
  397. })
  398. }
  399. }
  400. /**
  401. * 调整剩余止盈价格
  402. */
  403. private adjustRemainingTakeProfit(order: StopOrder): void {
  404. // 部分止盈后,可以将剩余仓位的止盈点位适当调高
  405. const adjustmentFactor = 1.5 // 调高50%
  406. const originalPercent = order.config.takeProfitPercent
  407. const newPercent = originalPercent * adjustmentFactor
  408. order.takeProfitPrice = order.isLong ? order.entryPrice * (1 + newPercent) : order.entryPrice * (1 - newPercent)
  409. logger.info('调整剩余止盈价格', {
  410. orderId: order.id,
  411. newTakeProfitPrice: order.takeProfitPrice,
  412. adjustmentFactor,
  413. })
  414. }
  415. /**
  416. * 获取当前价格
  417. */
  418. private async getCurrentPrice(client: PacificaProxyClient, symbol: string): Promise<number | null> {
  419. try {
  420. const ticker = await client.getTicker(symbol)
  421. return ticker?.price || null
  422. } catch (error: any) {
  423. logger.error('获取当前价格失败', {
  424. symbol,
  425. error: error.message,
  426. })
  427. return null
  428. }
  429. }
  430. /**
  431. * 生成订单ID
  432. */
  433. private generateOrderId(account: string, symbol: string): string {
  434. return `stop_${account}_${symbol}_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`
  435. }
  436. /**
  437. * 取消止损订单
  438. */
  439. cancelStopOrder(orderId: string): boolean {
  440. const order = this.stopOrders.get(orderId)
  441. if (!order) return false
  442. order.status = 'cancelled'
  443. logger.info('取消止损订单', { orderId })
  444. this.emit('stopOrderCancelled', order)
  445. return true
  446. }
  447. /**
  448. * 获取活跃止损订单
  449. */
  450. getActiveStopOrders(account?: string): StopOrder[] {
  451. const orders = Array.from(this.stopOrders.values()).filter(order => order.status === 'active')
  452. return account ? orders.filter(order => order.account === account) : orders
  453. }
  454. /**
  455. * 获取止损统计
  456. */
  457. getStopOrderStats(): StopOrderStats {
  458. const allOrders = Array.from(this.stopOrders.values())
  459. return {
  460. totalOrders: allOrders.length,
  461. activeOrders: allOrders.filter(o => o.status === 'active').length,
  462. completedOrders: allOrders.filter(o => o.status === 'completed').length,
  463. cancelledOrders: allOrders.filter(o => o.status === 'cancelled').length,
  464. totalExecutions: allOrders.reduce((sum, o) => sum + o.executedOrders.length, 0),
  465. successfulStopLosses: allOrders.filter(o => o.executedOrders.some(e => e.triggerType === 'stop_loss')).length,
  466. successfulTakeProfits: allOrders.filter(o => o.executedOrders.some(e => e.triggerType === 'take_profit')).length,
  467. }
  468. }
  469. }
  470. // 类型定义
  471. export interface StopOrder {
  472. id: string
  473. account: string
  474. symbol: string
  475. originalSize: number
  476. remainingSize: number
  477. entryPrice: number
  478. isLong: boolean
  479. status: 'active' | 'completed' | 'cancelled'
  480. createdAt: number
  481. lastUpdate: number
  482. config: StopOrderConfig
  483. stopLossPrice?: number
  484. takeProfitPrice?: number
  485. trailingStopPrice?: number | null
  486. highestPrice: number
  487. lowestPrice: number
  488. executedOrders: StopExecution[]
  489. partialCloseCount: number
  490. scaleOutExecuted?: number[]
  491. }
  492. export interface StopOrderConfig {
  493. stopLossPercent: number
  494. takeProfitPercent: number
  495. enableTrailingStop: boolean
  496. trailingStopPercent: number
  497. enablePartialClose: boolean
  498. partialClosePercent: number
  499. stopType: 'fixed' | 'trailing' | 'break_even'
  500. }
  501. export interface StopLossConfig {
  502. checkIntervalMs: number
  503. defaultStopLossPercent: number
  504. defaultTakeProfitPercent: number
  505. trailingStopPercent: number
  506. partialClosePercent: number
  507. enableBreakEvenStop: boolean
  508. breakEvenTriggerPercent: number
  509. enableScaleOut: boolean
  510. scaleOutLevels: number[]
  511. maxSlippagePercent: number
  512. }
  513. export interface TriggerResult {
  514. shouldTrigger: boolean
  515. triggerType?: 'stop_loss' | 'take_profit' | 'scale_out'
  516. triggerPrice?: number
  517. closePercent?: number
  518. scaleOutLevel?: number
  519. }
  520. export interface StopExecution {
  521. orderId: string
  522. triggerType: 'stop_loss' | 'take_profit' | 'scale_out'
  523. executedSize: number
  524. executedPrice: number
  525. timestamp: number
  526. scaleOutLevel?: number
  527. }
  528. export interface StopOrderStats {
  529. totalOrders: number
  530. activeOrders: number
  531. completedOrders: number
  532. cancelledOrders: number
  533. totalExecutions: number
  534. successfulStopLosses: number
  535. successfulTakeProfits: number
  536. }
  537. export const stopLossManager = new StopLossManager()