This commit is contained in:
2026-08-24 20:53:21 +08:00
commit 6e78bba16a
287 changed files with 32443 additions and 0 deletions

271
arb/stat_job.go Normal file
View File

@@ -0,0 +1,271 @@
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})
}
}
}
},
}
}