StopLossService.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import { logger } from '../../utils/logger.js'
  2. /**
  3. * 止盈止损服务 - 管理止损订单和价格监控
  4. */
  5. export class StopLossService {
  6. constructor(accountManager, cacheManager) {
  7. this.accountManager = accountManager
  8. this.cacheManager = cacheManager
  9. this.activeStopLossOrders = new Map()
  10. this.activeTakeProfitOrders = new Map()
  11. this.lastPriceCheck = new Map()
  12. this.monitoringIntervals = new Map()
  13. this.stopLossConfig = {
  14. defaultStopLoss: 0.015,
  15. defaultTakeProfit: 0.025,
  16. enableTrailing: true,
  17. trailingPercent: 0.005, // 0.5% 追踪止损
  18. }
  19. }
  20. async initialize() {
  21. logger.info('StopLossService初始化')
  22. }
  23. async start() {
  24. logger.info('StopLossService启动')
  25. }
  26. async stop() {
  27. logger.info('StopLossService停止')
  28. // 清理所有监控间隔
  29. this.monitoringIntervals.forEach(interval => clearInterval(interval))
  30. this.monitoringIntervals.clear()
  31. this.activeStopLossOrders.clear()
  32. this.activeTakeProfitOrders.clear()
  33. }
  34. getStatus() {
  35. return {
  36. name: 'StopLossService',
  37. status: 'running',
  38. lastUpdate: Date.now(),
  39. details: {
  40. activeStopLoss: this.activeStopLossOrders.size,
  41. activeTakeProfit: this.activeTakeProfitOrders.size,
  42. monitoringSymbols: this.monitoringIntervals.size,
  43. },
  44. }
  45. }
  46. /**
  47. * 设置止盈止损订单
  48. */
  49. async setupStopLossAndTakeProfit(params) {
  50. const {
  51. parentOrderId,
  52. symbol,
  53. amount,
  54. side,
  55. currentPrice,
  56. clientId,
  57. stopLoss,
  58. takeProfit,
  59. enableTrailing = false,
  60. } = params
  61. try {
  62. // 计算止损价格
  63. let stopLossPrice
  64. if (stopLoss) {
  65. stopLossPrice = stopLoss
  66. } else {
  67. const stopLossPercent = this.stopLossConfig.defaultStopLoss
  68. if (side === 'buy') {
  69. stopLossPrice = currentPrice * (1 - stopLossPercent)
  70. } else {
  71. stopLossPrice = currentPrice * (1 + stopLossPercent)
  72. }
  73. }
  74. // 计算止盈价格
  75. let takeProfitPrice
  76. if (takeProfit) {
  77. takeProfitPrice = takeProfit
  78. } else {
  79. const takeProfitPercent = this.stopLossConfig.defaultTakeProfit
  80. if (side === 'buy') {
  81. takeProfitPrice = currentPrice * (1 + takeProfitPercent)
  82. } else {
  83. takeProfitPrice = currentPrice * (1 - takeProfitPercent)
  84. }
  85. }
  86. // 创建止损订单
  87. if (stopLossPrice) {
  88. const stopLossOrder = {
  89. orderId: `sl_${parentOrderId}_${Date.now()}`,
  90. symbol,
  91. amount,
  92. stopPrice: stopLossPrice,
  93. side: side === 'buy' ? 'sell' : 'buy',
  94. accountId: clientId,
  95. timestamp: Date.now(),
  96. isActive: true,
  97. }
  98. this.activeStopLossOrders.set(stopLossOrder.orderId, stopLossOrder)
  99. logger.info('止损订单已设置', {
  100. orderId: stopLossOrder.orderId,
  101. stopPrice: stopLossPrice,
  102. enableTrailing,
  103. })
  104. }
  105. // 创建止盈订单
  106. if (takeProfitPrice) {
  107. const takeProfitOrder = {
  108. orderId: `tp_${parentOrderId}_${Date.now()}`,
  109. symbol,
  110. amount,
  111. targetPrice: takeProfitPrice,
  112. side: side === 'buy' ? 'sell' : 'buy',
  113. accountId: clientId,
  114. timestamp: Date.now(),
  115. isActive: true,
  116. }
  117. this.activeTakeProfitOrders.set(takeProfitOrder.orderId, takeProfitOrder)
  118. logger.info('止盈订单已设置', {
  119. orderId: takeProfitOrder.orderId,
  120. targetPrice: takeProfitPrice,
  121. })
  122. }
  123. // 启动价格监控
  124. if (stopLossPrice || takeProfitPrice) {
  125. this.startPriceMonitoring(symbol)
  126. }
  127. console.log(
  128. `🎯 止盈止损已设置: ${stopLossPrice ? `止损@$${stopLossPrice.toFixed(2)}` : ''} ${
  129. takeProfitPrice ? `止盈@$${takeProfitPrice.toFixed(2)}` : ''
  130. }`,
  131. )
  132. } catch (error) {
  133. logger.error('设置止盈止损失败', { error: error.message, params })
  134. }
  135. }
  136. /**
  137. * 启动价格监控
  138. */
  139. startPriceMonitoring(symbol) {
  140. // 避免重复启动监控
  141. if (this.monitoringIntervals.has(symbol)) {
  142. return
  143. }
  144. this.lastPriceCheck.set(symbol, { price: 0, timestamp: Date.now() })
  145. // 每5秒检查一次价格触发
  146. const monitoringInterval = setInterval(async () => {
  147. try {
  148. await this.checkStopLossAndTakeProfitTriggers(symbol)
  149. // 如果没有活跃的止盈止损订单,停止监控
  150. const hasActiveOrders =
  151. Array.from(this.activeStopLossOrders.values()).some(order => order.symbol === symbol && order.isActive) ||
  152. Array.from(this.activeTakeProfitOrders.values()).some(order => order.symbol === symbol && order.isActive)
  153. if (!hasActiveOrders) {
  154. clearInterval(monitoringInterval)
  155. this.monitoringIntervals.delete(symbol)
  156. this.lastPriceCheck.delete(symbol)
  157. logger.info(`价格监控已停止: ${symbol}`)
  158. }
  159. } catch (error) {
  160. logger.error('价格监控检查失败', { symbol, error: error.message })
  161. }
  162. }, 5000)
  163. this.monitoringIntervals.set(symbol, monitoringInterval)
  164. logger.info(`价格监控已启动: ${symbol}`)
  165. }
  166. /**
  167. * 检查止盈止损触发条件
  168. */
  169. async checkStopLossAndTakeProfitTriggers(symbol) {
  170. try {
  171. const currentPrice = await this.getCurrentPrice(symbol)
  172. // 检查止损触发
  173. for (const [orderId, stopLossOrder] of this.activeStopLossOrders) {
  174. if (!stopLossOrder.isActive || stopLossOrder.symbol !== symbol) continue
  175. const shouldTrigger =
  176. stopLossOrder.side === 'sell'
  177. ? currentPrice <= stopLossOrder.stopPrice
  178. : currentPrice >= stopLossOrder.stopPrice
  179. if (shouldTrigger) {
  180. await this.executeTrigger('stop_loss', stopLossOrder, currentPrice)
  181. }
  182. }
  183. // 检查止盈触发
  184. for (const [orderId, takeProfitOrder] of this.activeTakeProfitOrders) {
  185. if (!takeProfitOrder.isActive || takeProfitOrder.symbol !== symbol) continue
  186. const shouldTrigger =
  187. takeProfitOrder.side === 'sell'
  188. ? currentPrice >= takeProfitOrder.targetPrice
  189. : currentPrice <= takeProfitOrder.targetPrice
  190. if (shouldTrigger) {
  191. await this.executeTrigger('take_profit', takeProfitOrder, currentPrice)
  192. }
  193. }
  194. // 更新价格检查记录
  195. this.lastPriceCheck.set(symbol, { price: currentPrice, timestamp: Date.now() })
  196. } catch (error) {
  197. logger.error('检查止盈止损触发失败', { symbol, error: error.message })
  198. }
  199. }
  200. /**
  201. * 执行止盈止损触发
  202. */
  203. async executeTrigger(type, order, currentPrice) {
  204. try {
  205. const client = this.accountManager.getClient(order.accountId)
  206. if (!client) {
  207. logger.error(`客户端 ${order.accountId} 不可用,无法执行${type}`)
  208. return
  209. }
  210. const side = order.side === 'sell' ? 'ask' : 'bid'
  211. const triggerPrice = 'stopPrice' in order ? order.stopPrice : order.targetPrice
  212. logger.info(`执行${type}触发`, {
  213. orderId: order.orderId,
  214. symbol: order.symbol,
  215. triggerPrice,
  216. currentPrice,
  217. side: order.side,
  218. })
  219. // 执行平仓订单
  220. const accounts = this.accountManager.getAllAccountStates()
  221. const firstAccount = Array.from(accounts.keys())[0]
  222. const accountData = this.accountManager.getAccountState(firstAccount)
  223. if (!accountData) {
  224. logger.error('无法获取账户数据执行止损')
  225. return
  226. }
  227. const result = await client.createMarketOrder({
  228. account: accountData.toString(),
  229. symbol: 'BTCUSDT',
  230. amount: order.amount,
  231. side: side,
  232. reduceOnly: true,
  233. slippagePercent: '5.0',
  234. })
  235. if (result.success) {
  236. // 标记订单为已执行
  237. if (type === 'stop_loss') {
  238. order.isActive = false
  239. } else {
  240. order.isActive = false
  241. }
  242. const emoji = type === 'stop_loss' ? '🛑' : '🎯'
  243. const typeText = type === 'stop_loss' ? '止损' : '止盈'
  244. console.log(
  245. `${emoji} ${typeText}触发成功: ${order.symbol} @ $${currentPrice.toFixed(2)} (目标: $${triggerPrice.toFixed(
  246. 2,
  247. )})`,
  248. )
  249. // 更新账户状态
  250. this.accountManager.updateTradeState(order.accountId, {
  251. side: order.side,
  252. amount: order.amount,
  253. success: true,
  254. })
  255. } else {
  256. logger.error(`${type}执行失败`, { error: result.error, order })
  257. }
  258. } catch (error) {
  259. logger.error(`执行${type}触发失败`, { error: error.message, order })
  260. }
  261. }
  262. /**
  263. * 获取当前市场价格
  264. */
  265. async getCurrentPrice(symbol) {
  266. try {
  267. // 使用缓存的价格数据
  268. const cachedPrice = this.cacheManager.get(`price_${symbol}`, this.cacheManager.getTTL('ticker'))
  269. if (cachedPrice && typeof cachedPrice === 'number') {
  270. return cachedPrice
  271. }
  272. // 缓存未命中,使用估算价格
  273. const estimatedPrice = symbol.includes('BTC') ? 65000 : 3500
  274. this.cacheManager.set(`price_${symbol}`, estimatedPrice, this.cacheManager.getTTL('ticker'))
  275. logger.warn(`无法获取${symbol}实时价格,使用估算价格: $${estimatedPrice}`)
  276. return estimatedPrice
  277. } catch (error) {
  278. logger.error('获取当前价格失败', { symbol, error: error.message })
  279. return symbol.includes('BTC') ? 65000 : 3500
  280. }
  281. }
  282. /**
  283. * 获取活跃订单统计
  284. */
  285. getActiveOrdersStats() {
  286. return {
  287. stopLossOrders: this.activeStopLossOrders.size,
  288. takeProfitOrders: this.activeTakeProfitOrders.size,
  289. monitoringSymbols: this.monitoringIntervals.size,
  290. }
  291. }
  292. }