package his import ( "fmt" "github.com/banbox/banbot/com" "github.com/banbox/banbot/config" "github.com/banbox/banbot/core" "github.com/banbox/banbot/orm/ormo" "github.com/banbox/banbot/strat" "github.com/banbox/banexg" "github.com/banbox/banexg/log" "github.com/banbox/banexg/utils" ta "github.com/banbox/banta" "go.uber.org/zap" "math" ) /* 超跌反弹策略。30个品种为宜 做空部分没有利润,禁用 */ type State3762 struct { NoLongUntil int64 NoShortUntil int64 } func J3762(pol *config.RunPolicyConfig) *strat.TradeStrat { // 定义策略参数 p := pol.DefInt("p", 20, core.PNorm(5, 50)) d := pol.Def("d", 30.0, core.PNorm(5, 50)) mult := pol.Def("mult", 1.5, core.PNorm(1.0, 3.0)) slRate := pol.Def("slRate", 25, core.PNorm(0, 50)) needlong := pol.Def("needlong", 1.0, core.PUniform(0, 1)) > 0.5 needshort := pol.Def("needshort", 0.0, core.PUniform(0, 1)) > 0.5 dayMS := int64(utils.TFToSecs("1d") * 1000) lenAtr := 20 baseAtrLen := int(math.Round(float64(lenAtr) * 4.3)) return &strat.TradeStrat{ WarmupNum: baseAtrLen * 2, // 预热K线数量,确保有足够数据计算指标 RunTimeFrames: []string{"15m"}, OnStartUp: func(s *strat.StratJob) { s.More = &State3762{} }, OnBar: func(s *strat.StratJob) { e := s.Env m, _ := s.More.(*State3762) // 计算基础布林带指标 sma := ta.SMA(e.Close, p) ma7 := ta.SMA(e.Close, 7).Get(0) ma30 := ta.SMA(e.Close, 30).Get(0) multCur := mult if ma7 < ma30 { // 熊市下移下限 multCur *= 1.35 } stddev := ta.StdDev(e.Close, p).Get(0) * multCur upperBB := sma.Get(0) + stddev lowerBB := sma.Get(0) - stddev atr := ta.ATR(e.High, e.Low, e.Close, lenAtr) atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0) // 计算百分比偏离轨道 atrRt := d if ma7 < ma30 { // 熊市下移下限 atrRt *= 1.35 } upex2 := sma.Get(0) + atrBase*atrRt dnex2 := sma.Get(0) - atrBase*atrRt // 计算最终轨道 upexPrice := max(upperBB, (upex2+upperBB)/2) dnexPrice := min(lowerBB, (dnex2+lowerBB)/2) // 获取当前价格 closePrice := e.Close.Get(0) openPrice := e.Open.Get(0) // 生成交易信号 exitSignal := closePrice > openPrice // 绿色K线信号 if math.IsNaN(upexPrice) || math.IsNaN(dnexPrice) { return } slRange := min(atrBase*slRate, closePrice*0.25) // 执行交易逻辑 if !s.IsWarmUp { s.Strat.WriteOutput(fmt.Sprintf("%s upp: %.6f, low: %.6f upex2: %.6f, dnex2: %.6f upex: %.6f, dnex: %.6f slRange: %.6f", e.Symbol, upperBB, lowerBB, upex2, dnex2, upexPrice, dnexPrice, slRange), true) } if needlong { if len(s.LongOrders) == 0 { // 使用限价单在下轨价格做多,不依赖当前价格是否突破轨道 if e.TimeStop >= m.NoLongUntil { curPrice := com.GetPrice(e.Symbol, banexg.OdSideBuy) if math.Abs(curPrice/closePrice-1) > 0.01 { log.Info("bad price from GetPrice", zap.String("pair", e.Symbol), zap.Float64("val", curPrice), zap.Float64("close", closePrice)) } s.OpenOrder(&strat.EnterReq{ Tag: "Long", Limit: dnexPrice, // 以下轨价格作为限价单 StopLossVal: slRange, Log: true, }) } } else { for _, od := range s.LongOrders { if od.Status == ormo.InOutStatusInit { err := od.SetEnterLimit(dnexPrice) if err != nil { log.Error("SetEnterLimit fail", zap.Error(err)) } else { err = od.SetStopLoss(&ormo.ExitTrigger{ Price: dnexPrice - slRange, }) if err != nil { log.Error("SetStopLoss fail", zap.Error(err)) } } } else if od.Status == ormo.InOutStatusFullEnter { barNum := e.BarCount(od.Enter.UpdateAt) if barNum > 2 && od.ProfitRate < 0 { // 持仓2个k线,出现亏损,平仓 s.CloseOrders(&strat.ExitReq{ Tag: "Exit bad Long", Dirt: core.OdDirtLong, Log: true, }) m.NoLongUntil = e.TimeStop + dayMS } } } } } if needshort { if len(s.ShortOrders) == 0 { // 使用限价单在上轨价格做空,不依赖当前价格是否突破轨道 if e.TimeStop >= m.NoShortUntil { s.OpenOrder(&strat.EnterReq{ Tag: "Short", Short: true, Limit: upexPrice, // 以上轨价格作为限价单 StopLossVal: slRange, Log: true, }) } } else { for _, od := range s.ShortOrders { if od.Status == ormo.InOutStatusInit { err := od.SetEnterLimit(upexPrice) if err != nil { log.Error("SetEnterLimit fail", zap.Error(err)) } else { err = od.SetStopLoss(&ormo.ExitTrigger{ Price: upexPrice + slRange, }) if err != nil { log.Error("SetStopLoss fail", zap.Error(err)) } } } else if od.Status == ormo.InOutStatusFullEnter { barNum := e.BarCount(od.Enter.UpdateAt) if barNum > 2 && od.ProfitRate < 0 { // 持仓2个k线,出现亏损,平仓 s.CloseOrders(&strat.ExitReq{ Tag: "Exit bad short", Dirt: core.OdDirtShort, Log: true, }) m.NoShortUntil = e.TimeStop + dayMS } } } } } // 平仓条件:绿色K线 if exitSignal { if len(s.LongOrders) > 0 { s.CloseOrders(&strat.ExitReq{ Tag: "Exit Long", Dirt: core.OdDirtLong, Log: true, }) } } else { if len(s.ShortOrders) > 0 { s.CloseOrders(&strat.ExitReq{ Tag: "Exit Short", Dirt: core.OdDirtShort, Log: true, }) } } }, OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) { if chgType == core.OdChgExitFill && od.ExitTag == core.ExitTagStopLoss { m, _ := s.More.(*State3762) if od.Short { m.NoShortUntil = od.ExitAt + dayMS } else { m.NoLongUntil = od.ExitAt + dayMS } } }, } }