173 lines
4.6 KiB
Go
173 lines
4.6 KiB
Go
package fac
|
|
|
|
import (
|
|
"github.com/banbox/banbot/config"
|
|
"github.com/banbox/banbot/opt"
|
|
"github.com/banbox/banbot/utils"
|
|
"github.com/banbox/banexg/log"
|
|
utils2 "github.com/banbox/banexg/utils"
|
|
"go.uber.org/zap"
|
|
"gonum.org/v1/gonum/floats"
|
|
)
|
|
|
|
func init() {
|
|
opt.FactorMap["best"] = best
|
|
opt.FactorMap["best3"] = best3
|
|
opt.FactorMap["bigVol"] = bigVols
|
|
opt.FactorMap["sharpe"] = sharpe
|
|
}
|
|
|
|
func bigVols(arg opt.FacArgs) ([]string, error) {
|
|
allNum := len(arg.Pairs)
|
|
cutNum := min(int(float64(allNum)/10), 30)
|
|
// 只取成交量高的一半品种
|
|
volPeriodMS := int64(utils2.TFToSecs("30d") * 1000)
|
|
volBarNum := int(volPeriodMS / arg.TFMSecs)
|
|
vols := make([]float64, 0, allNum)
|
|
for _, sta := range arg.Pairs {
|
|
start := max(0, len(sta.KLines)-volBarNum)
|
|
bars := sta.KLines[start:]
|
|
volSum := float64(0)
|
|
for _, b := range bars {
|
|
volSum += b.Volume * b.Close
|
|
}
|
|
vols = append(vols, volSum)
|
|
}
|
|
volIdxArr := utils.ArgSortDesc(vols)[:allNum/2]
|
|
var result = make([]string, 0, cutNum)
|
|
for _, idx := range volIdxArr {
|
|
result = append(result, arg.Pairs[idx].Symbol)
|
|
if len(result) >= cutNum {
|
|
break
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func best(arg opt.FacArgs) ([]string, error) {
|
|
return bestOf(arg, 0)
|
|
}
|
|
|
|
func best3(arg opt.FacArgs) ([]string, error) {
|
|
return bestOf(arg, 0.25)
|
|
}
|
|
|
|
func bestOf(arg opt.FacArgs, offRate float64) ([]string, error) {
|
|
allNum := len(arg.Pairs)
|
|
initFund := float64(0)
|
|
for _, v := range config.WalletAmounts {
|
|
initFund = v
|
|
break
|
|
}
|
|
cutNum := min(int(float64(allNum)/10), 30)
|
|
// 只取成交量高的一半品种
|
|
volPeriodMS := int64(utils2.TFToSecs("30d") * 1000)
|
|
volBarNum := int(volPeriodMS / arg.TFMSecs)
|
|
vols := make([]float64, 0, allNum)
|
|
scores := make([]float64, 0, allNum)
|
|
for _, sta := range arg.Pairs {
|
|
start := max(0, len(sta.KLines)-volBarNum)
|
|
bars := sta.KLines[start:]
|
|
volSum := float64(0)
|
|
for _, b := range bars {
|
|
volSum += b.Volume * b.Close
|
|
}
|
|
vols = append(vols, volSum)
|
|
profitPct := floats.Sum(sta.Returns) * 100 / initFund
|
|
reals := make([]float64, 0, len(sta.Returns))
|
|
curFund := initFund
|
|
for _, v := range sta.Returns {
|
|
curFund += v
|
|
reals = append(reals, curFund)
|
|
}
|
|
drawRate, _ := utils.CalcDrawDown(reals, opt.ShowNum)
|
|
score := opt.CalcBtScore(profitPct, drawRate*100)
|
|
scores = append(scores, score)
|
|
}
|
|
volIdxArr := utils.ArgSortDesc(vols)[:allNum/2]
|
|
scoreIdxArr := utils.ArgSortDesc(scores)
|
|
volIdMap := utils.ArrToMap(volIdxArr, func(t2 int) int {
|
|
return t2
|
|
})
|
|
offset := int(float64(len(scoreIdxArr)) * offRate)
|
|
var result = make([]string, 0, cutNum)
|
|
for _, idx := range scoreIdxArr[offset:] {
|
|
if _, ok := volIdMap[idx]; ok {
|
|
result = append(result, arg.Pairs[idx].Symbol)
|
|
if len(result) >= cutNum {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func sharpe(arg opt.FacArgs) ([]string, error) {
|
|
allNum := len(arg.Pairs)
|
|
initFund := float64(0)
|
|
for _, v := range config.WalletAmounts {
|
|
initFund = v
|
|
break
|
|
}
|
|
cutNum := min(int(float64(allNum)/10), 30)
|
|
// 只取成交量高的一半品种
|
|
volPeriodMS := int64(utils2.TFToSecs("30d") * 1000)
|
|
volBarNum := int(volPeriodMS / arg.TFMSecs)
|
|
vols := make([]float64, 0, allNum)
|
|
scores := make([]float64, 0, allNum)
|
|
totOdNum := 0
|
|
rangeSecs := (arg.EndMS - arg.StartMS) / 1000
|
|
for _, sta := range arg.Pairs {
|
|
if len(sta.KLines) == 0 || sta.KLines[0].Time > arg.StartMS {
|
|
// 跳过回顾周期内数据不全的品种
|
|
vols = append(vols, 0)
|
|
scores = append(scores, -99)
|
|
continue
|
|
}
|
|
start := max(0, len(sta.KLines)-volBarNum)
|
|
bars := sta.KLines[start:]
|
|
volSum := float64(0)
|
|
for _, b := range bars {
|
|
volSum += b.Volume * b.Close
|
|
}
|
|
vols = append(vols, volSum)
|
|
reals := make([]float64, 0, len(sta.Returns))
|
|
curFund := initFund
|
|
for _, v := range sta.Returns {
|
|
curFund += v
|
|
reals = append(reals, curFund)
|
|
}
|
|
sharp, _, err := opt.CalcMeasuresByReal(reals, rangeSecs, "", 0, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scores = append(scores, sharp)
|
|
totOdNum += len(sta.Orders)
|
|
}
|
|
validNum := len(vols)
|
|
volIdxArr := utils.ArgSortDesc(vols)[:validNum/2]
|
|
scoreIdxArr := utils.ArgSortDesc(scores)
|
|
volIdMap := utils.ArrToMap(volIdxArr, func(t2 int) int {
|
|
return t2
|
|
})
|
|
avgOdNum := totOdNum / len(arg.Pairs)
|
|
var result = make([]string, 0, cutNum)
|
|
var raw = make([]string, 0, allNum)
|
|
for _, idx := range scoreIdxArr {
|
|
//raw[arg.Pairs[idx].Symbol] = scores[idx]
|
|
raw = append(raw, arg.Pairs[idx].Symbol)
|
|
if _, ok := volIdMap[idx]; ok {
|
|
sta := arg.Pairs[idx]
|
|
if len(sta.Orders)*4 < avgOdNum {
|
|
continue
|
|
}
|
|
result = append(result, sta.Symbol)
|
|
if len(result) >= cutNum {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
log.Info("by sharpe", zap.Strings("pairs", raw))
|
|
return result, nil
|
|
}
|