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

159
arb/common.go Normal file
View File

@@ -0,0 +1,159 @@
package arb
import (
"github.com/banbox/banbot/exg"
"github.com/banbox/banbot/orm"
"github.com/banbox/banbot/strat"
"github.com/banbox/banexg"
"github.com/banbox/banexg/errs"
"github.com/banbox/banstagy/com"
"gonum.org/v1/gonum/floats"
"math"
"slices"
"sort"
)
func init() {
strat.AddStratGroup("arb", map[string]strat.FuncMakeStrat{
"stat": MeanRegressStat,
})
}
type spikeTuple struct {
A *orm.ExSymbol
B *orm.ExSymbol
Score float64 // 分数,越高越好
BigSlope float64 // 大趋势平均斜率
PftRate float64 // (hh-ll)/minChg平均倍率
RawXBig int // 价格比率与大周期均线交叉次数
LastDiff float64 // 最新fast与大趋势偏移,是PftRate倍数,越小越好
}
/*
发现两个品种价格比率套利机会。
通过此函数发现的机会实际上是交易量很小,价格变动百分比较大的小品种的波动。如XEM,最小变动0.6%,不如直接交易这个品种
要求:EMA1333较稳定,(hh-ll)/maxChg较大,ratio和大均线交叉次数较多
建议:pftRate>0.4,rawXBig>200,score>0.3
*/
func findSpikeTuples(endMS int64, tf string, barNum, hlWindow, itemMax int) ([]*spikeTuple, *errs.Error) {
// 获取所有标的的K线
exgId := exg.Default.Info().ID
exsMap := orm.GetExSymbols(exgId, banexg.MarketLinear)
exsList, barList, _, err := getExsBars(exg.Default, exsMap, tf, 0, endMS, barNum)
if err != nil {
return nil, err
}
// 计算周期内关键指标
minChg := 0.0005 * 4 * 4 // 变动至少是两次开平仓手续费的4倍
bigSlopeDist := 120 // 计算大周期斜率的距离
items := make([]*spikeTuple, 0, len(exsList)*2)
halfWindow := hlWindow / 2
if hlWindow%2 > 0 {
halfWindow += 1
}
for i := 0; i < len(exsList); i++ {
barsA := barList[i]
for j := i + 1; j < len(exsList); j++ {
barsB := barList[j]
barsRes := getAlignBars(barsA, barsB)
a, b := barsRes[0], barsRes[1]
if len(a) < barNum/2 {
continue
}
var ma = com.NewEMA(0.08)
//var maSlow = common.NewEMA(0.03)
var maBig = com.NewEMA(0.0015)
var rawXBig = com.NewCrossLog()
//var rawXfa = common.NewCrossLog()
//var vmax, vmin = float64(0), float64(0)
var raw = make([]float64, 0, len(a))
var bbRates = make([]float64, 0, len(a)/3)
var spikeNum = 0
var maBigs = make([]float64, 0, len(a))
var bigSlopes = make([]float64, 0, 100)
for m := 0; m < len(a); m++ {
ratio := a[m].Close / b[m].Close
raw = append(raw, ratio)
maVal := ma.Update(ratio)
maBigV := maBig.Update(ratio)
if math.IsNaN(maBigV) {
continue
}
maBigs = append(maBigs, maBigV)
if len(maBigs)%bigSlopeDist == 0 && len(maBigs) > 0 {
// 采样大周期斜率
bigSlopes = append(bigSlopes, math.Abs(maBigV/maBigs[len(maBigs)-bigSlopeDist]-1))
}
if m%halfWindow == 0 && len(raw) > hlWindow {
// 计算hh-ll的盈利空间
near5 := raw[len(raw)-hlWindow:]
bbRates = append(bbRates, (slices.Max(near5)-slices.Min(near5))/maVal/minChg)
}
// 计算raw与big均线交叉次数
if ratio != maBigV {
rawXBig.Update(ratio - maBigV)
fxs := rawXBig.Get()
if fxs == 1 {
// 只记录上穿,避免交叉太多
spikeNum += 1
}
}
// 计算比率在均线附近振幅可盈利的次数
//rawXfa.Update(ratio - maVal)
//rxf := rawXfa.Get()
//if rxf == 1 || rxf == -1 {
// pft := (vmax-vmin)/maVal > minChg
// if pft {
// spikeNum += 1
// }
// vmin = maVal
// vmax = maVal
//}
//if ratio > maVal {
// vmax = max(vmax, ratio)
//} else {
// vmin = min(vmin, ratio)
//}
}
// 平均大周期斜率
avgSlope := floats.Sum(bigSlopes) * 50 / float64(len(bigSlopes))
slopeRate := math.Pow(1-min(1, avgSlope), 1.5)
// 平均盈利空间
sort.Slice(bbRates, func(i, j int) bool {
return bbRates[i] < bbRates[j]
})
bbRates = bbRates[:len(bbRates)/2]
avgPftRate := floats.Sum(bbRates) / float64(len(bbRates))
// 与大周期交叉的分数,交叉少于200权重降低
hitScore := math.Log(float64(spikeNum)/200 + 1)
val := hitScore * avgPftRate * slopeRate
// 计算fast与大周期偏移
lastDiff := math.Abs(ma.Val/maBig.Val-1) / minChg / avgPftRate
items = append(items, &spikeTuple{
A: exsList[i],
B: exsList[j],
Score: val,
BigSlope: avgSlope,
PftRate: avgPftRate,
RawXBig: spikeNum,
LastDiff: lastDiff,
})
}
}
sort.Slice(items, func(i, j int) bool {
return items[i].Score > items[j].Score
})
tops := items[:min(1000, int(float64(len(items))*0.1))]
res := make([]*spikeTuple, 0, len(tops)/2)
hits := make(map[string]int)
for _, t := range tops {
numA, _ := hits[t.A.Symbol]
numB, _ := hits[t.B.Symbol]
if max(numA, numB) < itemMax {
hits[t.A.Symbol] = numA + 1
hits[t.B.Symbol] = numB + 1
res = append(res, t)
}
}
return res, nil
}