package arb import ( "github.com/banbox/banbot/btime" "github.com/banbox/banbot/config" "github.com/banbox/banbot/core" "github.com/banbox/banbot/strat" "github.com/banbox/banexg/log" "github.com/banbox/banexg/utils" ta "github.com/banbox/banta" "go.uber.org/zap" "maps" "math" "math/rand" "path/filepath" ) func init() { strat.AddStratGroup("arb", map[string]strat.FuncMakeStrat{ "gp": ArbGroupJob, }) } /* strengths widRatios rate: 0 -0.002491 0.859934 rate: 14234 -0.000915 0.946673 rate: 28468 -0.000089 0.997275 rate: 42702 0.000818 1.048731 rate: 56936 0.002571 1.166192 rate: 71170 0.007989 1.619902 */ func ArbGroupJob(pol *config.RunPolicyConfig) *strat.TradeStrat { lenFast := 6 lenSlow := 20 lenMid := 96 lenStd := 96 bollWid := 0.0043 lastSplitMS := int64(0) tfMSecs := int64(60000) dayMS := int64(utils.TFToSecs("1d") * 1000) env, _ := ta.NewBarEnv(core.ExgName, core.Market, "", "1m") var rate *ta.Series groups := make(map[*strat.StratJob]float64) hits := make(map[string]float64) warmNum := 120 var widths []float64 var maxRate float64 var position float64 var posTag string var rates []*TimeFlt dumpDir := filepath.Join(config.GetDataDir(), "dump") return &strat.TradeStrat{ WarmupNum: warmNum, RunTimeFrames: []string{"1m"}, BatchInOut: true, OnBar: func(s *strat.StratJob) { }, OnBatchJobs: func(jobs []*strat.StratJob) { var curMS int64 newNum := 0 hitDup := maps.Clone(hits) for _, j := range jobs { curMS = max(curMS, j.Env.TimeStop) if _, ok := hitDup[j.Symbol.Symbol]; ok { delete(hitDup, j.Symbol.Symbol) } else { newNum += 1 } } alignDayMS := utils.AlignTfMSecs(curMS, dayMS) if alignDayMS > lastSplitMS || newNum > 0 || len(hitDup) > 0 { // 每天刷新品种分组 for j := range groups { j.CloseOrders(&strat.ExitReq{Tag: "switch", Log: true}) } offsetMS := alignDayMS - dayMS dateStr := btime.ToDateStr(offsetMS, "20060102") dumpPath := filepath.Join(dumpDir, dateStr+".html") if len(rates) > 1000 { log.Info("try dump rates", zap.String("date", dateStr), zap.Int("num", len(rates))) err := dumpRateChart(rates, "1m", dumpPath, dateStr) if err != nil { log.Error("dumpRateChart fail", zap.Error(err)) } } rates = nil rand.Shuffle(len(jobs), func(i, j int) { jobs[i], jobs[j] = jobs[j], jobs[i] }) env.Reset() rate = env.NewSeries(nil) groups = make(map[*strat.StratJob]float64) hits = make(map[string]float64) for i, j := range jobs { if i%2 == 0 { groups[j] = -1 } else { groups[j] = 1 } hits[j.Symbol.Symbol] = j.Env.Close.Get(lenMid) } env.TimeStop = curMS - int64(lenMid)*tfMSecs env.TimeStart = env.TimeStop - tfMSecs for i := warmNum; i >= 0; i-- { var price1, price2 float64 var num1, num2 float64 for j, tag := range groups { base := hits[j.Symbol.Symbol] ratec := j.Env.Close.Get(i) / base if tag < 0 { price1 += ratec num1 += 1 } else { price2 += ratec num2 += 1 } } price1 /= num1 price2 /= num2 env.TimeStart += tfMSecs env.TimeStop += tfMSecs rate.Append(price1 / price2) _ = ta.EMA(rate, lenFast) _ = ta.EMA(rate, lenSlow) maMid := ta.EMA(rate, lenMid).Get(0) dev := ta.StdDev(rate, lenStd) stdDev := ta.SMA(dev, lenStd).Get(0) * 1.7 bollWidAvg := bollWid*maMid*0.5 + stdDev*0.5 widths = append(widths, bollWidAvg) } lastSplitMS = alignDayMS } var price1, price2 float64 var num1, num2 float64 for j, tag := range groups { base := hits[j.Symbol.Symbol] ratec := j.Env.Close.Get(0) / base if tag < 0 { price1 += ratec num1 += 1 } else { price2 += ratec num2 += 1 } } price1 /= num1 price2 /= num2 curRate := price1 / price2 env.TimeStart += tfMSecs env.TimeStop += tfMSecs rate.Append(curRate) rates = append(rates, &TimeFlt{ Stamp: curMS, Val: curRate, }) maFast := ta.EMA(rate, lenFast) maSlow := ta.EMA(rate, lenSlow) //66 maMidS := ta.EMA(rate, lenMid) maMid := maMidS.Get(0) dev := ta.StdDev(rate, lenStd) stdDev := ta.SMA(dev, lenStd).Get(0) * 1.7 bollWidAvg := bollWid*maMid*0.5 + stdDev*0.5 widths = append(widths, bollWidAvg) zScore := (curRate - maMid) / bollWidAvg strength := maMid/maMidS.Get(warmNum) - 1 widDelta := bollWidAvg - widths[len(widths)-60] widRatio := bollWidAvg / widths[len(widths)-warmNum] fastXSlow := maFast.Cross(maSlow) fastXMid := maFast.Cross(maMidS) absStrength := math.Abs(strength) mkt := "" if absStrength > 0.01 || math.Abs(zScore) > 4 { // D: 结构性跳变,暂停交易 mkt = "D" } else if widRatio > 1.5 && widDelta > 0 || bollWidAvg >= 0.008 { // C: 高波动突破 mkt = "C" } else if absStrength >= 0.003 || widRatio > 1.3 && widDelta > 0 { // B: 趋势市场 mkt = "B" } else if absStrength < 0.002 && widRatio < 1.2 && widDelta <= 0 { // A: 低波动区间 mkt = "A" } else { //if absStrength >= 0.001 && absStrength < 0.002 && widRatio < 1.3 // E: 低频弱趋势,中性,混合策略 mkt = "E" } dirt := -1.0 if strength > 0 { dirt = 1 } if math.Abs(zScore) > maxRate*dirt { maxRate = zScore } allow1, allow2 := false, false if mkt == "A" { allow1 = true } else if mkt == "B" || mkt == "C" { // B明显趋势 或 C波动扩大方向明确 allow2 = true } else if mkt == "E" { allow1, allow2 = true, true } openDirt := 0.0 openTag := "" dateStr := btime.ToDateStr(curMS, core.DefaultDateFmt) if allow1 { expPft := math.Abs(curRate - maMid) if math.Abs(maxRate) >= 1.5 && expPft > 0.002 && float64(fastXSlow)*dirt == -1 { openDirt = -dirt openTag = "rev" log.Info("open rev", zap.String("date", dateStr), zap.Float64("z", zScore), zap.Float64("width", bollWidAvg), zap.Float64("widDelta", widDelta)) } } if allow2 && float64(fastXMid)*dirt == 1 { if absStrength >= 0.003 || widRatio > 1.3 && widDelta > 0 { openDirt = dirt openTag = "follow" log.Info("open trend", zap.String("date", dateStr), zap.Float64("z", zScore), zap.Float64("width", bollWidAvg), zap.Float64("widDelta", widDelta), zap.Float64("strength", strength), zap.Float64("widRatio", widRatio)) } } if position != openDirt && openDirt != 0 { if position != 0 { log.Info("close pos", zap.String("date", dateStr), zap.String("tag", "reverse"), zap.Float64("dirt", position)) for j := range groups { j.CloseOrders(&strat.ExitReq{Tag: "reverse"}) } } for j, tag := range groups { j.OpenOrder(&strat.EnterReq{ Tag: openTag, Short: tag != openDirt, }) } log.Info("open pos", zap.String("date", dateStr), zap.String("tag", openTag), zap.Float64("dirt", openDirt)) position = openDirt posTag = openTag } else if position != 0 { exitTag := "" if posTag == "rev" { if position*dirt > 0 { exitTag = "tpRev" } else if math.Abs(zScore) >= 3 { exitTag = "slRev" } } else if posTag == "follow" { if position*dirt < 0 && (math.Abs(zScore) > 0.5 || widDelta < 0) { exitTag = "slFol" } else if position*dirt > 0 && math.Abs(zScore) >= 1 { exitTag = "tpFol" } } if exitTag != "" { log.Info("close pos", zap.String("date", dateStr), zap.String("tag", exitTag), zap.Float64("dirt", position)) position = 0 posTag = "" for j := range groups { j.CloseOrders(&strat.ExitReq{Tag: exitTag}) } } } }, } }