package old import ( "github.com/banbox/banbot/config" "github.com/banbox/banbot/strat" "github.com/banbox/banexg/log" ta "github.com/banbox/banta" "go.uber.org/zap" "math" "sort" ) var ( lenAtr = 9 rangeRate = 0.6 // 预计bar利润区间比率:60% ShakeTop = 0.2 // 只对分数前20%的标的进行震荡交易 minProfitPct = 0.01 // 最小目标盈利百分比 ) type TMore struct { symbol string shakeScore float64 // 震荡分数:(1-平均实体比率)*平均bar波动比率 btmPrice float64 // 平均向下突破比率 topPrice float64 // 平均向上突破比率 shakeOK bool // 是否允许震荡中开单 shakeHit float64 // 震荡首先触发位置:-1触发顶部,1触发底部 } func Eat(pol *config.RunPolicyConfig) *strat.TradeStrat { baseAtrLen := int(math.Round(float64(lenAtr) * 5)) return &strat.TradeStrat{ Version: 1, WarmupNum: max(30, baseAtrLen), MinTfScore: 0.75, BatchInfo: true, OnStartUp: func(s *strat.StratJob) { s.More = &TMore{ symbol: s.Symbol.Symbol, } }, OnPairInfos: func(s *strat.StratJob) []*strat.PairSub { return []*strat.PairSub{ {Pair: "_cur_", TimeFrame: "8h", WarmupNum: 30}, } }, OnBar: func(s *strat.StratJob) { e := s.Env h, l, c := e.High.Get(0), e.Low.Get(0), e.Close.Get(0) m, _ := s.More.(*TMore) if m.shakeOK { // 允许震荡开单 if m.shakeHit == 0 { if h >= m.topPrice { m.shakeHit = -1 } else if l <= m.btmPrice { m.shakeHit = 1 } } if s.OrderNum == 0 && m.shakeHit != 0 { slRange := (m.topPrice - m.btmPrice) tag := "long" trigPrice := m.btmPrice takeProfit := m.topPrice if m.shakeHit < 0 { tag = "short" trigPrice = m.topPrice takeProfit = m.btmPrice } stopLoss := trigPrice - slRange*m.shakeHit slDst := (c - stopLoss) * m.shakeHit / slRange tpDst := (takeProfit - c) * m.shakeHit / slRange if slDst > 0.5 && tpDst > 0.7 { // 到止损的距离不低于50% err := s.OpenOrder(&strat.EnterReq{ Tag: tag, Short: m.shakeHit < 0, StopLoss: stopLoss, TakeProfit: takeProfit, }) //date := btime.ToDateStr(btime.TimeMS(), "") //log.Info(fmt.Sprintf("%s %s open %s c:%.3f sl:%.3f, tp:%.3f", s.Symbols.Symbols, // date, tag, c, stopLoss, takeProfit)) if err != nil { log.Error("open fail", zap.Error(err)) } } } } }, OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) { // ma := ta.EMA(e.Close, 5) rsi := ta.RSI(e.Close, 9) atr := ta.ATR(e.High, e.Low, e.Close, lenAtr) atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0) atrRate := atr.Get(0) / atrBase // 计算平均实体比率 o, h, l, c := e.Open.Get(0), e.High.Get(0), e.Low.Get(0), e.Close.Get(0) ph, pl, pc := e.High.Get(1), e.Low.Get(1), e.Close.Get(1) solidTop, solidBtm := max(o, c), min(o, c) tRange, pRange := h-l, ph-pl //tr := e.GetSeries("tRange") //tr.Set(math.Sqrt(tRange * 100 / c)) //maTRate := ta.EMA(tr, 3) solRate := e.Close.To("solRate", 0) solRate.Set(math.Abs(o-c) / tRange) maSRT := ta.EMA(solRate, 3).Get(0) // 计算上下行比率 dnRate := e.Close.To("dnRate", 0) dnRate.Set((l - pc) / pRange) maDnRate := ta.EMA(dnRate, 5) upRate := e.Close.To("upRate", 0) upRate.Set((h - pc) / pRange) maUpRate := ta.EMA(upRate, 5) m, _ := s.More.(*TMore) m.shakeScore = (1 - maSRT) // * maTRate.Get(0) top := c + maUpRate.Get(0)*tRange btm := c + maDnRate.Get(0)*tRange edgeOff := (top - btm) * (1 - rangeRate) * 0.5 m.btmPrice = btm - edgeOff m.topPrice = top + edgeOff //m.maChg = (ma.Get(0) - ma.Get(1)) / pRange topLineRate := (h - solidTop) / tRange btmLineRate := (solidBtm - l) / tRange lineDiff := math.Abs(topLineRate-btmLineRate) / max(topLineRate, btmLineRate) rsiVal, pRsi := rsi.Get(0)-50, rsi.Get(1)-50 m.shakeOK = (top-btm)*rangeRate/c >= minProfitPct && lineDiff < 0.35 && maSRT < 0.3 m.shakeHit = 0 //log.Info(fmt.Sprintf("h: %.3f l:%.3f c:%.3f maSRT: %.3f upRate:%.3f dnRate:%.3f %v", h, l, c, // maSRT.Get(0), maUpRate.Get(0), maDnRate.Get(0), m.shakeOK)) if math.Abs(rsiVal) > 20 || math.Abs(pRsi) > 20 { // (ATR较大/反向影线占比大)和RSI都极端时,容易反转,禁止震荡开单 revLineRate := btmLineRate if rsiVal > 0 { revLineRate = topLineRate } if atrRate > 2.5 || atrRate > 1.3 && revLineRate > 0.75 { m.shakeOK = false } } }, OnBatchInfos: func(tf string, jobs map[string]*strat.JobEnv) { // 筛选出所有币中shakeScore降序的前n个,然后在子周期中低买高卖 var mores = make([]*TMore, 0, len(jobs)) for _, job := range jobs { m, _ := job.Job.More.(*TMore) mores = append(mores, m) } var shakeTopN = int(math.Ceil(float64(len(jobs)) * ShakeTop)) sort.Slice(mores, func(i, j int) bool { return mores[i].shakeScore > mores[j].shakeScore }) for _, m := range mores { if shakeTopN > 0 && m.shakeOK { shakeTopN -= 1 } else { m.shakeOK = false } } }, } }