package trade import ( "sig-pub/pkg/strategy" "sig-pub/pkg/types" "sig-pub/pkg/types/decimals" "time" ) // CloseAssess 价格更新评估是否平仓 // @return closeTicket平仓单信息 func (s *SigTradeStrategy) CloseAssessOnPrice(ctx strategy.IInstanceIntervalSigStrategyContext, account ITradeAccount, instId string, price float64) (closeTickets []TradeTicket, err error) { openTrades := account.GetOpenTrades(instId) if len(openTrades) == 0 { return } for _, trd := range openTrades { k := ctx.Get(trd.InstId, PriceDriverInterval, 0) price := decimals.MustToFloat64(k.Close) account.OnPrice(instId, price) closeTrade, cause := s.closeTradeOnPrice(price, trd) if closeTrade { closeTickets = append(closeTickets, TradeTicket{ TradeType: TradeTypeClose, InstId: trd.InstId, Side: trd.Side.Opposite(), Price: price, Leverage: trd.Leverage, Qty: trd.Qty, Interval: string(k.Interval), Ktime: k.Interval.MustAddMul(k.Ts, 1), Ctime: time.Now().UnixMilli(), Cause: cause, TradesId: []int64{trd.TradeId}, }) } } return } func (s *SigTradeStrategy) closeTradeOnPrice(price float64, trd *TradeOrder) (closeTrade bool, cause Cause) { if !trd.Side.IsValid() { return } // update peak px if trd.Side == types.SideLong && (price > trd.PeakPx) { trd.PeakPx = price } if trd.Side == types.SideShort && (price < trd.PeakPx) { trd.PeakPx = price } entry := trd.Price // side long: if trd.Side == types.SideLong { // 固定止损 if s.closeParam.StopLossPct > 0 && price <= entry*(1-s.closeParam.StopLossPct) { return true, CauseCloseStoploss } // 固定止盈 if s.closeParam.TakeProfitPct > 0 && price >= entry*(1+s.closeParam.TakeProfitPct) { return true, CauseCloseTakeprofit } // 基于最高利润动态止盈 if len(s.closeParam.ProfitRetracePcts) > 0 { // peak profit fraction peakProfit := (trd.PeakPx - entry) / entry minProfitToTrail, trailingPct := float64(0), float64(0) for _, profit := range s.closeParam.ProfitRetracePcts { if len(profit) != 2 { continue } _minProfitToTrail := profit[0] // 启动最高利润回撤的最小盈利阈值 _trailingPct := profit[1] // 基于最高利润回撤触发平仓 if peakProfit >= _minProfitToTrail && _minProfitToTrail > minProfitToTrail { minProfitToTrail = _minProfitToTrail trailingPct = _trailingPct } } if minProfitToTrail > 0 && trailingPct > 0 { trail := entry + (trd.PeakPx-entry)*(1-trailingPct) if price <= trail { return true, CauseCloseTrailing } } } return } // side short: if s.closeParam.StopLossPct > 0 && price >= trd.Price*(1+s.closeParam.StopLossPct) { return true, CauseCloseStoploss } if s.closeParam.TakeProfitPct > 0 && price <= trd.Price*(1-s.closeParam.TakeProfitPct) { return true, CauseCloseTakeprofit } // 基于最高利润动态止盈 if len(s.closeParam.ProfitRetracePcts) > 0 { // peak profit fraction peakProfit := (entry - trd.PeakPx) / entry minProfitToTrail, trailingPct := float64(0), float64(0) for _, profit := range s.closeParam.ProfitRetracePcts { if len(profit) != 2 { continue } _minProfitToTrail := profit[0] // 启动最高利润回撤的最小盈利阈值 _trailingPct := profit[1] // 基于最高利润回撤触发平仓 if peakProfit >= _minProfitToTrail && _minProfitToTrail >= minProfitToTrail { minProfitToTrail = _minProfitToTrail trailingPct = _trailingPct } } if minProfitToTrail > 0 && trailingPct > 0 { trail := entry - (entry-trd.PeakPx)*(1+trailingPct) if price >= trail { return true, CauseCloseTrailing } } } return } // CloseAssessOnSig 信号触发时评估是否平仓 func (s *SigTradeStrategy) CloseAssessOnSig(ctx strategy.IInstanceIntervalSigStrategyContext, account ITradeAccount, instId string, sigSide types.Side) (closeTickets []TradeTicket, err error) { if !s.closeParam.CloseOnSideReverse { return } oppositeSide := sigSide.Opposite() for _, trade := range account.GetOpenTrades(instId) { // 关闭反方向单 if trade.Side == oppositeSide { k := ctx.Get(trade.InstId, PriceDriverInterval, 0) price := decimals.MustToFloat64(k.Close) account.OnPrice(instId, price) closeTickets = append(closeTickets, TradeTicket{ TradeType: TradeTypeClose, InstId: instId, TradesId: []int64{trade.TradeId}, Side: trade.Side.Opposite(), Price: price, Leverage: trade.Leverage, Qty: trade.Qty, Interval: string(k.Interval), Ktime: k.Interval.MustAddMul(k.Ts, 1), Ctime: time.Now().UnixMilli(), Cause: CauseCloseReverseSingal, }) } } return }