| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- import { EventEmitter } from 'events'
- import { logger } from '../../utils/logger.js'
- import { PacificaProxyClient } from '../../exchanges/pacifica/PacificaProxyClient.js'
- import { OrderCreatePayload } from '../../exchanges/pacifica/OrdersAdapter.js'
- /**
- * 止盈止损管理器 - 高级止损策略管理
- */
- export class StopLossManager extends EventEmitter {
- private stopOrders: Map<string, StopOrder> = new Map()
- private clients: Map<string, PacificaProxyClient> = new Map()
- private config: StopLossConfig
- private isActive: boolean = false
- private monitoringInterval?: NodeJS.Timeout
- constructor(config?: Partial<StopLossConfig>) {
- super()
- this.config = {
- checkIntervalMs: config?.checkIntervalMs || 2000, // 2秒检查间隔
- defaultStopLossPercent: config?.defaultStopLossPercent || 0.01, // 1% 止损
- defaultTakeProfitPercent: config?.defaultTakeProfitPercent || 0.02, // 2% 止盈
- trailingStopPercent: config?.trailingStopPercent || 0.005, // 0.5% 追踪止损
- partialClosePercent: config?.partialClosePercent || 0.5, // 50% 部分平仓
- enableBreakEvenStop: config?.enableBreakEvenStop || true, // 启用盈亏平衡止损
- breakEvenTriggerPercent: config?.breakEvenTriggerPercent || 0.01, // 1% 触发盈亏平衡
- enableScaleOut: config?.enableScaleOut || true, // 启用分批止盈
- scaleOutLevels: config?.scaleOutLevels || [0.01, 0.02, 0.03], // 分批止盈点位
- maxSlippagePercent: config?.maxSlippagePercent || 0.01, // 最大滑点
- }
- }
- /**
- * 注册交易客户端
- */
- registerClient(account: string, client: PacificaProxyClient): void {
- this.clients.set(account, client)
- logger.info('注册止损管理客户端', { account })
- }
- /**
- * 为仓位设置止盈止损
- */
- async setStopLossAndTakeProfit(
- account: string,
- symbol: string,
- positionSize: number,
- entryPrice: number,
- isLong: boolean,
- customConfig?: Partial<StopOrderConfig>,
- ): Promise<string> {
- const orderId = this.generateOrderId(account, symbol)
- const stopOrder: StopOrder = {
- id: orderId,
- account,
- symbol,
- originalSize: Math.abs(positionSize),
- remainingSize: Math.abs(positionSize),
- entryPrice,
- isLong,
- status: 'active',
- createdAt: Date.now(),
- lastUpdate: Date.now(),
- config: {
- stopLossPercent: customConfig?.stopLossPercent || this.config.defaultStopLossPercent,
- takeProfitPercent: customConfig?.takeProfitPercent || this.config.defaultTakeProfitPercent,
- enableTrailingStop: customConfig?.enableTrailingStop || false,
- trailingStopPercent: customConfig?.trailingStopPercent || this.config.trailingStopPercent,
- enablePartialClose: customConfig?.enablePartialClose || false,
- partialClosePercent: customConfig?.partialClosePercent || this.config.partialClosePercent,
- stopType: customConfig?.stopType || 'fixed',
- },
- trailingStopPrice: null,
- highestPrice: entryPrice,
- lowestPrice: entryPrice,
- executedOrders: [],
- partialCloseCount: 0,
- }
- // 计算初始止损止盈价格
- this.updateStopPrices(stopOrder, entryPrice)
- this.stopOrders.set(orderId, stopOrder)
- logger.info('设置止盈止损', {
- orderId,
- account,
- symbol,
- positionSize,
- entryPrice,
- isLong,
- stopLossPrice: stopOrder.stopLossPrice,
- takeProfitPrice: stopOrder.takeProfitPrice,
- })
- this.emit('stopOrderCreated', stopOrder)
- return orderId
- }
- /**
- * 启动止损监控
- */
- startMonitoring(): void {
- if (this.isActive) return
- this.isActive = true
- this.monitoringInterval = setInterval(() => {
- this.performStopChecks()
- }, this.config.checkIntervalMs)
- logger.info('止损监控已启动', {
- interval: this.config.checkIntervalMs,
- activeOrders: this.stopOrders.size,
- })
- this.emit('monitoringStarted')
- }
- /**
- * 停止止损监控
- */
- stopMonitoring(): void {
- if (!this.isActive) return
- this.isActive = false
- if (this.monitoringInterval) {
- clearInterval(this.monitoringInterval)
- this.monitoringInterval = undefined
- }
- logger.info('止损监控已停止')
- this.emit('monitoringStopped')
- }
- /**
- * 执行止损检查
- */
- private async performStopChecks(): Promise<void> {
- const activeOrders = Array.from(this.stopOrders.values()).filter(order => order.status === 'active')
- for (const order of activeOrders) {
- try {
- await this.checkStopOrder(order)
- } catch (error: any) {
- logger.error('止损检查失败', {
- orderId: order.id,
- account: order.account,
- symbol: order.symbol,
- error: error.message,
- })
- }
- }
- }
- /**
- * 检查单个止损订单
- */
- private async checkStopOrder(order: StopOrder): Promise<void> {
- const client = this.clients.get(order.account)
- if (!client) {
- logger.warn('未找到交易客户端', { account: order.account })
- return
- }
- // 获取当前价格
- const currentPrice = await this.getCurrentPrice(client, order.symbol)
- if (!currentPrice) return
- // 更新价格追踪
- this.updatePriceTracking(order, currentPrice)
- // 检查是否触发止损或止盈
- const triggerResult = this.checkTriggers(order, currentPrice)
- if (triggerResult.shouldTrigger) {
- await this.executeTrigger(order, triggerResult, currentPrice)
- }
- order.lastUpdate = Date.now()
- }
- /**
- * 更新价格追踪
- */
- private updatePriceTracking(order: StopOrder, currentPrice: number): void {
- // 更新最高价和最低价
- if (currentPrice > order.highestPrice) {
- order.highestPrice = currentPrice
- }
- if (currentPrice < order.lowestPrice) {
- order.lowestPrice = currentPrice
- }
- // 更新追踪止损价格
- if (order.config.enableTrailingStop) {
- this.updateTrailingStop(order, currentPrice)
- }
- // 更新盈亏平衡止损
- if (this.config.enableBreakEvenStop) {
- this.updateBreakEvenStop(order, currentPrice)
- }
- // 更新止损止盈价格
- this.updateStopPrices(order, currentPrice)
- }
- /**
- * 更新追踪止损
- */
- private updateTrailingStop(order: StopOrder, currentPrice: number): void {
- if (!order.config.enableTrailingStop) return
- const trailingPercent = order.config.trailingStopPercent || this.config.trailingStopPercent
- if (order.isLong) {
- // 多头:追踪止损价格跟随最高价下移
- const newTrailingStop = order.highestPrice * (1 - trailingPercent)
- if (!order.trailingStopPrice || newTrailingStop > order.trailingStopPrice) {
- order.trailingStopPrice = newTrailingStop
- logger.debug('更新追踪止损', {
- orderId: order.id,
- newTrailingStop,
- highestPrice: order.highestPrice,
- currentPrice,
- })
- }
- } else {
- // 空头:追踪止损价格跟随最低价上移
- const newTrailingStop = order.lowestPrice * (1 + trailingPercent)
- if (!order.trailingStopPrice || newTrailingStop < order.trailingStopPrice) {
- order.trailingStopPrice = newTrailingStop
- logger.debug('更新追踪止损', {
- orderId: order.id,
- newTrailingStop,
- lowestPrice: order.lowestPrice,
- currentPrice,
- })
- }
- }
- }
- /**
- * 更新盈亏平衡止损
- */
- private updateBreakEvenStop(order: StopOrder, currentPrice: number): void {
- const triggerPercent = this.config.breakEvenTriggerPercent
- const profitPercent = order.isLong
- ? (currentPrice - order.entryPrice) / order.entryPrice
- : (order.entryPrice - currentPrice) / order.entryPrice
- // 如果盈利超过触发阈值,将止损移动到盈亏平衡点
- if (profitPercent >= triggerPercent && order.config.stopType === 'fixed') {
- order.config.stopType = 'break_even'
- logger.info('激活盈亏平衡止损', {
- orderId: order.id,
- profitPercent: profitPercent.toFixed(4),
- entryPrice: order.entryPrice,
- currentPrice,
- })
- }
- }
- /**
- * 更新止损止盈价格
- */
- private updateStopPrices(order: StopOrder, referencePrice?: number): void {
- const price = referencePrice || order.entryPrice
- if (order.config.stopType === 'break_even') {
- // 盈亏平衡止损
- order.stopLossPrice = order.entryPrice
- } else if (order.config.enableTrailingStop && order.trailingStopPrice) {
- // 追踪止损
- order.stopLossPrice = order.trailingStopPrice
- } else {
- // 固定止损
- const stopLossPercent = order.config.stopLossPercent
- order.stopLossPrice = order.isLong
- ? order.entryPrice * (1 - stopLossPercent)
- : order.entryPrice * (1 + stopLossPercent)
- }
- // 止盈价格
- const takeProfitPercent = order.config.takeProfitPercent
- order.takeProfitPrice = order.isLong
- ? order.entryPrice * (1 + takeProfitPercent)
- : order.entryPrice * (1 - takeProfitPercent)
- }
- /**
- * 检查触发条件
- */
- private checkTriggers(order: StopOrder, currentPrice: number): TriggerResult {
- // 检查止损
- if (order.stopLossPrice) {
- const stopLossTriggered = order.isLong ? currentPrice <= order.stopLossPrice : currentPrice >= order.stopLossPrice
- if (stopLossTriggered) {
- return {
- shouldTrigger: true,
- triggerType: 'stop_loss',
- triggerPrice: order.stopLossPrice,
- closePercent: 1.0, // 全部平仓
- }
- }
- }
- // 检查止盈
- if (order.takeProfitPrice) {
- const takeProfitTriggered = order.isLong
- ? currentPrice >= order.takeProfitPrice
- : currentPrice <= order.takeProfitPrice
- if (takeProfitTriggered) {
- const closePercent = order.config.enablePartialClose
- ? order.config.partialClosePercent || this.config.partialClosePercent
- : 1.0
- return {
- shouldTrigger: true,
- triggerType: 'take_profit',
- triggerPrice: order.takeProfitPrice,
- closePercent,
- }
- }
- }
- // 检查分批止盈
- if (this.config.enableScaleOut) {
- const scaleOutTrigger = this.checkScaleOutTriggers(order, currentPrice)
- if (scaleOutTrigger.shouldTrigger) {
- return scaleOutTrigger
- }
- }
- return { shouldTrigger: false }
- }
- /**
- * 检查分批止盈触发
- */
- private checkScaleOutTriggers(order: StopOrder, currentPrice: number): TriggerResult {
- const scaleOutLevels = this.config.scaleOutLevels
- for (let i = 0; i < scaleOutLevels.length; i++) {
- const level = scaleOutLevels[i]
- const targetPrice = order.isLong ? order.entryPrice * (1 + level) : order.entryPrice * (1 - level)
- const triggered = order.isLong ? currentPrice >= targetPrice : currentPrice <= targetPrice
- if (triggered && !order.scaleOutExecuted?.includes(i)) {
- if (!order.scaleOutExecuted) {
- order.scaleOutExecuted = []
- }
- return {
- shouldTrigger: true,
- triggerType: 'scale_out',
- triggerPrice: targetPrice,
- closePercent: 0.25, // 25% 分批平仓
- scaleOutLevel: i,
- }
- }
- }
- return { shouldTrigger: false }
- }
- /**
- * 执行触发操作
- */
- private async executeTrigger(order: StopOrder, trigger: TriggerResult, currentPrice: number): Promise<void> {
- const client = this.clients.get(order.account)
- if (!client) return
- const closeSize = order.remainingSize * trigger.closePercent
- const side = order.isLong ? 'ask' : 'bid' // 平仓方向
- try {
- const payload: OrderCreatePayload = {
- account: order.account,
- symbol: order.symbol,
- amount: closeSize.toString(),
- side,
- reduceOnly: true,
- slippagePercent: (this.config.maxSlippagePercent * 100).toString(),
- }
- let result
- if (trigger.triggerType === 'stop_loss') {
- // 止损使用市价单确保成交
- result = await client.createMarketOrder(payload)
- } else {
- // 止盈可以使用限价单获得更好价格
- payload.orderType = 'limit'
- payload.price = trigger.triggerPrice?.toString()
- result = await client.createLimitOrder(payload)
- }
- // 记录执行结果
- const execution: StopExecution = {
- orderId: result.orderId || result.order_id || '',
- triggerType: trigger.triggerType,
- executedSize: closeSize,
- executedPrice: currentPrice,
- timestamp: Date.now(),
- scaleOutLevel: trigger.scaleOutLevel,
- }
- order.executedOrders.push(execution)
- order.remainingSize -= closeSize
- // 更新分批执行状态
- if (trigger.triggerType === 'scale_out' && trigger.scaleOutLevel !== undefined) {
- if (!order.scaleOutExecuted) {
- order.scaleOutExecuted = []
- }
- order.scaleOutExecuted.push(trigger.scaleOutLevel)
- order.partialCloseCount++
- }
- // 检查是否完全平仓
- if (order.remainingSize <= 0.0001) {
- order.status = 'completed'
- } else if (trigger.triggerType === 'take_profit' && order.config.enablePartialClose) {
- // 部分止盈后,调整剩余止盈价格
- this.adjustRemainingTakeProfit(order)
- }
- logger.info('触发执行成功', {
- orderId: order.id,
- triggerType: trigger.triggerType,
- executedSize: closeSize,
- remainingSize: order.remainingSize,
- executedPrice: currentPrice,
- resultOrderId: result.orderId || result.order_id,
- })
- this.emit('stopTriggered', {
- order,
- execution,
- trigger,
- currentPrice,
- })
- } catch (error: any) {
- logger.error('触发执行失败', {
- orderId: order.id,
- triggerType: trigger.triggerType,
- error: error.message,
- })
- this.emit('stopTriggerFailed', {
- order,
- trigger,
- error: error.message,
- })
- }
- }
- /**
- * 调整剩余止盈价格
- */
- private adjustRemainingTakeProfit(order: StopOrder): void {
- // 部分止盈后,可以将剩余仓位的止盈点位适当调高
- const adjustmentFactor = 1.5 // 调高50%
- const originalPercent = order.config.takeProfitPercent
- const newPercent = originalPercent * adjustmentFactor
- order.takeProfitPrice = order.isLong ? order.entryPrice * (1 + newPercent) : order.entryPrice * (1 - newPercent)
- logger.info('调整剩余止盈价格', {
- orderId: order.id,
- newTakeProfitPrice: order.takeProfitPrice,
- adjustmentFactor,
- })
- }
- /**
- * 获取当前价格
- */
- private async getCurrentPrice(client: PacificaProxyClient, symbol: string): Promise<number | null> {
- try {
- const ticker = await client.getTicker(symbol)
- return ticker?.price || null
- } catch (error: any) {
- logger.error('获取当前价格失败', {
- symbol,
- error: error.message,
- })
- return null
- }
- }
- /**
- * 生成订单ID
- */
- private generateOrderId(account: string, symbol: string): string {
- return `stop_${account}_${symbol}_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`
- }
- /**
- * 取消止损订单
- */
- cancelStopOrder(orderId: string): boolean {
- const order = this.stopOrders.get(orderId)
- if (!order) return false
- order.status = 'cancelled'
- logger.info('取消止损订单', { orderId })
- this.emit('stopOrderCancelled', order)
- return true
- }
- /**
- * 获取活跃止损订单
- */
- getActiveStopOrders(account?: string): StopOrder[] {
- const orders = Array.from(this.stopOrders.values()).filter(order => order.status === 'active')
- return account ? orders.filter(order => order.account === account) : orders
- }
- /**
- * 获取止损统计
- */
- getStopOrderStats(): StopOrderStats {
- const allOrders = Array.from(this.stopOrders.values())
- return {
- totalOrders: allOrders.length,
- activeOrders: allOrders.filter(o => o.status === 'active').length,
- completedOrders: allOrders.filter(o => o.status === 'completed').length,
- cancelledOrders: allOrders.filter(o => o.status === 'cancelled').length,
- totalExecutions: allOrders.reduce((sum, o) => sum + o.executedOrders.length, 0),
- successfulStopLosses: allOrders.filter(o => o.executedOrders.some(e => e.triggerType === 'stop_loss')).length,
- successfulTakeProfits: allOrders.filter(o => o.executedOrders.some(e => e.triggerType === 'take_profit')).length,
- }
- }
- }
- // 类型定义
- export interface StopOrder {
- id: string
- account: string
- symbol: string
- originalSize: number
- remainingSize: number
- entryPrice: number
- isLong: boolean
- status: 'active' | 'completed' | 'cancelled'
- createdAt: number
- lastUpdate: number
- config: StopOrderConfig
- stopLossPrice?: number
- takeProfitPrice?: number
- trailingStopPrice?: number | null
- highestPrice: number
- lowestPrice: number
- executedOrders: StopExecution[]
- partialCloseCount: number
- scaleOutExecuted?: number[]
- }
- export interface StopOrderConfig {
- stopLossPercent: number
- takeProfitPercent: number
- enableTrailingStop: boolean
- trailingStopPercent: number
- enablePartialClose: boolean
- partialClosePercent: number
- stopType: 'fixed' | 'trailing' | 'break_even'
- }
- export interface StopLossConfig {
- checkIntervalMs: number
- defaultStopLossPercent: number
- defaultTakeProfitPercent: number
- trailingStopPercent: number
- partialClosePercent: number
- enableBreakEvenStop: boolean
- breakEvenTriggerPercent: number
- enableScaleOut: boolean
- scaleOutLevels: number[]
- maxSlippagePercent: number
- }
- export interface TriggerResult {
- shouldTrigger: boolean
- triggerType?: 'stop_loss' | 'take_profit' | 'scale_out'
- triggerPrice?: number
- closePercent?: number
- scaleOutLevel?: number
- }
- export interface StopExecution {
- orderId: string
- triggerType: 'stop_loss' | 'take_profit' | 'scale_out'
- executedSize: number
- executedPrice: number
- timestamp: number
- scaleOutLevel?: number
- }
- export interface StopOrderStats {
- totalOrders: number
- activeOrders: number
- completedOrders: number
- cancelledOrders: number
- totalExecutions: number
- successfulStopLosses: number
- successfulTakeProfits: number
- }
- export const stopLossManager = new StopLossManager()
|