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

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
}