You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
4.6 KiB
151 lines
4.6 KiB
package trade |
|
|
|
import ( |
|
"sig-pub/pkg/strategy" |
|
"sig-pub/pkg/types" |
|
"sig-pub/pkg/types/decimals" |
|
"time" |
|
) |
|
|
|
// ExitAnalysis 价格更新评估是否平仓 |
|
// @return closeTicket平仓单信息 |
|
func (s *SigTradeStrategy) ExitAnalysisOnPrice(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 |
|
} |
|
|
|
// ExitAnalysis 信号触发时评估是否平仓 |
|
func (s *SigTradeStrategy) ExitAnalysisOnSig(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 |
|
}
|
|
|