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 }