package t01 import ( "github.com/banbox/banbot/orm/ormo" "github.com/banbox/banbot/strat" "github.com/banbox/banexg/log" "github.com/banbox/banstagy/com" ta "github.com/banbox/banta" "go.uber.org/zap" "math" ) var ( // 这里是默认参数,请尽量在MainVX()中修改,不要在这里修改 maBig = 30 maSml = 30 maParent = 50 sampleNum = 500 // 斜率采样数量 sampleStep = 2 // 斜率计算间隔 atrLen = 14 openOffRate = float64(0) // 开单时,从均线偏移基准ATR的倍数 goodRate = float64(10) // 收益超过此倍数的ATR时,将止损更新为均线 badRate = float64(5) // 损失超过此倍数的ATR时,止损退出 ) type MoreType struct { bigSlop *com.Slop hitGoods map[int64]bool // 触发goodRate的订单ID,允许每个bar更新止损 } func openOrder(s *strat.StratJob, closePrice, dirt, price, stopLoss float64) { if (stopLoss-closePrice)*dirt >= 0 { // 当前止损立刻满足,跳过 return } tag := "long" if dirt < 0 { tag = "short" } err := s.OpenOrder(&strat.EnterReq{ Tag: tag, Limit: price, StopLoss: stopLoss, Short: dirt < 0, }) if err != nil { log.Error("open order fail", zap.Error(err)) } } func onStartUp(s *strat.StratJob) { s.More = &MoreType{ bigSlop: com.NewSlop(sampleNum, 0.25), hitGoods: make(map[int64]bool), } } func onInfoBar(s *strat.StratJob, e *ta.BarEnv, pair, tf string) { sma := ta.SMA(e.Close, maParent) prevSma := sma.Get(sampleStep) if e.BarNum%sampleStep > 0 || math.IsNaN(prevSma) { return } more, _ := s.More.(*MoreType) curSma := sma.Get(0) slop := (curSma - prevSma) / curSma more.bigSlop.Update(e.BarNum, slop) } func onBar(s *strat.StratJob) { e := s.Env atr := ta.ATR(e.High, e.Low, e.Close, atrLen) baseAtrLen := int(math.Round(float64(atrLen) * 4.3)) atrBase := ta.Lowest(ta.Highest(atr, atrLen), baseAtrLen).Get(0) c := e.Close.Get(0) smaBig := ta.SMA(e.Close, maBig) smaVal := smaBig.Get(0) // smaSml := ta.SMA(e.Close, maSml) // maX := smaSml.Cross(smaBig) // rsiVal := ta.RSI(e.Close, 20).Get(0) more, _ := s.More.(*MoreType) prevSma := smaBig.Get(sampleStep) if e.BarNum%sampleStep == 0 && !math.IsNaN(prevSma) { slop := (smaVal - prevSma) / smaVal more.bigSlop.Update(e.BarNum, slop) } var longOd, shortOd *ormo.InOutOrder if len(s.LongOrders) > 0 { longOd = s.LongOrders[0] } if len(s.ShortOrders) > 0 { shortOd = s.ShortOrders[0] } atrVal := atr.Get(0) slRange := max(atrBase*badRate, atrVal*1.5) // 检查是否触发盈利阈值,若触发更新止损 var orders = []*ormo.InOutOrder{longOd, shortOd} for _, od := range orders { if od != nil { odDirt := 1.0 if od.Short { odDirt = -1 } isGood, ok := more.hitGoods[od.ID] if !ok && (c-od.Enter.Average)*odDirt >= atrBase*goodRate { more.hitGoods[od.ID] = true isGood = true } if isGood { // 已触发盈利阈值,每个bar更新止损 sl := od.GetStopLoss() if sl == nil || (smaVal-sl.Price)*odDirt > 0 { od.SetStopLoss(&ormo.ExitTrigger{Price: smaVal}) } } else if od.Status == ormo.InOutStatusInit { // 尚未入场,更新入场价格,更新止损 _ = od.SetEnterLimit(smaVal) od.SetStopLoss(&ormo.ExitTrigger{Price: smaVal - odDirt*slRange}) } } } if atrVal < atrBase { // && more.bigSlop.Dirt > 0 if longOd == nil && more.bigSlop.Dirt >= 0 { // 开多单 && c-smaVal+slRange > atrBase*0.5 openOrder(s, c, 1, 0, c-slRange) } // && more.bigSlop.Dirt < 0 if shortOd == nil && more.bigSlop.Dirt <= 0 { // 开空单 && smaVal+slRange-c > atrBase*0.5 openOrder(s, c, -1, 0, c+slRange) } } }