init
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.idea
|
||||
.omx
|
||||
tmp
|
||||
bot
|
||||
code
|
||||
*.exe
|
||||
*.profile
|
||||
*.db
|
||||
*.gob
|
||||
*.log
|
||||
.noai
|
||||
922
adv/pairs.go
Normal file
922
adv/pairs.go
Normal file
@@ -0,0 +1,922 @@
|
||||
package adv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gonum.org/v1/gonum/floats"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/entry"
|
||||
"github.com/banbox/banbot/opt"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
utils2 "github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
"github.com/banbox/banexg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runConfig := func(args *config.CmdArgs, run func(*config.CmdArgs) *errs.Error) error {
|
||||
core.SetRunMode(core.RunModeOther)
|
||||
args.Init()
|
||||
return run(args)
|
||||
}
|
||||
bindCommon := func(command *cobra.Command, args *config.CmdArgs) {
|
||||
flags := command.Flags()
|
||||
flags.StringArrayVar((*[]string)(&args.Configs), "config", nil, "config path; may be repeated")
|
||||
flags.StringVar(&args.ConfigData, "config-data", "", "inline YAML config")
|
||||
flags.StringVar(&args.Logfile, "logfile", "", "log file path")
|
||||
flags.StringVar(&args.DataDir, "datadir", "", "path to the data directory")
|
||||
flags.BoolVar(&args.NoDefault, "no-default", false, "ignore config.yml and config.local.yml")
|
||||
flags.StringVar(&args.LogLevel, "level", "info", "logging level")
|
||||
flags.IntVar(&args.MaxPoolSize, "max-pool-size", 0, "maximum database pool size")
|
||||
flags.BoolVar(&core.NetDisable, "net-off", false, "disable network requests")
|
||||
}
|
||||
newConfigCommand := func(name, help string, run func(*config.CmdArgs) *errs.Error,
|
||||
bind func(*cobra.Command, *config.CmdArgs)) *cobra.Command {
|
||||
args := &config.CmdArgs{}
|
||||
command := &cobra.Command{
|
||||
Use: name,
|
||||
Short: help,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return runConfig(args, run)
|
||||
},
|
||||
}
|
||||
bindCommon(command, args)
|
||||
if bind != nil {
|
||||
bind(command, args)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
dayRets := newConfigCommand("dayRets", "calc return rate for pairs by months", CalcPairDayPerfs,
|
||||
func(command *cobra.Command, args *config.CmdArgs) {
|
||||
command.Flags().StringVar(&args.InPath, "in", "", "input file or directory")
|
||||
})
|
||||
entry.AddCommand("tool", dayRets)
|
||||
|
||||
entry.AddCommand("tool", newConfigCommand("updMkt", "update coin markets by month", UpdateCoinMarkets, nil))
|
||||
entry.AddCommand("tool", newConfigCommand("buildFac", "build factor samples", BuildFacSamples, nil))
|
||||
entry.AddCommand("tool", &cobra.Command{
|
||||
Use: "testFac",
|
||||
Short: "test factors",
|
||||
DisableFlagParsing: true,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return TestFactors(args)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var urlCoinMarket = "https://api.coinmarketcap.com/data-api/v3/cryptocurrency/listings/historical?convertId=2781&date=%s&limit=%d&start=%d"
|
||||
var coinSlugMap = map[string]string{
|
||||
"BTCDOM": "",
|
||||
"1000XEC": "ecash",
|
||||
"LUNA2": "terra-luna-v2",
|
||||
"DODOX": "dodo",
|
||||
"BEAMX": "onbeam",
|
||||
"1000RATS": "",
|
||||
"RONIN": "ronin",
|
||||
"1000SHIB": "shiba-inu",
|
||||
"1000LUNC": "terra-luna",
|
||||
"1000FLOKI": "floki-inu",
|
||||
"1000PEPE": "pepe",
|
||||
"1000BONK": "bonk1",
|
||||
}
|
||||
|
||||
type PairReturns struct {
|
||||
StartMS int64
|
||||
EndMS int64
|
||||
Pairs map[string]*ItemReturns
|
||||
}
|
||||
|
||||
type ItemReturns struct {
|
||||
StartMS int64
|
||||
EndMS int64
|
||||
Arr []float64
|
||||
}
|
||||
|
||||
type CoinMarket struct {
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
ListMS int64 `json:"list_ms"`
|
||||
Slug string `json:"slug"`
|
||||
FirstMS int64 `json:"first_ms"`
|
||||
LastMS int64 `json:"last_ms"`
|
||||
MarketHis []*MarketSlice `json:"market_his"`
|
||||
}
|
||||
|
||||
type MarketSlice struct {
|
||||
TimeMS int64 `json:"time_ms"`
|
||||
CirculatingSupply float64 `json:"circulating_supply"`
|
||||
TotalSupply float64 `json:"total_supply"`
|
||||
Price float64 `json:"price"`
|
||||
MarketCap float64 `json:"market_cap"`
|
||||
}
|
||||
|
||||
type CoinMarketHis struct {
|
||||
Coins map[string]*CoinMarket
|
||||
Times []int64
|
||||
}
|
||||
|
||||
type CoinMarketResponse struct {
|
||||
Data []struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
Slug string `json:"slug"`
|
||||
DateAdded string `json:"dateAdded"`
|
||||
CirculatingSupply float64 `json:"circulatingSupply"`
|
||||
TotalSupply float64 `json:"totalSupply"`
|
||||
Quotes []struct {
|
||||
Name string `json:"name"`
|
||||
Price float64 `json:"price"`
|
||||
MarketCap float64 `json:"marketCap"`
|
||||
} `json:"quotes"`
|
||||
} `json:"data"`
|
||||
Status struct {
|
||||
ErrorCode string `json:"error_code"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
/*
|
||||
某个品种在某个时间的历史收益,市场、未来回报等信息
|
||||
*/
|
||||
type PairSample struct {
|
||||
Offset int // 值int是在ItemReturns.Arr的位置索引
|
||||
Pair string
|
||||
Code string
|
||||
Price float64
|
||||
MarketCap float64
|
||||
Vols []float64
|
||||
}
|
||||
|
||||
type FacHis struct {
|
||||
Times []int64
|
||||
Pairs map[string]*ItemReturns
|
||||
Data map[int64]map[string]*PairSample // time: pair: sample
|
||||
}
|
||||
|
||||
type FacState struct {
|
||||
Ret *ItemReturns
|
||||
Sample *PairSample
|
||||
Groups map[string]string
|
||||
Label string
|
||||
Tmp float64 // 因子计算临时使用
|
||||
OutScore float64 // 输出分数
|
||||
OutID int // 输出分数排序序号
|
||||
}
|
||||
|
||||
type GpState struct {
|
||||
count float64 // 该块中FacState的数量
|
||||
sumScore float64 // 该块的平均分数
|
||||
}
|
||||
|
||||
/*
|
||||
CalcPairDayPerfs 从订单计算每个品种的每日收益率
|
||||
*/
|
||||
func CalcPairDayPerfs(args *config.CmdArgs) *errs.Error {
|
||||
err := biz.SetupComs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orders, err := ormo.LoadOrdersGob(args.InPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pairOrders := make(map[string][]*ormo.InOutOrder)
|
||||
minTimeMS, maxTimeMS := btime.TimeMS(), int64(0)
|
||||
totalCost := float64(0)
|
||||
for _, o := range orders {
|
||||
odList, _ := pairOrders[o.Symbol]
|
||||
pairOrders[o.Symbol] = append(odList, o)
|
||||
minTimeMS = min(minTimeMS, o.Enter.CreateAt)
|
||||
maxTimeMS = max(maxTimeMS, o.Exit.CreateAt)
|
||||
totalCost += o.EnterCost() / o.Leverage
|
||||
}
|
||||
// 假设初始金额能同时开50笔
|
||||
initStake := totalCost / float64(len(orders)) * 50
|
||||
// 解析开始时间和结束时间,对齐到月的1号
|
||||
startYear, startMon, _ := btime.MSToTime(minTimeMS).Date()
|
||||
endYear, endMon, endDay := btime.MSToTime(maxTimeMS).Date()
|
||||
endAddMon := 0
|
||||
if endDay > 1 {
|
||||
endAddMon = 1
|
||||
}
|
||||
startMS := time.Date(startYear, startMon, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
endTime := time.Date(endYear, endMon, 1, 0, 0, 0, 0, time.UTC)
|
||||
endMS := endTime.AddDate(0, endAddMon, 0).UnixMilli()
|
||||
symbolMap := orm.GetExSymbolMap(core.ExgName, core.Market)
|
||||
dayMSecs := int64(utils.TFToSecs("1d") * 1000)
|
||||
retMap := make(map[string]*ItemReturns)
|
||||
var addNum int
|
||||
for pair, odList := range pairOrders {
|
||||
sort.Slice(odList, func(i, j int) bool {
|
||||
a, b := odList[i], odList[j]
|
||||
return a.Enter.CreateAt < b.Enter.CreateAt
|
||||
})
|
||||
exs := symbolMap[pair]
|
||||
_, bars, err := orm.GetOHLCV(exs, "1d", startMS, endMS, 0, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var closes []float64
|
||||
if len(bars) > 0 {
|
||||
bars, addNum = utils2.FillOHLCVLacks(bars, startMS, endMS, dayMSecs)
|
||||
if addNum > 0 {
|
||||
log.Info("fill ohlcv lack", zap.String("pair", pair), zap.Int("num", addNum))
|
||||
}
|
||||
closes = make([]float64, len(bars))
|
||||
for i, b := range bars {
|
||||
closes[i] = b.Close
|
||||
}
|
||||
}
|
||||
// 计算每日回报率
|
||||
returns, dayOff, dayEnd := ormo.CalcUnitReturns(odList, closes, startMS, endMS, dayMSecs)
|
||||
retRates := make([]float64, len(returns))
|
||||
for i, ret := range returns {
|
||||
retRates[i] = ret / initStake
|
||||
}
|
||||
retMap[pair] = &ItemReturns{
|
||||
StartMS: startMS + int64(dayOff)*dayMSecs,
|
||||
EndMS: startMS + int64(dayEnd)*dayMSecs,
|
||||
Arr: retRates[dayOff:dayEnd],
|
||||
}
|
||||
}
|
||||
outPath := filepath.Join(filepath.Dir(args.InPath), "out.gob")
|
||||
return utils2.EncodeGob(outPath, &PairReturns{
|
||||
StartMS: startMS,
|
||||
EndMS: endMS,
|
||||
Pairs: retMap,
|
||||
})
|
||||
}
|
||||
|
||||
func loadHisMarkets() (*CoinMarketHis, string, *errs.Error) {
|
||||
path := filepath.Join(config.GetDataDir(), "cryptoMarkets.gob")
|
||||
var data = &CoinMarketHis{
|
||||
Coins: make(map[string]*CoinMarket),
|
||||
Times: make([]int64, 0),
|
||||
}
|
||||
err := utils2.DecodeGobFile(path, data)
|
||||
if err != nil {
|
||||
return data, path, err
|
||||
}
|
||||
return data, path, nil
|
||||
}
|
||||
|
||||
/*
|
||||
UpdateCoinMarkets 更新BanDataDir目录下的cryptoMarkets.gob全币种市场信息。每个月更新一次。
|
||||
*/
|
||||
func UpdateCoinMarkets(args *config.CmdArgs) *errs.Error {
|
||||
err := biz.SetupComs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, path, err := loadHisMarkets()
|
||||
if err != nil {
|
||||
log.Warn("load cryptoMarkets error", zap.Error(err))
|
||||
}
|
||||
|
||||
// 确定开始时间
|
||||
startTime := time.Date(2014, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
if len(data.Times) > 0 {
|
||||
startTime = time.UnixMilli(data.Times[len(data.Times)-1]).UTC().AddDate(0, 1, 0)
|
||||
}
|
||||
lastTimeMS := startTime.AddDate(0, -1, 0).UnixMilli()
|
||||
batchSize := 2000
|
||||
isLast := false
|
||||
|
||||
// 循环获取每个月的数据
|
||||
for {
|
||||
if startTime.After(time.Now()) {
|
||||
pyear, pmon, pday := time.Now().UTC().Date()
|
||||
startTime = time.Date(pyear, pmon, pday, 0, 0, 0, 0, time.UTC).AddDate(0, 0, -1)
|
||||
isLast = true
|
||||
}
|
||||
|
||||
// 获取该月所有币种数据
|
||||
monthData := make(map[string]*MarketSlice)
|
||||
start := 1
|
||||
fetchNum, fixLack := 0, 0
|
||||
timeMS := startTime.UnixMilli()
|
||||
for {
|
||||
url := fmt.Sprintf(urlCoinMarket, startTime.Format("2006-01-02"), batchSize, start)
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
resp, err_ := http.Get(url)
|
||||
log.Info("request", zap.String("url", url))
|
||||
if err_ != nil {
|
||||
return errs.New(errs.CodeNetFail, err_)
|
||||
}
|
||||
|
||||
body, err_ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err_ != nil {
|
||||
return errs.New(errs.CodeNetFail, err_)
|
||||
}
|
||||
|
||||
var rsp CoinMarketResponse
|
||||
if err_ = utils.Unmarshal(body, &rsp, utils.JsonNumAuto); err_ != nil {
|
||||
return errs.New(errs.CodeUnmarshalFail, err_)
|
||||
}
|
||||
|
||||
if rsp.Status.ErrorCode != "0" {
|
||||
return errs.NewMsg(errs.CodeRunTime, "code: %v, msg: %v", rsp.Status.ErrorCode, rsp.Status.ErrorMessage)
|
||||
}
|
||||
|
||||
if len(rsp.Data) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, coin := range rsp.Data {
|
||||
slice := &MarketSlice{
|
||||
TimeMS: timeMS,
|
||||
CirculatingSupply: coin.CirculatingSupply,
|
||||
TotalSupply: coin.TotalSupply,
|
||||
}
|
||||
if len(coin.Quotes) > 0 {
|
||||
slice.Price = coin.Quotes[0].Price
|
||||
slice.MarketCap = coin.Quotes[0].MarketCap
|
||||
}
|
||||
monthData[coin.Slug] = slice
|
||||
|
||||
// 更新或创建币种信息
|
||||
if cm, exists := data.Coins[coin.Slug]; exists {
|
||||
if len(cm.MarketHis) == 0 || timeMS > cm.MarketHis[len(cm.MarketHis)-1].TimeMS {
|
||||
// 检查是否有缺失的月份数据需要补充
|
||||
if !isLast {
|
||||
if cm.LastMS < lastTimeMS {
|
||||
fixLack += 1
|
||||
lastSlice := cm.MarketHis[len(cm.MarketHis)-1]
|
||||
// 从上次更新的下个月开始,直到上个月为止,补充所有缺失的月份
|
||||
curTime := time.UnixMilli(cm.LastMS).UTC().AddDate(0, 1, 0)
|
||||
for curTime.UnixMilli() <= lastTimeMS {
|
||||
newSlice := &MarketSlice{
|
||||
TimeMS: curTime.UnixMilli(),
|
||||
CirculatingSupply: lastSlice.CirculatingSupply,
|
||||
TotalSupply: lastSlice.TotalSupply,
|
||||
Price: lastSlice.Price,
|
||||
MarketCap: lastSlice.MarketCap,
|
||||
}
|
||||
cm.MarketHis = append(cm.MarketHis, newSlice)
|
||||
cm.LastMS = curTime.UnixMilli()
|
||||
curTime = curTime.AddDate(0, 1, 0)
|
||||
}
|
||||
}
|
||||
// 添加当前月份的数据
|
||||
cm.MarketHis = append(cm.MarketHis, slice)
|
||||
cm.LastMS = timeMS
|
||||
}
|
||||
if cm.Name != coin.Name {
|
||||
cm.Name = coin.Name
|
||||
}
|
||||
if cm.Symbol != coin.Symbol {
|
||||
cm.Symbol = coin.Symbol
|
||||
}
|
||||
fetchNum += 1
|
||||
}
|
||||
} else if !isLast {
|
||||
listTime, _ := time.Parse("2006-01-02T15:04:05.000Z", coin.DateAdded)
|
||||
data.Coins[coin.Slug] = &CoinMarket{
|
||||
Name: coin.Name,
|
||||
Symbol: coin.Symbol,
|
||||
ListMS: listTime.UnixMilli(),
|
||||
Slug: coin.Slug,
|
||||
FirstMS: timeMS,
|
||||
LastMS: timeMS,
|
||||
MarketHis: []*MarketSlice{slice},
|
||||
}
|
||||
fetchNum += 1
|
||||
}
|
||||
}
|
||||
|
||||
// 这里可能返回数量不足batchSize个,但不代表结束了,继续请求返回为空才表示结束
|
||||
start += batchSize
|
||||
}
|
||||
|
||||
// 添加时间戳
|
||||
data.Times = append(data.Times, timeMS)
|
||||
|
||||
// 保存数据
|
||||
if err := utils2.EncodeGob(path, data); err != nil {
|
||||
log.Error("save market data failed", zap.Error(err))
|
||||
return err
|
||||
} else {
|
||||
log.Info("update coin markets ok", zap.String("date", startTime.Format("2006-01-02")),
|
||||
zap.Int("fetch_num", fetchNum), zap.Int("fixLack", fixLack))
|
||||
}
|
||||
|
||||
// 移动到下一个月
|
||||
lastTimeMS = timeMS
|
||||
startTime = startTime.AddDate(0, 1, 0)
|
||||
if isLast {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
BuildFacSamples 构建因子定期采样历史
|
||||
*/
|
||||
func BuildFacSamples(args *config.CmdArgs) *errs.Error {
|
||||
err := biz.SetupComs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, _, err := loadHisMarkets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
retData := &PairReturns{Pairs: make(map[string]*ItemReturns)}
|
||||
retPath := "E:\\trade\\go\\bandata\\backtest\\383f7dc32a_all_pairs\\out.gob"
|
||||
if !utils2.Exists(retPath) {
|
||||
args.InPath = filepath.Join(filepath.Dir(retPath), "orders.gob")
|
||||
err = CalcPairDayPerfs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = utils2.DecodeGobFile(retPath, retData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(retData.Pairs) == 0 {
|
||||
return errs.NewMsg(errs.CodeRunTime, "no Pairs for PairReturns")
|
||||
}
|
||||
res := &FacHis{Pairs: retData.Pairs, Data: make(map[int64]map[string]*PairSample)}
|
||||
startMS := retData.StartMS
|
||||
|
||||
// 构建Symbol到CoinMarket的映射
|
||||
var b strings.Builder
|
||||
symbolToCoin := make(map[string]*CoinMarket)
|
||||
badCoins := make(map[string]bool)
|
||||
skipCoins := make(map[string]bool)
|
||||
dupCoins := make(map[string][]string)
|
||||
for _, coin := range data.Coins {
|
||||
if old, ok := symbolToCoin[coin.Symbol]; ok {
|
||||
dups, _ := dupCoins[coin.Symbol]
|
||||
if len(dups) == 0 {
|
||||
dups = append(dups, old.Slug)
|
||||
}
|
||||
dupCoins[coin.Symbol] = append(dups, coin.Slug)
|
||||
continue
|
||||
}
|
||||
symbolToCoin[coin.Symbol] = coin
|
||||
b.WriteString(coin.Symbol)
|
||||
b.WriteString(", ")
|
||||
}
|
||||
symbolMap := orm.GetExSymbolMap(core.ExgName, core.Market)
|
||||
log.Info("all coins", zap.String("v", b.String()))
|
||||
if len(dupCoins) > 0 {
|
||||
log.Info("dupCoins", zap.String("v", utils2.MapToStr(dupCoins, true, 0)))
|
||||
}
|
||||
|
||||
// 设置月份间隔(这里设置为3个月)
|
||||
monNum := 2
|
||||
|
||||
// 将startMS转换为月初
|
||||
startTime := time.UnixMilli(startMS).UTC()
|
||||
startTime = time.Date(startTime.Year(), startTime.Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
startTime = startTime.AddDate(0, monNum, 0)
|
||||
endTime := time.UnixMilli(retData.EndMS).UTC()
|
||||
|
||||
// 从startTime开始遍历时间点
|
||||
for curTime := startTime; !curTime.After(endTime); curTime = curTime.AddDate(0, monNum, 0) {
|
||||
timeMS := curTime.UnixMilli()
|
||||
timeMap := make(map[string]*PairSample)
|
||||
prevTime := curTime.AddDate(0, -monNum, 0)
|
||||
nextTime := curTime.AddDate(0, monNum, 0)
|
||||
|
||||
// 对每个pair的returns进行检查
|
||||
for pair, itemRet := range retData.Pairs {
|
||||
// 检查时间范围是否符合要求
|
||||
if itemRet.StartMS > prevTime.UnixMilli() || itemRet.EndMS < nextTime.UnixMilli() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 计算当前时间点在Arr中的索引
|
||||
offset := int((timeMS - itemRet.StartMS) / (24 * 3600 * 1000)) // 转换为天数
|
||||
if offset < 0 || offset >= len(itemRet.Arr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 查找对应的CoinMarket
|
||||
baseCode, _, _, _ := core.SplitSymbol(pair)
|
||||
coin, ok := symbolToCoin[baseCode]
|
||||
if !ok {
|
||||
if slug, ok := coinSlugMap[baseCode]; ok {
|
||||
if slug == "" {
|
||||
skipCoins[baseCode] = true
|
||||
continue
|
||||
}
|
||||
coin = data.Coins[slug]
|
||||
} else {
|
||||
badCoins[baseCode] = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 查找当前时间点对应的MarketSlice
|
||||
var mktSlice *MarketSlice
|
||||
for _, slice := range coin.MarketHis {
|
||||
if slice.TimeMS <= timeMS {
|
||||
mktSlice = slice
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if mktSlice == nil {
|
||||
// 太新,跳过
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取成交量数据
|
||||
exs := symbolMap[pair]
|
||||
if exs == nil {
|
||||
return errs.NewMsg(errs.CodeRunTime, "ExSymbol not exist: %v", pair)
|
||||
}
|
||||
|
||||
// 计算2周和1个月的成交量
|
||||
barStartMS := prevTime.UnixMilli()
|
||||
_, bars, err := orm.GetOHLCV(exs, "1d", barStartMS, timeMS, 0, false)
|
||||
if err != nil {
|
||||
log.Warn("get volume failed", zap.String("pair", pair), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 计算成交量
|
||||
vols := make([]float64, len(bars))
|
||||
for i, bar := range bars {
|
||||
vols[i] = bar.Close * bar.Volume
|
||||
}
|
||||
|
||||
// 构建PairSample
|
||||
sample := &PairSample{
|
||||
Offset: offset,
|
||||
Pair: pair,
|
||||
Code: baseCode,
|
||||
Price: mktSlice.Price,
|
||||
MarketCap: mktSlice.MarketCap,
|
||||
Vols: vols,
|
||||
}
|
||||
|
||||
timeMap[pair] = sample
|
||||
}
|
||||
|
||||
if len(timeMap) > 0 {
|
||||
res.Data[timeMS] = timeMap
|
||||
res.Times = append(res.Times, timeMS)
|
||||
}
|
||||
}
|
||||
|
||||
if len(badCoins) > 0 {
|
||||
log.Warn("unknown coins", zap.String("v", utils2.MapToStr(badCoins, false, 0)))
|
||||
}
|
||||
if len(skipCoins) > 0 {
|
||||
log.Warn("skip coins", zap.String("v", utils2.MapToStr(skipCoins, false, 0)))
|
||||
}
|
||||
|
||||
// 保存结果
|
||||
name := fmt.Sprintf("facHis_%dM.gob", monNum)
|
||||
outPath := filepath.Join(config.GetDataDir(), name)
|
||||
return utils2.EncodeGob(outPath, res)
|
||||
}
|
||||
|
||||
func TestFactors(args []string) error {
|
||||
parser := flag.NewFlagSet("", flag.ExitOnError)
|
||||
var monNum, outNum, offNum int
|
||||
var facStr, outBy string
|
||||
parser.IntVar(&monNum, "mon", 2, "month for period")
|
||||
parser.IntVar(&outNum, "out", 3, "output group num")
|
||||
parser.IntVar(&offNum, "off", 0, "skip group num in head")
|
||||
parser.StringVar(&facStr, "fac", "", "factors to test")
|
||||
parser.StringVar(&outBy, "outBy", "", "calc output score by")
|
||||
err_ := parser.Parse(args)
|
||||
if err_ != nil {
|
||||
return err_
|
||||
}
|
||||
facArr := utils2.SplitSolid(facStr, ",", true)
|
||||
if len(facArr) == 0 {
|
||||
return errors.New("-fac is required")
|
||||
}
|
||||
dayNum := monNum * 30
|
||||
fac := &FacHis{Pairs: make(map[string]*ItemReturns), Data: make(map[int64]map[string]*PairSample)}
|
||||
facPath := filepath.Join(config.GetDataDir(), fmt.Sprintf("facHis_%dM.gob", monNum))
|
||||
err := utils2.DecodeGobFile(facPath, fac)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groupMeas := make(map[string][]*GpState)
|
||||
for i, stampMS := range fac.Times {
|
||||
if i <= offNum {
|
||||
continue
|
||||
}
|
||||
pairSamples := fac.Data[stampMS]
|
||||
items := make([]*FacState, 0)
|
||||
for pair, smp := range pairSamples {
|
||||
ret := fac.Pairs[pair]
|
||||
items = append(items, &FacState{
|
||||
Ret: ret,
|
||||
Sample: smp,
|
||||
Groups: make(map[string]string),
|
||||
})
|
||||
}
|
||||
// 计算每个采样所属的因子分组
|
||||
for _, factor := range facArr {
|
||||
var pos []int
|
||||
var tags []string
|
||||
if factor == "marketCap" {
|
||||
pos, tags = facMarketCap(items)
|
||||
} else if factor == "vol1M" {
|
||||
// 优于vol2w和marketCap
|
||||
pos, tags = facVol1M(items)
|
||||
} else if factor == "volStd" {
|
||||
// 成交量波动较低的
|
||||
pos, tags = facVolStd(items)
|
||||
} else if factor == "mont1" {
|
||||
// 3M下,跟mont2不相上下
|
||||
pos, tags = facMont1(items, dayNum)
|
||||
} else if factor == "mont2" {
|
||||
pos, tags = facMont2(items, dayNum)
|
||||
} else if factor == "mont1y" {
|
||||
pos, tags = facMont1Y(items, dayNum)
|
||||
} else if factor == "sharpe1y" {
|
||||
pos, tags = facSharpe1Y(items, dayNum)
|
||||
} else if factor == "score1y" {
|
||||
pos, tags = facScore1Y(items, dayNum)
|
||||
} else {
|
||||
return errors.New("unknown factor: " + factor)
|
||||
}
|
||||
if len(pos)+1 != len(tags) {
|
||||
return errors.New("invalid factor: " + factor)
|
||||
}
|
||||
start := 0
|
||||
for i, end := range pos {
|
||||
tag := tags[i]
|
||||
for j := start; j < end; j++ {
|
||||
items[j].Groups[factor] = tag
|
||||
}
|
||||
start = end
|
||||
}
|
||||
tag := tags[len(tags)-1]
|
||||
for j := start; j < len(items); j++ {
|
||||
items[j].Groups[factor] = tag
|
||||
}
|
||||
}
|
||||
// 组合因子分组
|
||||
groups := make(map[string][]*FacState)
|
||||
for _, sta := range items {
|
||||
var b strings.Builder
|
||||
for _, f := range facArr {
|
||||
b.WriteString(sta.Groups[f] + "+")
|
||||
}
|
||||
sta.Label = strings.TrimRight(b.String(), "+")
|
||||
gpItems, _ := groups[sta.Label]
|
||||
groups[sta.Label] = append(gpItems, sta)
|
||||
end := sta.Sample.Offset + dayNum
|
||||
if end > len(sta.Ret.Arr) {
|
||||
end = len(sta.Ret.Arr)
|
||||
}
|
||||
// 计算未来分数,并进行分组打分
|
||||
futures := sta.Ret.Arr[sta.Sample.Offset:end]
|
||||
if outBy == "profit" {
|
||||
sta.OutScore = floats.Sum(futures)
|
||||
} else if outBy == "sharpe" {
|
||||
sta.OutScore, err_ = utils2.SharpeRatioBy(futures, 0.02, 365, true)
|
||||
if err_ != nil {
|
||||
return err_
|
||||
}
|
||||
} else if outBy == "smart" || outBy == "" {
|
||||
sharpe, err_ := utils2.SharpeRatioBy(futures, 0.02, 365, true)
|
||||
if err_ != nil {
|
||||
return err_
|
||||
}
|
||||
profit := floats.Sum(futures)
|
||||
sta.OutScore = math.Log2(profit) * sharpe
|
||||
} else {
|
||||
return errors.New("unknown outBy: " + outBy)
|
||||
}
|
||||
}
|
||||
// 按输出分数降序
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].OutScore < items[j].OutScore
|
||||
})
|
||||
for i, it := range items {
|
||||
it.OutID = i
|
||||
}
|
||||
// 计算每个目标块应包含的FacState数量
|
||||
blockNum := int(math.Ceil(float64(len(items)) / float64(outNum)))
|
||||
for key, gpList := range groups {
|
||||
// 遍历分组中的每个FacState,根据其OutID确定所属块
|
||||
blocks, ok := groupMeas[key]
|
||||
if !ok {
|
||||
blocks = make([]*GpState, outNum)
|
||||
for i := 0; i < outNum; i++ {
|
||||
blocks[i] = &GpState{}
|
||||
}
|
||||
groupMeas[key] = blocks
|
||||
}
|
||||
for _, state := range gpList {
|
||||
blockID := state.OutID / blockNum // 确定目标块ID
|
||||
if blockID >= outNum {
|
||||
blockID = outNum - 1 // 处理边界情况
|
||||
}
|
||||
|
||||
// 更新该块的统计信息
|
||||
block := blocks[blockID]
|
||||
block.count++
|
||||
block.sumScore += utils2.NanInfTo(state.OutScore, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
countDsList := make([]*opt.ChartDs, len(groupMeas)) // 用于绘制数量分布图
|
||||
scoreDsList := make([]*opt.ChartDs, len(groupMeas)) // 用于绘制平均分数图
|
||||
// 生成x轴标签
|
||||
xLabels := make([]string, outNum)
|
||||
for i := 0; i < outNum; i++ {
|
||||
xLabels[i] = fmt.Sprintf("Block%d", i+1)
|
||||
}
|
||||
|
||||
keys := utils2.KeysOfMap(groupMeas)
|
||||
slices.Sort(keys)
|
||||
for i, key := range keys {
|
||||
counts := make([]float64, outNum)
|
||||
for j := 0; j < outNum; j++ {
|
||||
counts[j] = groupMeas[key][j].count
|
||||
}
|
||||
countDsList[i] = &opt.ChartDs{
|
||||
Label: key,
|
||||
Data: counts,
|
||||
}
|
||||
scores := make([]float64, outNum)
|
||||
for j := 0; j < outNum; j++ {
|
||||
bk := groupMeas[key][j]
|
||||
scores[j] = bk.sumScore / bk.count
|
||||
}
|
||||
scoreDsList[i] = &opt.ChartDs{
|
||||
Label: key,
|
||||
Data: scores,
|
||||
}
|
||||
}
|
||||
|
||||
facNames := strings.Join(facArr, "+")
|
||||
|
||||
// 生成数量分布折线图
|
||||
countPath := filepath.Join(config.GetDataDir(), fmt.Sprintf("fac_count_%dM.html", monNum))
|
||||
err = opt.DumpChart(countPath, "Distribution: "+facNames, xLabels, 5, nil, countDsList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成平均分数折线图
|
||||
scorePath := filepath.Join(config.GetDataDir(), fmt.Sprintf("fac_score_%dM.html", monNum))
|
||||
err = opt.DumpChart(scorePath, "Average Scores: "+facNames, xLabels, 5, nil, scoreDsList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(fmt.Sprintf("generate in BanDataDir: fac_score_%dM.html fac_count_%dM.html", monNum, monNum))
|
||||
return nil
|
||||
}
|
||||
|
||||
func facMarketCap(items []*FacState) ([]int, []string) {
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Sample.MarketCap - a.Sample.MarketCap) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facVol1M(items []*FacState) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
start := max(0, len(it.Sample.Vols)-30)
|
||||
it.Tmp = floats.Sum(it.Sample.Vols[start:])
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facVolStd(items []*FacState) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
arr := it.Sample.Vols
|
||||
data := make([]float64, 0, len(arr))
|
||||
for i, v := range arr[1:] {
|
||||
data = append(data, v/arr[i])
|
||||
}
|
||||
it.Tmp = utils2.StdDevVolatility(data, 1)
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facMont1(items []*FacState, unitDays int) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
start := max(0, it.Sample.Offset-unitDays)
|
||||
it.Tmp = floats.Sum(it.Ret.Arr[start:it.Sample.Offset])
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facMont2(items []*FacState, unitDays int) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
start := max(0, it.Sample.Offset-unitDays*2)
|
||||
it.Tmp = floats.Sum(it.Ret.Arr[start:it.Sample.Offset])
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facMont1Y(items []*FacState, unitDays int) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
start := max(0, it.Sample.Offset-unitDays*6)
|
||||
it.Tmp = floats.Sum(it.Ret.Arr[start:it.Sample.Offset])
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facSharpe1Y(items []*FacState, unitDays int) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
start := max(0, it.Sample.Offset-unitDays*6)
|
||||
arr := it.Ret.Arr[start:it.Sample.Offset]
|
||||
sharpe, err := utils2.SharpeRatioBy(arr, 0.02, 365, true)
|
||||
if err != nil {
|
||||
log.Info("calc sharpe fail", zap.Error(err))
|
||||
}
|
||||
it.Tmp = sharpe
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
|
||||
func facScore1Y(items []*FacState, unitDays int) ([]int, []string) {
|
||||
for _, it := range items {
|
||||
start := max(0, it.Sample.Offset-unitDays*6)
|
||||
arr := it.Ret.Arr[start:it.Sample.Offset]
|
||||
sharpe, err_ := utils2.SharpeRatioBy(arr, 0.02, 365, true)
|
||||
if err_ != nil {
|
||||
log.Error("calc sharpe fail", zap.Error(err_))
|
||||
}
|
||||
profit := floats.Sum(arr)
|
||||
it.Tmp = math.Log2(profit) * sharpe
|
||||
}
|
||||
slices.SortFunc(items, func(a, b *FacState) int {
|
||||
return int((b.Tmp - a.Tmp) / 1000)
|
||||
})
|
||||
totalNum := float64(len(items))
|
||||
bigNum := int(totalNum * 0.3)
|
||||
midEnd := int(totalNum*0.4) + bigNum
|
||||
return []int{bigNum, midEnd}, []string{"big", "mid", "sml"}
|
||||
}
|
||||
159
arb/common.go
Normal file
159
arb/common.go
Normal 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
|
||||
}
|
||||
52
arb/stat_group.go
Normal file
52
arb/stat_group.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package arb
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func StatGroupPairs() *errs.Error {
|
||||
exgId := "binance"
|
||||
endMS := config.TimeRange.EndMS
|
||||
// 生成待订阅标的列表
|
||||
exsMap := orm.GetExSymbols(exgId, banexg.MarketLinear)
|
||||
limit := 720
|
||||
exsList, barList, _, err := getExsBars(exg.Default, exsMap, "1h", 0, endMS, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arr := make([]*core.StrFloat, 0, len(exsList))
|
||||
for i, exs := range exsList {
|
||||
var sumVal float64
|
||||
bars := barList[i]
|
||||
if float64(len(bars)) < float64(limit)*0.9 {
|
||||
continue
|
||||
}
|
||||
for _, b := range bars {
|
||||
sumVal += b.Volume * b.Close
|
||||
}
|
||||
arr = append(arr, &core.StrFloat{
|
||||
Str: exs.Symbol,
|
||||
Val: sumVal,
|
||||
})
|
||||
}
|
||||
// 按成交量降序,取前30%
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
return arr[i].Val > arr[j].Val
|
||||
})
|
||||
cutNum := int(float64(len(arr)) * 0.2)
|
||||
cutPairs := make([]string, 0, cutNum)
|
||||
for i := 0; i < cutNum; i++ {
|
||||
base, _, _, _ := core.SplitSymbol(arr[i].Str)
|
||||
cutPairs = append(cutPairs, base)
|
||||
}
|
||||
log.Info("cut 20% of pairs", zap.Int("num", cutNum), zap.Int("total", len(arr)), zap.Strings("items", cutPairs))
|
||||
return nil
|
||||
}
|
||||
271
arb/stat_job.go
Normal file
271
arb/stat_job.go
Normal 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})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
373
arb/stat_mean.go
Normal file
373
arb/stat_mean.go
Normal file
@@ -0,0 +1,373 @@
|
||||
package arb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"github.com/banbox/banta"
|
||||
"go.uber.org/zap"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
统计套利:对两个交易对的价格比率下注
|
||||
价格比率较稳定的:
|
||||
快速穿越大趋势跟随开单
|
||||
超出上下轨,回弹交叉开单
|
||||
价格比率明显单边趋势:
|
||||
在标准差30%以内交叉,跟随大趋势方向开单
|
||||
|
||||
强劲单边跟随:
|
||||
平时价格比率小幅上下波动,但偶尔有强劲单边价格剧烈运动:GLM/USDT:USDT, XEM/USDT:USDT 20241001
|
||||
|
||||
瞬时极端波动:
|
||||
某段时间,价格比率快速上下极端波动,GLM/USDT:USDT, XEM/USDT:USDT 20241025
|
||||
|
||||
*/
|
||||
|
||||
func MeanRegressStat(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
var sta *StatTwo
|
||||
var jobs = make(map[string]*strat.StratJob)
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 1000,
|
||||
BatchInOut: true,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
jobs[s.Symbol.Symbol] = s
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
if sta == nil {
|
||||
sta = newStatTwo(jobs, nil)
|
||||
}
|
||||
e := s.Env
|
||||
if e.TimeStart > sta.barMS {
|
||||
sta.barMS = e.TimeStart
|
||||
}
|
||||
sta.OnPrice(s.Symbol.Symbol, e.Close.Get(0))
|
||||
},
|
||||
OnBatchJobs: func(jobs []*strat.StratJob) {
|
||||
sta.onStampDone()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
slopeDist = 150
|
||||
maSlopeDist = 500
|
||||
)
|
||||
|
||||
type StatTwo struct {
|
||||
barMS int64 // 收到最新bar的时间戳
|
||||
prices map[string]float64 // 当前时间戳下,所有品种的价格
|
||||
jobs map[string]*strat.StratJob // 所有的任务
|
||||
cacheSize int // Tuple中保留历史数据的最小长度
|
||||
lock sync.Mutex
|
||||
Tuples map[string]map[string]*Tuple
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
A *strat.StratJob
|
||||
B *strat.StratJob
|
||||
env *banta.BarEnv
|
||||
raw *banta.Series
|
||||
maFast *banta.Series
|
||||
maSlow *banta.Series
|
||||
maBig *banta.Series
|
||||
rawSbBig *banta.Series // sma(raw-maBig, 500)
|
||||
faXSl int // fast slow交叉:1快线上穿慢线;-1快线下穿慢线
|
||||
rawXBig int // raw big交叉
|
||||
maSlope float64 // ma斜率(和500个之前)
|
||||
slope float64 // 价格比率斜率(和150个之前)
|
||||
sort float64 // 排序分数,降序
|
||||
stdDev float64 // 标准差
|
||||
allowType int // 允许开单类型: 1突破布林带回弹;2快速穿越大趋势线;3中心交叉跟随趋势
|
||||
allowDirt int // 允许开单方向: 1做多A做空B; -1做空A做多B
|
||||
openType int // 开单类型:从allowType赋值
|
||||
openRate float64 // 开单的价格比率,0表示未开单
|
||||
openAt int64 // 开单的时间戳
|
||||
}
|
||||
|
||||
func newStatTwo(jobs map[string]*strat.StratJob, tuples [][]string) *StatTwo {
|
||||
tupleMap := make(map[string]map[string]*Tuple)
|
||||
res := &StatTwo{
|
||||
cacheSize: 2000,
|
||||
jobs: jobs,
|
||||
Tuples: tupleMap,
|
||||
}
|
||||
if len(tuples) == 0 {
|
||||
codes := make([]string, 0, len(jobs))
|
||||
for key := range jobs {
|
||||
codes = append(codes, key)
|
||||
}
|
||||
for i := 0; i < len(codes); i++ {
|
||||
for j := i + 1; j < len(codes); j++ {
|
||||
tuples = append(tuples, []string{codes[i], codes[j]})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(tuples) > 7000 {
|
||||
log.Warn("too many tuples may cause OOM!", zap.Int("num", len(tuples)))
|
||||
}
|
||||
for _, va := range tuples {
|
||||
a, b := va[0], va[1]
|
||||
if a > b {
|
||||
a, b = b, a
|
||||
}
|
||||
subMap, ok := tupleMap[a]
|
||||
if !ok {
|
||||
subMap = make(map[string]*Tuple)
|
||||
tupleMap[a] = subMap
|
||||
}
|
||||
tup, ok := subMap[b]
|
||||
if !ok {
|
||||
baseA, _, _, _ := core.SplitSymbol(a)
|
||||
baseB, _, _, _ := core.SplitSymbol(b)
|
||||
symbol := fmt.Sprintf("%s/%s", baseA, baseB)
|
||||
env, _ := banta.NewBarEnv(core.ExgName, core.Market, symbol, "1m")
|
||||
env.MaxCache = res.cacheSize
|
||||
raw := env.NewSeries(nil)
|
||||
A, _ := jobs[a]
|
||||
B, _ := jobs[b]
|
||||
tup = &Tuple{
|
||||
A: A,
|
||||
B: B,
|
||||
raw: raw,
|
||||
env: env,
|
||||
}
|
||||
subMap[b] = tup
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *StatTwo) OnPrice(symbol string, price float64) {
|
||||
s.lock.Lock()
|
||||
s.prices[symbol] = price
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *StatTwo) popTuples() []*Tuple {
|
||||
s.lock.Lock()
|
||||
tasks := make(map[string]map[string]bool)
|
||||
for codeA := range s.prices {
|
||||
subMap := make(map[string]bool)
|
||||
for codeB := range s.prices {
|
||||
if codeA >= codeB {
|
||||
continue
|
||||
}
|
||||
subMap[codeB] = true
|
||||
}
|
||||
tasks[codeA] = subMap
|
||||
}
|
||||
var res = make([]*Tuple, 0, len(s.prices)*2)
|
||||
for codeA, subMap := range tasks {
|
||||
for codeB := range subMap {
|
||||
ratio := s.prices[codeA] / s.prices[codeB]
|
||||
t := s.tuple(codeA, codeB, ratio)
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
res = append(res, t)
|
||||
}
|
||||
}
|
||||
s.prices = make(map[string]float64)
|
||||
s.lock.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *StatTwo) onStampDone() {
|
||||
tuples := s.popTuples()
|
||||
// 筛选两种开单类型
|
||||
size := len(tuples) / 3
|
||||
list1 := make([]*Tuple, 0, size)
|
||||
list2 := make([]*Tuple, 0, size)
|
||||
list3 := make([]*Tuple, 0, size)
|
||||
for _, t := range tuples {
|
||||
t.calcInOuts()
|
||||
if t.allowType == 1 {
|
||||
list1 = append(list1, t)
|
||||
} else if t.allowType == 2 {
|
||||
list2 = append(list2, t)
|
||||
} else if t.allowType == 3 {
|
||||
list3 = append(list3, t)
|
||||
}
|
||||
}
|
||||
var list = list1
|
||||
if len(list) == 0 {
|
||||
if len(list2) == 0 {
|
||||
if len(list3) == 0 {
|
||||
return
|
||||
}
|
||||
list = list3
|
||||
} else {
|
||||
list = list2
|
||||
}
|
||||
}
|
||||
// 按分数降序,取前三个中没有开单的
|
||||
var it *Tuple
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].sort > list[j].sort
|
||||
})
|
||||
gap := len(list) / 2
|
||||
list = append(list[gap:], list[:gap]...)
|
||||
for i, v := range list {
|
||||
if v.openType == 0 && v.A.OrderNum == 0 && v.B.OrderNum == 0 {
|
||||
it = v
|
||||
break
|
||||
}
|
||||
if i == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if it == nil {
|
||||
return
|
||||
}
|
||||
// 下单
|
||||
shortA := it.allowDirt < 0
|
||||
errA := it.A.OpenOrder(&strat.EnterReq{
|
||||
Tag: strconv.Itoa(it.allowType),
|
||||
Short: shortA,
|
||||
})
|
||||
if errA == nil {
|
||||
errB := it.B.OpenOrder(&strat.EnterReq{
|
||||
Tag: strconv.Itoa(it.allowType),
|
||||
Short: !shortA,
|
||||
})
|
||||
if errB != nil {
|
||||
it.A.Entrys = nil
|
||||
} else {
|
||||
it.openType = it.allowType
|
||||
it.openRate = it.raw.Get(0)
|
||||
it.openAt = it.env.TimeStop
|
||||
}
|
||||
}
|
||||
//dateStr := btime.ToDateStr(s.barMS, core.DefaultDateFmt)
|
||||
//fmt.Printf("%s %s - %s type=%v sort=%.3f slope=%.3f bigSlope=%.3f\n", dateStr, it.A.Symbol.Symbol,
|
||||
// it.B.Symbol.Symbol, it.allowType, it.sort, it.slope, it.maSlope)
|
||||
}
|
||||
|
||||
func (s *StatTwo) tuple(a, b string, ratio float64) *Tuple {
|
||||
if a > b {
|
||||
a, b = b, a
|
||||
ratio = 1 / ratio
|
||||
}
|
||||
subMap, ok := s.Tuples[a]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
res, ok := subMap[b]
|
||||
if ok {
|
||||
res.update(s.barMS, ratio)
|
||||
return res
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tuple) update(stamp int64, ratio float64) {
|
||||
t.env.TimeStart = stamp
|
||||
t.env.TimeStop = stamp + t.env.TFMSecs
|
||||
t.env.BarNum += 1
|
||||
t.raw.Append(ratio)
|
||||
t.maFast = banta.EMA(t.raw, 24)
|
||||
t.maSlow = banta.EMA(t.raw, 66)
|
||||
t.maBig = banta.EMA(t.raw, 1333)
|
||||
t.rawSbBig = banta.SMA(t.raw.Sub(t.maBig), 500)
|
||||
t.faXSl = t.maFast.Cross(t.maSlow)
|
||||
t.rawXBig = t.raw.Cross(t.maBig)
|
||||
sdev := banta.StdDev(t.raw, 1333)
|
||||
t.stdDev = banta.SMA(sdev, 800).Get(0) * 1.7
|
||||
if t.raw.Len() > slopeDist {
|
||||
// 计算近期斜率,优先用基于历史150偏移计算
|
||||
// 当基于均线历史150偏移的绝对变动区间更小时,基于均线计算
|
||||
old := t.raw.Get(slopeDist)
|
||||
oldMa := t.maBig.Get(slopeDist)
|
||||
slope := ratio/old - 1
|
||||
slopeMa := ratio/oldMa - 1
|
||||
if math.Abs(slopeMa) < math.Abs(slope) {
|
||||
slope = slopeMa
|
||||
}
|
||||
t.slope = slope
|
||||
}
|
||||
if t.maBig.Len() > maSlopeDist {
|
||||
t.allowType = 0
|
||||
t.sort = 0
|
||||
t.maSlope = t.maBig.Get(0)/t.maBig.Get(maSlopeDist) - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tuple) calcInOuts() {
|
||||
if t.maBig.Len() < maSlopeDist {
|
||||
return
|
||||
}
|
||||
//fastVal, fastVal30 := t.maFast.Get(0), t.maFast.Get(30)
|
||||
//if t.rawXBig == -1 || t.rawXBig == 1 {
|
||||
// // 快速穿越大趋势,开单
|
||||
// fastSlope := math.Abs(fastVal/fastVal30 - 1)
|
||||
// distRate := t.rawSbBig.Get(0) / stdDev
|
||||
// if fastSlope > 0.01 && distRate*float64(t.rawXBig) < 0 && math.Abs(distRate) > 0.7 {
|
||||
// // 要求穿过大趋势线时,具备较高的斜率,且过去500个平均与大周期的差超过70%标准差
|
||||
// t.allowType = 2
|
||||
// t.allowDirt = t.rawXBig
|
||||
// t.sort = math.Abs(distRate)
|
||||
// }
|
||||
//}
|
||||
maSlopeAbs := math.Abs(t.maSlope)
|
||||
//diff := math.Abs(t.slope - t.maSlope)
|
||||
//if maSlopeAbs < min(math.Abs(t.slope), 0.02) && diff > 0.015 {
|
||||
// // 突破布林带上下轨,回弹时开单
|
||||
// bigVal := t.maBig.Get(0)
|
||||
// slowPrev := t.maSlow.Get(60)
|
||||
// // 按maSlope进行惩罚,极端0.02时削减64%
|
||||
// sortV := diff * math.Pow(1-maSlopeAbs*20, 2)
|
||||
// if t.faXSl == -1 && max(fastVal, fastVal30) > bigVal+stdDev {
|
||||
// // 均线上方,突破上界,快线下穿慢线
|
||||
// if fastVal > slowPrev {
|
||||
// t.allowType = 1
|
||||
// t.allowDirt = t.faXSl
|
||||
// t.sort = sortV
|
||||
// }
|
||||
// } else if t.faXSl == 1 && min(fastVal, fastVal30) < bigVal-stdDev {
|
||||
// // 均线下方,突破下界,快线上穿慢线
|
||||
// if fastVal < slowPrev {
|
||||
// t.allowType = 1
|
||||
// t.allowDirt = t.faXSl
|
||||
// t.sort = sortV
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
nearToCenter := math.Abs(t.raw.Get(0)-t.maBig.Get(0)) < t.stdDev*0.3
|
||||
if maSlopeAbs > 0.02 && nearToCenter && (t.faXSl == 1 || t.faXSl == -1) && t.maSlope*float64(t.faXSl) > 0 {
|
||||
// 明显大趋势,在中心附近交叉,按大趋势方向开单
|
||||
t.allowType = 3
|
||||
t.allowDirt = t.faXSl
|
||||
t.sort = maSlopeAbs
|
||||
}
|
||||
closeOd := false
|
||||
if t.openType == 2 {
|
||||
// 根据穿越大趋势开单:回穿大趋势平仓、快线慢线交叉平仓
|
||||
if t.faXSl*t.allowDirt == -1 || t.rawXBig*t.allowDirt == -1 {
|
||||
closeOd = true
|
||||
}
|
||||
} else if t.openType == 1 && t.faXSl*t.allowDirt == -1 {
|
||||
// 根据突破上下轨回弹开单,(盈利达到标准差30% || 持有时间超过700个bar):快线慢线交叉平仓
|
||||
pft30 := (t.raw.Get(0)-t.openRate)*float64(t.allowDirt) > t.stdDev*0.3
|
||||
//costBarNum := int((t.env.TimeStop - t.openAt) / t.env.TFMSecs)
|
||||
if pft30 { //|| costBarNum > 1000 {
|
||||
closeOd = true
|
||||
}
|
||||
} else if t.openType == 3 && t.faXSl*t.allowDirt == -1 {
|
||||
// 中心附近交叉,跟随趋势的,再次交叉时平仓
|
||||
closeOd = true
|
||||
}
|
||||
if closeOd {
|
||||
t.A.CloseOrders(&strat.ExitReq{Tag: "exit"})
|
||||
t.B.CloseOrders(&strat.ExitReq{Tag: "exit"})
|
||||
t.openType = 0
|
||||
t.openRate = 0
|
||||
t.openAt = 0
|
||||
}
|
||||
}
|
||||
348
arb/stat_prof.go
Normal file
348
arb/stat_prof.go
Normal file
@@ -0,0 +1,348 @@
|
||||
package arb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
"github.com/banbox/banstagy/com"
|
||||
"go.uber.org/zap"
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IJV struct {
|
||||
I int
|
||||
J int
|
||||
V float64
|
||||
}
|
||||
|
||||
func ProfTuples() *errs.Error {
|
||||
exgId := "binance"
|
||||
tf := "1m"
|
||||
endMS := config.TimeRange.EndMS
|
||||
// 生成待订阅标的列表
|
||||
jobs := make(map[string]*strat.StratJob)
|
||||
exsMap := orm.GetExSymbols(exgId, banexg.MarketLinear)
|
||||
exsList, barList, jobs, err := getExsBars(exg.Default, exsMap, "1h", 0, endMS, 720)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 先从1h级别过滤,筛选比率较稳定的,取前10%,最多2k个
|
||||
maLen := 17
|
||||
stepMa := 8 //对应480分钟
|
||||
ts := make([]*IJV, 0, len(exsList)*2)
|
||||
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)
|
||||
if len(barsRes) == 0 {
|
||||
continue
|
||||
}
|
||||
a, b := barsRes[0], barsRes[1]
|
||||
if len(a) <= 100 {
|
||||
continue
|
||||
}
|
||||
slopes := make([]float64, 0, 90)
|
||||
sum := float64(0)
|
||||
avgs := make([]float64, 0, len(a))
|
||||
for m := 0; m < len(a); m++ {
|
||||
sum += a[m].Close / b[m].Close
|
||||
if m >= maLen {
|
||||
sum -= a[m-maLen].Close / b[m-maLen].Close
|
||||
avg := sum / float64(maLen)
|
||||
avgs = append(avgs, avg)
|
||||
if len(avgs)%stepMa == 1 && len(avgs) > stepMa {
|
||||
slopes = append(slopes, math.Abs(avg/avgs[len(avgs)-stepMa-1]-1)*100)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(slopes) < 20 {
|
||||
continue
|
||||
}
|
||||
avgSlope := floats.Sum(slopes) / float64(len(slopes))
|
||||
if avgSlope < 0.05 {
|
||||
continue
|
||||
}
|
||||
ts = append(ts, &IJV{I: i, J: j, V: avgSlope})
|
||||
}
|
||||
}
|
||||
sort.Slice(ts, func(i, j int) bool {
|
||||
return ts[i].V < ts[j].V
|
||||
})
|
||||
tops := ts[:min(2000, int(float64(len(ts))*0.1))]
|
||||
log.Info("cut top tuples", zap.Int("num", len(tops)), zap.Int("all", len(ts)))
|
||||
var tuples = make([][]string, 0, len(tops))
|
||||
its := make([]*com.CodeVal, 0, len(tops))
|
||||
for _, t := range tops {
|
||||
ca, cb := exsList[t.I].Symbol, exsList[t.J].Symbol
|
||||
tuples = append(tuples, []string{ca, cb})
|
||||
codeA, _, _, _ := core.SplitSymbol(ca)
|
||||
codeB, _, _, _ := core.SplitSymbol(cb)
|
||||
if codeA > codeB {
|
||||
codeA, codeB = codeB, codeA
|
||||
}
|
||||
its = append(its, &com.CodeVal{Code: codeA + "/" + codeB, Val: t.V})
|
||||
}
|
||||
printCodeVals(its)
|
||||
sta := newStatTwo(jobs, tuples)
|
||||
barNum := 0
|
||||
// 输出csv信息
|
||||
var datas = make(map[string][]float64)
|
||||
// 每行表示一对;每列表示一个时间帧
|
||||
var rows = make(map[string][]float64)
|
||||
colCount := 0 // 列数量,即时间采样数量
|
||||
// 进度条
|
||||
totalSecs := (endMS - config.TimeRange.StartMS) / 1000
|
||||
pbar := utils.NewPrgBar(int(totalSecs), "Run")
|
||||
defer pbar.Close()
|
||||
onStaBatch := func(arr []*Tuple) {
|
||||
if barNum%500 != 0 && barNum < 1800 {
|
||||
return
|
||||
}
|
||||
colCount += 1
|
||||
for _, it := range arr {
|
||||
code := it.env.Symbol
|
||||
if math.IsNaN(it.maSlope) {
|
||||
continue
|
||||
}
|
||||
absVal := math.Abs(it.maSlope * 100)
|
||||
|
||||
old, _ := datas[code]
|
||||
datas[code] = append(old, absVal)
|
||||
|
||||
col, _ := rows[code]
|
||||
if len(col) < colCount {
|
||||
col = append(col, make([]float64, colCount-len(col))...)
|
||||
}
|
||||
rows[code] = append(col, absVal)
|
||||
}
|
||||
}
|
||||
// Kline回调
|
||||
onBar := func(b *orm.InfoKline, nexts []*orm.InfoKline) {
|
||||
if b.Time > sta.barMS {
|
||||
if !b.IsWarmUp && sta.barMS > 0 {
|
||||
pbar.Add(int((b.Time - sta.barMS) / 1000))
|
||||
}
|
||||
barNum += 1
|
||||
sta.barMS = b.Time
|
||||
arr := sta.popTuples()
|
||||
if !b.IsWarmUp {
|
||||
onStaBatch(arr)
|
||||
}
|
||||
}
|
||||
sta.OnPrice(b.Symbol, b.Close)
|
||||
}
|
||||
var replayErr *errs.Error
|
||||
verCh := make(chan int, 1)
|
||||
onData := func(evt *orm.DataSeries, _ []*orm.DataSeries) {
|
||||
bar, err_ := orm.AsKline(evt)
|
||||
if err_ != nil {
|
||||
replayErr = errs.New(errs.CodeRunTime, err_)
|
||||
select {
|
||||
case verCh <- -1:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
onBar(bar, nil)
|
||||
}
|
||||
err = biz.RunHistSeries(&biz.RunHistSeriesArgs{
|
||||
ExsList: exsList,
|
||||
Start: config.TimeRange.StartMS,
|
||||
End: config.TimeRange.EndMS,
|
||||
VerCh: verCh,
|
||||
OnData: onData,
|
||||
TfWarms: map[string]int{tf: 0}, // 无需预热,必须所有bar一同运行
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if replayErr != nil {
|
||||
return replayErr
|
||||
}
|
||||
// 计算平均大趋势斜率
|
||||
var items = make([]*com.CodeVal, 0, len(datas))
|
||||
var avgs = make(map[string]float64)
|
||||
for code, arr := range datas {
|
||||
avgVal := floats.Sum(arr) / float64(len(arr))
|
||||
items = append(items, &com.CodeVal{Code: code, Val: avgVal})
|
||||
avgs[code] = avgVal
|
||||
}
|
||||
printCodeVals(items)
|
||||
// 生成csv文件
|
||||
outRows := make([][]string, 0)
|
||||
for key, row := range rows {
|
||||
if len(row) < colCount {
|
||||
row = append(row, make([]float64, colCount-len(row))...)
|
||||
rows[key] = row
|
||||
}
|
||||
out := make([]string, 0, colCount+2)
|
||||
out = append(out, key, strconv.FormatFloat(avgs[key], 'f', 2, 64))
|
||||
for _, v := range row {
|
||||
out = append(out, strconv.FormatFloat(v, 'f', 2, 64))
|
||||
}
|
||||
outRows = append(outRows, out)
|
||||
}
|
||||
return utils.WriteCsvFile("D:/slopes.csv", outRows, false)
|
||||
}
|
||||
|
||||
func getExsBars(exchange banexg.BanExchange, exsMap map[int32]*orm.ExSymbol, tf string, startMS, endMS int64, limit int) ([]*orm.ExSymbol, [][]*banexg.Kline, map[string]*strat.StratJob, *errs.Error) {
|
||||
_, err := orm.LoadMarkets(exg.Default, false)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
err = orm.BulkDownOHLCV(exchange, exsMap, tf, startMS, endMS, limit, nil)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
exsList := make([]*orm.ExSymbol, 0, len(exsMap))
|
||||
barList := make([][]*banexg.Kline, 0, len(exsMap))
|
||||
jobs := make(map[string]*strat.StratJob)
|
||||
for _, exs := range exsMap {
|
||||
_, quote, _, ident := core.SplitSymbol(exs.Symbol)
|
||||
if quote != "USDT" || ident != "" {
|
||||
continue
|
||||
}
|
||||
_, bars, err := orm.GetOHLCV(exs, tf, startMS, endMS, limit, false)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
exsList = append(exsList, exs)
|
||||
barList = append(barList, bars)
|
||||
jobs[exs.Symbol] = &strat.StratJob{Symbol: exs}
|
||||
}
|
||||
return exsList, barList, jobs, nil
|
||||
}
|
||||
|
||||
func getAlignBars(items ...[]*banexg.Kline) [][]*banexg.Kline {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(items) == 1 {
|
||||
return items
|
||||
}
|
||||
first := items[0]
|
||||
if len(first) == 0 {
|
||||
return nil
|
||||
}
|
||||
headLen, headStart, headStop := len(first), first[0].Time, first[len(first)-1].Time
|
||||
allAlign, minLen := true, headLen
|
||||
for _, a := range items {
|
||||
if len(a) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(a) != headLen || a[0].Time != headStart || a[len(a)-1].Time != headStop {
|
||||
allAlign = false
|
||||
}
|
||||
minLen = min(minLen, len(a))
|
||||
}
|
||||
if allAlign {
|
||||
return items
|
||||
}
|
||||
var resList = make([][]*banexg.Kline, len(items))
|
||||
var indexs = make([]int, len(items))
|
||||
started := false
|
||||
reachEnd := false
|
||||
for {
|
||||
stampCounts := make(map[int64]int)
|
||||
maxNum, mostTime, maxTime := 0, int64(0), int64(0)
|
||||
for i, idx := range indexs {
|
||||
b := items[i][idx]
|
||||
num, _ := stampCounts[b.Time]
|
||||
stampCounts[b.Time] = num + 1
|
||||
if num >= maxNum {
|
||||
maxNum, mostTime = num+1, b.Time
|
||||
}
|
||||
maxTime = max(maxTime, b.Time)
|
||||
}
|
||||
idxChange := false
|
||||
if len(stampCounts) > 1 {
|
||||
// 至少2个不同时间戳,将较旧的移动
|
||||
if maxNum <= len(indexs)/2 {
|
||||
mostTime = maxTime
|
||||
}
|
||||
for i, idx := range indexs {
|
||||
arr := items[i]
|
||||
for arr[idx].Time < mostTime {
|
||||
idx += 1
|
||||
if idx >= minLen {
|
||||
reachEnd = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx > indexs[i] {
|
||||
idxChange = true
|
||||
indexs[i] = idx
|
||||
}
|
||||
}
|
||||
} else if !started {
|
||||
started = true
|
||||
}
|
||||
if started && maxNum >= len(indexs)/2 {
|
||||
// 已开始,maxNum超过一半时有效
|
||||
for i, idx := range indexs {
|
||||
b := items[i][idx]
|
||||
res := resList[i]
|
||||
if b.Time > mostTime {
|
||||
b = res[len(res)-1]
|
||||
} else if b.Time < mostTime {
|
||||
panic("bad logic")
|
||||
} else {
|
||||
indexs[i] = idx + 1
|
||||
idxChange = true
|
||||
if idx+1 >= minLen {
|
||||
reachEnd = true
|
||||
}
|
||||
}
|
||||
resList[i] = append(res, b)
|
||||
}
|
||||
}
|
||||
if !idxChange {
|
||||
for i, idx := range indexs {
|
||||
arr := items[i]
|
||||
if arr[idx].Time <= mostTime {
|
||||
idx += 1
|
||||
indexs[i] = idx
|
||||
if idx >= minLen {
|
||||
reachEnd = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if reachEnd {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(resList[0]) == 0 {
|
||||
return nil
|
||||
}
|
||||
return resList
|
||||
}
|
||||
|
||||
func printCodeVals(items []*com.CodeVal) {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].Val < items[j].Val
|
||||
})
|
||||
var b strings.Builder
|
||||
for _, it := range items {
|
||||
b.WriteString(it.Code)
|
||||
b.WriteString(": ")
|
||||
b.WriteString(strconv.FormatFloat(it.Val, 'f', 2, 64))
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Printf(b.String())
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
203
arb/stat_showTwo.go
Normal file
203
arb/stat_showTwo.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package arb
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banbot/opt"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/utils"
|
||||
"github.com/banbox/banta"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GenTwoPairRatio() *errs.Error {
|
||||
//LineExamples()
|
||||
//return nil
|
||||
//groups := []string{"1000PEPE/USDT:USDT", "1000BONK/USDT:USDT"}
|
||||
groups := []string{
|
||||
"LPT,ATH,MOODENG,RNDR,APT,TRUMP,DOT,HYPE,BAKE,SPX,BOME,TUT,PUMP,BERA,OG,EOS,MERL,TAO,NMR,FET,TIA,FHE,ZORA,LINK,POL,TRX,1000PEPE,ETHFI,PROVE,TREE",
|
||||
"RED,SUI,VIRTUAL,NEIRO,GALA,TON,PAXG,XNY,CRV,NAORIS,XRP,NEAR,AVAX,ETC,XLM,MATIC,AAVE,SEI,1000SHIB,DAM,SPK,MEME,OP,FIL,KAITO,FARTCOIN,EIGEN,BCH,IP,XPL",
|
||||
}
|
||||
//startMS := config.TimeRange.StartMS
|
||||
endMS, _ := btime.ParseTimeMS("20250921")
|
||||
tf := "15m"
|
||||
core.Market = "linear"
|
||||
tfMSecs := int64(utils.TFToSecs(tf) * 1000)
|
||||
barLimit := 30000
|
||||
startMS := endMS - tfMSecs*int64(barLimit)
|
||||
barsArr := make([][]*TimeFlt, 0, len(groups))
|
||||
allPairs := strings.Split(strings.Join(groups, ","), ",")
|
||||
allPairs, err := config.ParsePairs(allPairs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exsMap := make(map[int32]*orm.ExSymbol)
|
||||
for _, pair := range allPairs {
|
||||
exs, err := orm.GetExSymbolCur(pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exsMap[exs.ID] = exs
|
||||
}
|
||||
err = orm.BulkDownOHLCV(exg.Default, exsMap, tf, startMS, endMS, 0, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, group := range groups {
|
||||
pairs := strings.Split(group, ",")
|
||||
prices, err := GetGroupPrices(pairs, tf, startMS, endMS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
barsArr = append(barsArr, prices)
|
||||
}
|
||||
rateArr := make([]*TimeFlt, len(barsArr[0]))
|
||||
for i, va := range barsArr[0] {
|
||||
rateArr[i] = &TimeFlt{
|
||||
Stamp: va.Stamp,
|
||||
Val: va.Val / barsArr[1][i].Val,
|
||||
}
|
||||
}
|
||||
path := "D:/ratios.html"
|
||||
title := "price ratio"
|
||||
return dumpRateChart(rateArr, tf, path, title)
|
||||
}
|
||||
|
||||
// GetGroupPrices 计算分组内多个品种的平均价格
|
||||
func GetGroupPrices(pairs []string, tf string, startMS, endMS int64) ([]*TimeFlt, *errs.Error) {
|
||||
var groupBar [][]*banexg.Kline
|
||||
pairs, err := config.ParsePairs(pairs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tfMSecs := int64(utils.TFToSecs(tf) * 1000)
|
||||
barLimit := (endMS - startMS) / tfMSecs
|
||||
for _, pair := range pairs {
|
||||
exs, err := orm.GetExSymbolCur(pair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, bars, err := orm.GetOHLCV(exs, tf, startMS, endMS, 0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if float64(len(bars)) > float64(barLimit)*0.8 {
|
||||
groupBar = append(groupBar, bars)
|
||||
}
|
||||
}
|
||||
// 对分组内品种计算价格归一化均值
|
||||
groupBar = getAlignBars(groupBar...)
|
||||
if len(groupBar) == 0 {
|
||||
return nil, errs.NewMsg(errs.CodeParamInvalid, "align bars fail for %v", pairs)
|
||||
}
|
||||
for _, gp := range groupBar {
|
||||
base := gp[0].Close
|
||||
gp[0].Close = 1
|
||||
for i := 1; i < len(gp); i++ {
|
||||
gp[i].Close /= base
|
||||
}
|
||||
}
|
||||
barNum := len(groupBar[0])
|
||||
bars := make([]*TimeFlt, 0, barNum)
|
||||
for j := 0; j < barNum; j++ {
|
||||
var sumVal float64
|
||||
for _, gp := range groupBar {
|
||||
sumVal += gp[j].Close
|
||||
}
|
||||
bars = append(bars, &TimeFlt{
|
||||
Stamp: groupBar[0][j].Time,
|
||||
Val: sumVal / float64(len(groupBar)),
|
||||
})
|
||||
}
|
||||
return bars, nil
|
||||
}
|
||||
|
||||
type TimeFlt struct {
|
||||
Stamp int64
|
||||
Val float64
|
||||
}
|
||||
|
||||
func dumpRateChart(rates []*TimeFlt, tf string, path, title string) *errs.Error {
|
||||
if len(rates) == 0 {
|
||||
return nil
|
||||
}
|
||||
var labels []string
|
||||
var arrRaw []float64
|
||||
var arrFast []float64
|
||||
var arrSlow []float64
|
||||
var arrMdMa []float64
|
||||
var arrBigMa []float64
|
||||
var arrBigUp []float64
|
||||
var arrBigDn []float64
|
||||
var arrHH, arrLL []float64
|
||||
|
||||
bollWid := 0.0043
|
||||
showNum := 1440
|
||||
stepFlt := float64(len(rates)) / float64(showNum)
|
||||
stepSize := int(math.Round(stepFlt))
|
||||
tfMSecs := int64(utils.TFToSecs(tf) * 1000)
|
||||
|
||||
lenFast := int(stepFlt * 5.77)
|
||||
lenSlow := int(stepFlt * 31.25)
|
||||
lenMid := int(stepFlt * 96.5)
|
||||
lenBig := int(stepFlt * 320.2)
|
||||
lenStd := int(stepFlt * 96.3)
|
||||
lenHL := int(stepFlt * 14.42)
|
||||
|
||||
var idx = 0
|
||||
env, _ := banta.NewBarEnv(core.ExgName, core.Market, "", tf)
|
||||
env.TimeStart = rates[idx].Stamp
|
||||
env.TimeStop = rates[idx].Stamp + tfMSecs
|
||||
rate := env.NewSeries(nil)
|
||||
rowId := 0
|
||||
dateFmt := "01-02 15:04"
|
||||
for idx < len(rates) {
|
||||
item := rates[idx]
|
||||
env.TimeStart += tfMSecs
|
||||
env.TimeStop += tfMSecs
|
||||
env.BarNum += 1
|
||||
rowId += 1
|
||||
ratio := item.Val
|
||||
rate.Append(ratio)
|
||||
maFast := banta.EMA(rate, lenFast)
|
||||
maSlow := banta.EMA(rate, lenSlow) //66
|
||||
maMid := banta.EMA(rate, lenMid).Get(0)
|
||||
maBig := banta.EMA(rate, lenBig)
|
||||
dev := banta.StdDev(rate, lenStd)
|
||||
stdDev := banta.SMA(dev, lenStd).Get(0) * 1.7
|
||||
bollWidAvg := bollWid*maMid*0.5 + stdDev*0.5
|
||||
hh := banta.Highest(rate, lenHL).Get(0)
|
||||
ll := banta.Lowest(rate, lenHL).Get(0)
|
||||
bigVal := maBig.Get(0)
|
||||
if rowId%stepSize == 0 && !math.IsNaN(maMid) {
|
||||
labels = append(labels, btime.ToDateStr(item.Stamp, dateFmt))
|
||||
arrRaw = append(arrRaw, ratio)
|
||||
arrFast = append(arrFast, maFast.Get(0))
|
||||
arrSlow = append(arrSlow, maSlow.Get(0))
|
||||
arrMdMa = append(arrMdMa, maMid)
|
||||
arrBigMa = append(arrBigMa, bigVal)
|
||||
arrBigUp = append(arrBigUp, maMid+bollWidAvg)
|
||||
arrBigDn = append(arrBigDn, maMid-bollWidAvg)
|
||||
arrHH = append(arrHH, hh)
|
||||
arrLL = append(arrLL, ll)
|
||||
}
|
||||
idx += 1
|
||||
}
|
||||
return opt.DumpChart(path, title, labels, 5, nil, []*opt.ChartDs{
|
||||
{Label: "raw", Data: arrRaw},
|
||||
{Label: "ma5", Data: arrFast, Hidden: true},
|
||||
{Label: "ma10", Data: arrSlow, Hidden: true},
|
||||
{Label: "mid", Data: arrMdMa},
|
||||
{Label: "big", Data: arrBigMa, Hidden: true},
|
||||
{Label: "upp", Data: arrBigUp, Hidden: false},
|
||||
{Label: "dnn", Data: arrBigDn, Hidden: false},
|
||||
//{Label: "hh", Data: arrHH, Hidden: true},
|
||||
//{Label: "ll", Data: arrLL, Hidden: true},
|
||||
})
|
||||
}
|
||||
31
arb/stat_spike.go
Normal file
31
arb/stat_spike.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package arb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banexg/errs"
|
||||
)
|
||||
|
||||
/*
|
||||
捕捉价格比率的瞬时剧烈波动
|
||||
*/
|
||||
|
||||
func FindTupleSpikes() *errs.Error {
|
||||
tf := "1m"
|
||||
endMS := config.TimeRange.EndMS
|
||||
items, err := findSpikeTuples(endMS, tf, 2000, 5, 5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("tuple,score,slope,pftRate,rawXBig")
|
||||
for _, t := range items {
|
||||
codeA, _, _, _ := core.SplitSymbol(t.A.Symbol)
|
||||
codeB, _, _, _ := core.SplitSymbol(t.B.Symbol)
|
||||
if codeA > codeB {
|
||||
codeA, codeB = codeB, codeA
|
||||
}
|
||||
fmt.Printf("%s/%s,%.2f,%.4f,%.3f,%v\n", codeA, codeB, t.Score, t.BigSlope, t.PftRate, t.RawXBig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
391
arb/stat_test.go
Normal file
391
arb/stat_test.go
Normal file
@@ -0,0 +1,391 @@
|
||||
package arb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/data"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banbot/goods"
|
||||
"github.com/banbox/banbot/orm"
|
||||
utils2 "github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
"github.com/banbox/banexg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPairPick(t *testing.T) {
|
||||
all := "ETH,BTC,SOL,DOGE,XRP,WLFI,MYX,ENA,BNB,1000PEPE,LINK,SUI,AVAX,ADA,PUMP,WLD,SOMI,HYPE,BIO,FARTCOIN,NMR,M,PENGU,PYTH,AAVE,LTC,HIFI,TA,WIF,ARB,RNDR,TRUMP,1000BONK,BCH,DOT,UNI,IP,BAKE,MATIC,FIL,EOS,TRX,NEAR,ONDO,XPL,1000SHIB,DOLO,PROMPT,CRV,OP,MEME,ETHFI,HBAR,XLM,ETC,TIA,LDO,SEI,TREE,EIGEN,TAO,XNY,SPK,APT,DAM,W,NAORIS,INJ,SAPIEN,JELLYJELLY,MOODENG,VIRTUAL,RED,TST,QTUM,MERL,TON,F,TUT,PNUT,LPT,SOLV,PENDLE,CFX,POL,KAITO,BERA,ZORA,GALA,FHE,PAXG,ONT,JUP,OG,FET,PUMPBTC,SKL,GAL,ATOM,NEIRO,BOME,MAV,PROVE,ATH,SPX"
|
||||
pickA, pickB := 30, 30
|
||||
coins := strings.Split(all, ",")
|
||||
rand.Shuffle(len(coins), func(i, j int) { coins[i], coins[j] = coins[j], coins[i] })
|
||||
groupA := coins[:pickA]
|
||||
groupB := coins[pickA : pickA+pickB]
|
||||
fmt.Println(strings.Join(groupA, ","))
|
||||
fmt.Println(strings.Join(groupB, ","))
|
||||
}
|
||||
|
||||
// 从高频逐笔数据中提取每隔5秒的价格
|
||||
func TestExtractPrices(t *testing.T) {
|
||||
core.SetRunMode(core.RunModeBackTest)
|
||||
err := biz.SetupComsExg(&config.CmdArgs{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loader, err := data.NewWsDataLoader()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dataDir := config.GetDataDir()
|
||||
outPath := filepath.Join(dataDir, "dump", "group1.gob")
|
||||
group1 := "LPT,ATH,MOODENG,RNDR,APT,TRUMP,DOT,HYPE,BAKE,SPX,BOME,TUT,PUMP,BERA,OG,EOS,MERL,TAO,NMR,FET,TIA,FHE,ZORA,LINK,POL,TRX,1000PEPE,ETHFI,PROVE,TREE"
|
||||
//group2 := "RED,SUI,VIRTUAL,NEIRO,GALA,TON,PAXG,XNY,CRV,NAORIS,XRP,NEAR,AVAX,ETC,XLM,MATIC,AAVE,SEI,1000SHIB,DAM,SPK,MEME,OP,FIL,KAITO,FARTCOIN,EIGEN,BCH,IP,XPL"
|
||||
pairs := strings.Split(group1, ",")
|
||||
pairs, err = config.ParsePairs(pairs...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result := make(map[string][]float64)
|
||||
if utils2.Exists(outPath) {
|
||||
err = utils2.DecodeGobFile(outPath, &result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cache := make(map[string]int)
|
||||
num := 0
|
||||
for k, v := range result {
|
||||
cache[k] = len(v)
|
||||
if len(v) > 0 {
|
||||
num += 1
|
||||
}
|
||||
}
|
||||
log.Info("load cache", zap.Int("num", num), zap.Any("keys", cache))
|
||||
}
|
||||
lock := deadlock.Mutex{}
|
||||
year, mon, day := time.Now().Add(-time.Hour * 24).Date()
|
||||
endTime := time.Date(year, mon, day, 0, 0, 0, 0, time.UTC)
|
||||
year, mon, day = endTime.AddDate(0, 0, -15).Date()
|
||||
startTime := time.Date(year, mon, day, 0, 0, 0, 0, time.UTC)
|
||||
gapMS := int64(5000) // 采样间隔ms
|
||||
startMS := startTime.UnixMilli()
|
||||
endMS := endTime.UnixMilli()
|
||||
size := int((endMS - startMS) / gapMS)
|
||||
pBar := utils2.NewPrgBar(len(pairs)*size, "run")
|
||||
defer pBar.Close()
|
||||
err = utils2.ParallelRun(pairs, 4, func(i int, pair string) *errs.Error {
|
||||
lock.Lock()
|
||||
arr, ok := result[pair]
|
||||
lock.Unlock()
|
||||
if ok && len(arr) > 0 {
|
||||
pBar.Add(size)
|
||||
return nil
|
||||
}
|
||||
arr = make([]float64, size+10)
|
||||
lastIdx := 0
|
||||
saveNum := 0
|
||||
curTime := startTime
|
||||
success := true
|
||||
for curTime.Before(endTime) {
|
||||
trades, err := loader.LoadTrades(&data.WsSymbol{
|
||||
ExgId: "binance",
|
||||
Market: banexg.MarketLinear,
|
||||
WsType: core.WsSubTrade,
|
||||
Symbol: pair,
|
||||
Date: curTime.Format(core.DateFmt),
|
||||
Hour: curTime.Hour(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("down trades fail", zap.Error(err))
|
||||
success = false
|
||||
break
|
||||
}
|
||||
curTime = curTime.Add(time.Hour)
|
||||
for _, td := range trades {
|
||||
curIdx := int((td.Timestamp - startMS) / gapMS)
|
||||
if curIdx > lastIdx {
|
||||
prevVal := arr[lastIdx]
|
||||
for lastIdx+1 < curIdx {
|
||||
lastIdx += 1
|
||||
arr[lastIdx] = prevVal
|
||||
}
|
||||
lastIdx = curIdx
|
||||
arr[curIdx] = td.Price
|
||||
}
|
||||
}
|
||||
if lastIdx > saveNum {
|
||||
pBar.Add(lastIdx - saveNum)
|
||||
saveNum = lastIdx
|
||||
}
|
||||
}
|
||||
if success {
|
||||
lock.Lock()
|
||||
result[pair] = arr
|
||||
lock.Unlock()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err = utils2.EncodeGob(outPath, result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试随机两组品种的平均价格比率(逐秒级别)
|
||||
func TestDayBatchRates(t *testing.T) {
|
||||
// 读取两组30个交易品种的每隔5s的价格序列,计算平均价比率,研究套利空间
|
||||
tf := "5s"
|
||||
dataDir := config.GetDataDir()
|
||||
outPath := filepath.Join(dataDir, "dump", "group1.gob")
|
||||
group1 := make(map[string][]float64)
|
||||
err := utils2.DecodeGobFile(outPath, &group1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outPath = filepath.Join(dataDir, "dump", "group2.gob")
|
||||
group2 := make(map[string][]float64)
|
||||
err = utils2.DecodeGobFile(outPath, &group2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tfSecs := utils.TFToSecs(tf)
|
||||
daySize := utils.TFToSecs("1d") / tfSecs
|
||||
dataLen := 0
|
||||
for _, arr := range group1 {
|
||||
dataLen = len(arr)
|
||||
break
|
||||
}
|
||||
tfMSecs := int64(tfSecs * 1000)
|
||||
year, mon, day := time.Now().Add(-time.Hour * 24).Date()
|
||||
endMS := time.Date(year, mon, day, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
startMS := endMS - int64(dataLen)*tfMSecs
|
||||
dumpDir := filepath.Join(config.GetDataDir(), "dump")
|
||||
for offset := 0; offset+daySize < dataLen; offset += daySize {
|
||||
offsetMS := startMS + int64(offset)*tfMSecs
|
||||
batchA := normInRange(group1, offset, offset+daySize, offsetMS, tfMSecs)
|
||||
batchB := normInRange(group2, offset, offset+daySize, offsetMS, tfMSecs)
|
||||
rateArr := make([]*TimeFlt, len(batchA))
|
||||
for i, va := range batchA {
|
||||
rateArr[i] = &TimeFlt{
|
||||
Stamp: va.Stamp,
|
||||
Val: va.Val / batchB[i].Val,
|
||||
}
|
||||
}
|
||||
dateStr := btime.ToDateStr(offsetMS, "20060102")
|
||||
dumpPath := filepath.Join(dumpDir, dateStr+".html")
|
||||
log.Info("dump rate chart", zap.String("title", dateStr))
|
||||
err = dumpRateChart(rateArr, tf, dumpPath, dateStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normInRange(dataMap map[string][]float64, start, end int, offsetMS, stepMS int64) []*TimeFlt {
|
||||
batchA := make(map[string][]float64)
|
||||
minLen := -1
|
||||
// 对每个品种的价格序列进行归一化
|
||||
for key, arr := range dataMap {
|
||||
vals := arr[start:end]
|
||||
res := make([]float64, len(vals))
|
||||
first := float64(0)
|
||||
for i, v := range vals {
|
||||
if v > 0 {
|
||||
if first > 0 {
|
||||
res[i] = v / first
|
||||
} else {
|
||||
res[i] = 1
|
||||
first = v
|
||||
}
|
||||
}
|
||||
}
|
||||
batchA[key] = res
|
||||
if minLen < 0 {
|
||||
minLen = len(res)
|
||||
} else {
|
||||
minLen = min(minLen, len(res))
|
||||
}
|
||||
}
|
||||
// 直接对所有品种价格序列取平均值
|
||||
res := make([]*TimeFlt, minLen)
|
||||
for i := 0; i < minLen; i++ {
|
||||
sumVal := float64(0)
|
||||
validNum := 0
|
||||
for _, arr := range batchA {
|
||||
val := arr[i]
|
||||
if val > 0 {
|
||||
sumVal += val
|
||||
validNum += 1
|
||||
}
|
||||
}
|
||||
item := &TimeFlt{
|
||||
Stamp: offsetMS + int64(i)*stepMS,
|
||||
}
|
||||
if validNum > 0 {
|
||||
item.Val = sumVal / float64(validNum)
|
||||
}
|
||||
res[i] = item
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/*
|
||||
每7日取成交量最高top60
|
||||
随机分为两组计算平均价格比率
|
||||
|
||||
获取7日成交量倒序前30%的品种列表
|
||||
每天计算:
|
||||
|
||||
计算最近14天1h的相关性矩阵
|
||||
按相关性从高到低取top60
|
||||
奇数位和偶数位分为两组
|
||||
|
||||
对比了按相关性从高到低取top60和随机分配top60,差别不大,简单起见,使用随机方法
|
||||
*/
|
||||
func TestDailyGroupArb(t *testing.T) {
|
||||
core.SetRunMode(core.RunModeBackTest)
|
||||
err := biz.SetupComsExg(&config.CmdArgs{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
groupSize := 30
|
||||
dayTf := "1d"
|
||||
dayMSecs := int64(utils.TFToSecs("1d") * 1000)
|
||||
year, mon, day := time.Now().Add(-time.Hour * 24).Date()
|
||||
endMS := time.Date(year, mon, day, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
startMS := endMS - dayMSecs*28
|
||||
minListMS := endMS - dayMSecs*180
|
||||
curMS := startMS
|
||||
exsMap := orm.GetExSymbols(core.ExgName, core.Market)
|
||||
pairs := make([]string, 0, len(exsMap))
|
||||
for _, exs := range exsMap {
|
||||
if exs.ListMs > minListMS {
|
||||
// 忽略上市不久的品种
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, exs.Symbol)
|
||||
}
|
||||
err = orm.BulkDownOHLCV(exg.Default, exsMap, "1h", startMS, endMS, 0, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var pre30Pairs []string
|
||||
var pre30Ms int64
|
||||
dumpDir := filepath.Join(config.GetDataDir(), "dump")
|
||||
for curMS < endMS {
|
||||
if len(pre30Pairs) == 0 || (curMS-pre30Ms)/dayMSecs >= 7 {
|
||||
// 获取14日成交量倒序前30%的品种列表
|
||||
pre30Pairs = nil
|
||||
symbolVols, err := goods.GetSymbolVols(pairs, dayTf, 14, curMS, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sort.Slice(symbolVols, func(i, j int) bool {
|
||||
return symbolVols[i].Vol > symbolVols[j].Vol
|
||||
})
|
||||
cutNum := int(float64(len(symbolVols)) * 0.23)
|
||||
for i := 0; i < cutNum; i++ {
|
||||
pre30Pairs = append(pre30Pairs, symbolVols[i].Symbol)
|
||||
}
|
||||
pre30Ms = curMS
|
||||
dateStr := btime.ToDateStr(curMS, core.DateFmt)
|
||||
log.Info("cut pre 30% pairs by vols", zap.Int("num", len(pre30Pairs)), zap.String("date", dateStr))
|
||||
}
|
||||
// 按成交量取前面的,然后随机挑选。
|
||||
coins := pre30Pairs[:groupSize*2]
|
||||
rand.Shuffle(len(coins), func(i, j int) { coins[i], coins[j] = coins[j], coins[i] })
|
||||
var pairsA, pairsB []string
|
||||
downMap := make(map[int32]*orm.ExSymbol)
|
||||
for i := 0; i < groupSize*2; i++ {
|
||||
pair := coins[i]
|
||||
if i%2 == 0 {
|
||||
pairsA = append(pairsA, pair)
|
||||
} else {
|
||||
pairsB = append(pairsB, pair)
|
||||
}
|
||||
exs, _ := orm.GetExSymbolCur(pair)
|
||||
downMap[exs.ID] = exs
|
||||
}
|
||||
// 对A和B两组计算1m加权价格
|
||||
curEndMS := curMS + dayMSecs
|
||||
err = orm.BulkDownOHLCV(exg.Default, downMap, "1m", curMS, curEndMS, 0, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pricesA, err := GetGroupPrices(pairsA, "1m", curMS, curEndMS)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pricesB, err := GetGroupPrices(pairsB, "1m", curMS, curEndMS)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rateArr := make([]*TimeFlt, len(pricesA))
|
||||
for i, va := range pricesA {
|
||||
rateArr[i] = &TimeFlt{
|
||||
Stamp: va.Stamp,
|
||||
Val: va.Val / pricesB[i].Val,
|
||||
}
|
||||
}
|
||||
dateStr := btime.ToDateStr(curMS, "20060102")
|
||||
dumpPath := filepath.Join(dumpDir, dateStr+"_r2.html")
|
||||
log.Info("dump rate chart", zap.String("title", dateStr))
|
||||
err = dumpRateChart(rateArr, "1m", dumpPath, dateStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
curMS += dayMSecs
|
||||
}
|
||||
}
|
||||
|
||||
func getPairCorrs(curMS int64, pairs []string) []string {
|
||||
// 计算最近14天1h的相关性矩阵
|
||||
dayMSecs := int64(utils.TFToSecs("1d") * 1000)
|
||||
hourMSecs := int64(utils.TFToSecs("1h") * 1000)
|
||||
pre14MS := curMS - dayMSecs*14
|
||||
names := make([]string, 0, len(pairs))
|
||||
dataArr := make([][]float64, 0, len(pairs))
|
||||
klineNum := int((curMS - pre14MS) / hourMSecs)
|
||||
err := orm.FastBulkOHLCV(exg.Default, pairs, "1h", pre14MS, curMS, 0, func(s string, s2 string, klines []*banexg.Kline, infos []*orm.AdjInfo) {
|
||||
if len(klines) == klineNum {
|
||||
prices := make([]float64, 0, len(klines))
|
||||
for _, b := range klines {
|
||||
prices = append(prices, b.Close)
|
||||
}
|
||||
names = append(names, s)
|
||||
dataArr = append(dataArr, prices[:klineNum])
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, avgs, err_ := utils2.CalcCorrMat(klineNum, dataArr, true)
|
||||
if err_ != nil {
|
||||
panic(err_)
|
||||
}
|
||||
// 基于平均相似度降序
|
||||
arr := make([]*goods.IdVal, 0, len(avgs))
|
||||
lefts := make(map[int]bool)
|
||||
for i, avg := range avgs {
|
||||
arr = append(arr, &goods.IdVal{Id: i, Val: avg})
|
||||
lefts[i] = true
|
||||
}
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
return arr[i].Val < arr[j].Val
|
||||
})
|
||||
var resPairs []string
|
||||
for _, v := range arr {
|
||||
resPairs = append(resPairs, names[v.Id])
|
||||
}
|
||||
return resPairs
|
||||
}
|
||||
91
com/common.go
Normal file
91
com/common.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/utils"
|
||||
ta "github.com/banbox/banta"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
)
|
||||
|
||||
const (
|
||||
MaSMA = "sma"
|
||||
MaEMA = "ema"
|
||||
MaRMA = "rma"
|
||||
MaWMA = "wma"
|
||||
MaVWMA = "vwma"
|
||||
MaHMA = "hma"
|
||||
MaKAMA = "kama"
|
||||
MaALMA = "alma"
|
||||
)
|
||||
|
||||
var MATypes = []string{MaSMA, MaEMA, MaRMA, MaWMA, MaVWMA, MaHMA, MaKAMA, MaALMA}
|
||||
|
||||
var maMap = make(map[string]bool)
|
||||
|
||||
func init() {
|
||||
for _, name := range MATypes {
|
||||
maMap[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func DefMaType(p *config.RunPolicyConfig, key, defVal string, allows ...string) string {
|
||||
if len(allows) == 0 {
|
||||
allows = MATypes
|
||||
} else {
|
||||
for _, k := range allows {
|
||||
if _, ok := maMap[k]; !ok {
|
||||
panic(fmt.Sprintf("unsupport maType: %s", k))
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := maMap[defVal]; !ok {
|
||||
panic(fmt.Sprintf("unsupport maType: %s", defVal))
|
||||
}
|
||||
defValInt := slices.Index(allows, defVal)
|
||||
if defValInt < 0 {
|
||||
defValInt = 0
|
||||
allows = append([]string{defVal}, allows...)
|
||||
}
|
||||
pVal := p.DefInt(key, defValInt, core.PUniform(0, float64(len(allows)-1)))
|
||||
return allows[pVal]
|
||||
}
|
||||
|
||||
func GetMA(price, vol *ta.Series, maType string, maLen int) *ta.Series {
|
||||
switch maType {
|
||||
case MaSMA:
|
||||
return ta.SMA(price, maLen)
|
||||
case MaEMA:
|
||||
return ta.EMA(price, maLen)
|
||||
case MaRMA:
|
||||
return ta.RMA(price, maLen)
|
||||
case MaWMA:
|
||||
return ta.WMA(price, maLen)
|
||||
case MaVWMA:
|
||||
return ta.VWMA(price, vol, maLen)
|
||||
case MaHMA:
|
||||
return ta.HMA(price, maLen)
|
||||
case MaKAMA:
|
||||
return ta.KAMA(price, maLen)
|
||||
case MaALMA:
|
||||
return ta.ALMA(price, maLen, 6, 0.85)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown maType: " + maType))
|
||||
}
|
||||
}
|
||||
|
||||
func GetTmpPath(name string) (string, error) {
|
||||
path, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpDir := filepath.Join(path, "tmp")
|
||||
err = utils.EnsureDir(tmpDir, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(tmpDir, name), nil
|
||||
}
|
||||
38
com/cross.go
Normal file
38
com/cross.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package com
|
||||
|
||||
import "math"
|
||||
|
||||
func NewCrossLog() *CrossLog {
|
||||
return &CrossLog{}
|
||||
}
|
||||
|
||||
type CrossLog struct {
|
||||
PrevVal float64
|
||||
Sign int // 最近一次交叉符号:1上穿,-1下穿
|
||||
XAt int // 最近一次交叉序号
|
||||
Num int // 当前序号
|
||||
}
|
||||
|
||||
func (l *CrossLog) Update(sub float64) {
|
||||
l.Num += 1
|
||||
if sub == 0 {
|
||||
return
|
||||
}
|
||||
if math.IsNaN(l.PrevVal) || l.PrevVal == 0 {
|
||||
l.PrevVal = sub
|
||||
} else {
|
||||
if factor := l.PrevVal * sub; factor < 0 {
|
||||
l.PrevVal = sub
|
||||
l.XAt = l.Num
|
||||
if sub > 0 {
|
||||
l.Sign = 1
|
||||
} else {
|
||||
l.Sign = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CrossLog) Get() int {
|
||||
return l.Sign * (l.Num - l.XAt + 1)
|
||||
}
|
||||
44
com/eff_trail_stop.go
Normal file
44
com/eff_trail_stop.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/utils"
|
||||
"math"
|
||||
)
|
||||
|
||||
type EFTRS struct {
|
||||
SLLong float64
|
||||
SLShort float64
|
||||
|
||||
EFLen int
|
||||
}
|
||||
|
||||
func NewEFTRS(efLen int) *EFTRS {
|
||||
return &EFTRS{EFLen: efLen}
|
||||
}
|
||||
|
||||
func (t *EFTRS) Calc(s *strat.StratJob, atr float64) (float64, float64) {
|
||||
e := s.Env
|
||||
c, c1 := e.Close.Get(0), e.Close.Get(1)
|
||||
totSum := utils.ArrSum(e.Close.Sub(e.Close.Back(1)).Abs().Range(0, t.EFLen))
|
||||
effRate := math.Abs(c-e.Close.Get(t.EFLen)) / totSum
|
||||
slLong := e.Low.Get(0) - atr
|
||||
slShort := e.High.Get(0) + atr
|
||||
if len(s.LongOrders) > 0 {
|
||||
if c > c1 {
|
||||
slLong = t.SLLong + effRate*(c-c1)
|
||||
} else {
|
||||
slLong = t.SLLong
|
||||
}
|
||||
}
|
||||
if len(s.ShortOrders) > 0 {
|
||||
if c < c1 {
|
||||
slShort = t.SLShort - effRate*(c1-c)
|
||||
} else {
|
||||
slShort = t.SLShort
|
||||
}
|
||||
}
|
||||
t.SLLong = slLong
|
||||
t.SLShort = slShort
|
||||
return slLong, slShort
|
||||
}
|
||||
28
com/ema.go
Normal file
28
com/ema.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package com
|
||||
|
||||
import "math"
|
||||
|
||||
type Ema struct {
|
||||
Alpha float64
|
||||
Val float64
|
||||
}
|
||||
|
||||
func NewEMA(alpha float64) *Ema {
|
||||
return &Ema{Alpha: alpha}
|
||||
}
|
||||
|
||||
func (e *Ema) Update(val float64) float64 {
|
||||
if math.IsNaN(val) || math.IsInf(val, 0) {
|
||||
return math.NaN()
|
||||
}
|
||||
if e.Val == 0 {
|
||||
e.Val = val
|
||||
} else {
|
||||
e.Val = e.Val*(1-e.Alpha) + val*e.Alpha
|
||||
}
|
||||
return e.Val
|
||||
}
|
||||
|
||||
func (e *Ema) Reset() {
|
||||
e.Val = 0
|
||||
}
|
||||
135
com/exm.go
Normal file
135
com/exm.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type ExmItem struct {
|
||||
BarNum int
|
||||
TimeMS int64
|
||||
Value float64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
type ExmTrace struct {
|
||||
Hist []*ExmItem
|
||||
Expect int // 1寻找极大值,-1寻找极小值
|
||||
PrevVals []float64 // 前一个值
|
||||
PrevDist float64 // 前两个极值的距离
|
||||
distList []float64 // 前面极值距离,保留最近3个
|
||||
}
|
||||
|
||||
/*
|
||||
Update
|
||||
val: 当前指标值
|
||||
pNum: 前一个索引
|
||||
ptime:前一个时间戳
|
||||
*/
|
||||
func (m *ExmTrace) Update(val float64, pNum int, ptime int64) bool {
|
||||
if math.IsNaN(val) {
|
||||
return false
|
||||
}
|
||||
if len(m.PrevVals) < 2 {
|
||||
// 至少积累2个
|
||||
if len(m.PrevVals) > 0 && m.PrevVals[len(m.PrevVals)-1] == val {
|
||||
// 和前一个值相同,跳过
|
||||
return false
|
||||
}
|
||||
m.PrevVals = append(m.PrevVals, val)
|
||||
return false
|
||||
}
|
||||
// 前1个,前2个
|
||||
p1, p2 := m.PrevVals[1], m.PrevVals[0]
|
||||
if p1 == val {
|
||||
// 当前值无效
|
||||
return false
|
||||
}
|
||||
// 更新
|
||||
m.PrevVals = append(m.PrevVals, val)
|
||||
m.PrevVals = m.PrevVals[len(m.PrevVals)-2:]
|
||||
pSub := p1 - p2
|
||||
sub := p1 - val
|
||||
var last *ExmItem
|
||||
if len(m.Hist) > 0 {
|
||||
last = m.Hist[len(m.Hist)-1]
|
||||
}
|
||||
getMinDist := func() float64 {
|
||||
if last == nil {
|
||||
return math.Abs(p1) * 0.3
|
||||
}
|
||||
if m.PrevDist != 0 {
|
||||
return m.PrevDist * 0.3
|
||||
}
|
||||
return max(math.Abs(last.Value), math.Abs(p1)) * 0.3
|
||||
}
|
||||
if pSub*sub <= 0 {
|
||||
// 非极大值或极小值
|
||||
if last != nil && !last.Valid && (val-last.Value)*float64(m.Expect) > getMinDist() {
|
||||
// 检查最后一个是否有效
|
||||
last.Valid = true
|
||||
if len(m.Hist) > 1 {
|
||||
dist := math.Abs(m.Hist[len(m.Hist)-2].Value - m.Hist[len(m.Hist)-1].Value)
|
||||
m.distList = append(m.distList, dist)
|
||||
if len(m.distList) > 3 {
|
||||
m.distList = m.distList[len(m.distList)-3:]
|
||||
}
|
||||
m.PrevDist = slices.Max(m.distList)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
valid := 0
|
||||
if sub > 0 {
|
||||
if m.Expect == 0 || m.Expect == 1 {
|
||||
// 新增极大值
|
||||
if last == nil || p1-last.Value >= getMinDist() {
|
||||
valid = -1
|
||||
}
|
||||
} else if p1 > last.Value {
|
||||
// 更新极大值
|
||||
valid = -1
|
||||
}
|
||||
} else if sub < 0 {
|
||||
if m.Expect == 0 || m.Expect == -1 {
|
||||
// 新增极小值
|
||||
if last == nil || last.Value-p1 >= getMinDist() {
|
||||
valid = 1
|
||||
}
|
||||
} else if p1 < last.Value {
|
||||
// 更新极小值
|
||||
valid = 1
|
||||
}
|
||||
}
|
||||
if valid != 0 {
|
||||
exm := &ExmItem{
|
||||
TimeMS: ptime,
|
||||
BarNum: pNum,
|
||||
Value: p1,
|
||||
}
|
||||
if m.Expect != valid {
|
||||
m.Hist = append(m.Hist, exm)
|
||||
//log.Info("add exm", zap.String("date", btime.ToDateStr(ptime, "")),
|
||||
// zap.Float64("val", p1))
|
||||
} else {
|
||||
m.Hist[len(m.Hist)-1] = exm
|
||||
//log.Info("update exm", zap.String("date", btime.ToDateStr(ptime, "")),
|
||||
// zap.Float64("val", p1))
|
||||
}
|
||||
if len(m.Hist) > 2 {
|
||||
dist := math.Abs(m.Hist[len(m.Hist)-3].Value - m.Hist[len(m.Hist)-2].Value)
|
||||
if len(m.distList) == 0 || dist != m.distList[len(m.distList)-1] {
|
||||
m.distList = append(m.distList, dist)
|
||||
if len(m.distList) > 3 {
|
||||
m.distList = m.distList[len(m.distList)-3:]
|
||||
}
|
||||
m.PrevDist = slices.Max(m.distList)
|
||||
}
|
||||
}
|
||||
m.Expect = valid
|
||||
m.PrevVals = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
41
com/inds.go
Normal file
41
com/inds.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package com
|
||||
|
||||
import "github.com/banbox/banta"
|
||||
|
||||
/*
|
||||
IchimokuCloud
|
||||
|
||||
sml: 9, mid: 26, big: 52, disp: 26
|
||||
return [smlMa, midMa, spanA, spanB]
|
||||
*/
|
||||
func IchimokuCloud(e *banta.BarEnv, sml, mid, big, disp int) *banta.Series {
|
||||
res := e.Close.To("_ichimokuCld", big*100000+mid*1000+disp*10+sml)
|
||||
if res.Cached() {
|
||||
return res
|
||||
}
|
||||
// earlier
|
||||
h := e.High.Back(disp - 1)
|
||||
l := e.Low.Back(disp - 1)
|
||||
hhSml := banta.Highest(h, sml).Get(0)
|
||||
llSml := banta.Lowest(l, sml).Get(0)
|
||||
hhMid := banta.Highest(h, mid).Get(0)
|
||||
llMid := banta.Lowest(l, mid).Get(0)
|
||||
hhBig := banta.Highest(h, big).Get(0)
|
||||
llBig := banta.Lowest(l, big).Get(0)
|
||||
|
||||
smlMa := (hhSml + llSml) / 2
|
||||
midMa := (hhMid + llMid) / 2
|
||||
bigMa := (hhBig + llBig) / 2
|
||||
spanA := (smlMa + midMa) / 2
|
||||
|
||||
// Current
|
||||
h, l = e.High, e.Low
|
||||
hhSml = banta.Highest(h, sml).Get(0)
|
||||
llSml = banta.Lowest(l, sml).Get(0)
|
||||
hhMid = banta.Highest(h, mid).Get(0)
|
||||
llMid = banta.Lowest(l, mid).Get(0)
|
||||
|
||||
smlMa2 := (hhSml + llSml) / 2
|
||||
midMa2 := (hhMid + llMid) / 2
|
||||
return res.Append([]float64{smlMa2, midMa2, spanA, bigMa})
|
||||
}
|
||||
65
com/slop.go
Normal file
65
com/slop.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Slop struct {
|
||||
lastCalc int
|
||||
sampleNum int
|
||||
Dirt int // 大周期斜率方向:1向上,0不明确,-1向下
|
||||
rate float64 // 斜率阈值,推荐0.25
|
||||
Data []float64
|
||||
Top float64
|
||||
Btm float64
|
||||
}
|
||||
|
||||
func NewSlop(sampleNum int, rate float64) *Slop {
|
||||
return &Slop{
|
||||
Dirt: 0,
|
||||
sampleNum: sampleNum,
|
||||
rate: rate,
|
||||
Top: math.NaN(),
|
||||
Btm: math.NaN(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Slop) Update(barNum int, slop float64) {
|
||||
s.Data = append(s.Data, math.Abs(slop))
|
||||
if len(s.Data) < s.sampleNum {
|
||||
return
|
||||
}
|
||||
if !math.IsNaN(s.Top) {
|
||||
s.Dirt = 0
|
||||
if slop >= s.Top {
|
||||
s.Dirt = 1
|
||||
} else if slop <= s.Btm {
|
||||
s.Dirt = -1
|
||||
}
|
||||
}
|
||||
if barNum-s.lastCalc < s.sampleNum/2 {
|
||||
return
|
||||
}
|
||||
if len(s.Data) > s.sampleNum {
|
||||
s.Data = s.Data[len(s.Data)-s.sampleNum:]
|
||||
}
|
||||
thres := CutScore(s.Data, s.rate)
|
||||
s.Top = thres
|
||||
s.Btm = -thres
|
||||
s.lastCalc = barNum
|
||||
s.Dirt = 0
|
||||
if slop >= s.Top {
|
||||
s.Dirt = 1
|
||||
} else if slop <= s.Btm {
|
||||
s.Dirt = -1
|
||||
}
|
||||
}
|
||||
|
||||
func CutScore(vals []float64, rate float64) float64 {
|
||||
arr := make([]float64, len(vals))
|
||||
copy(arr, vals)
|
||||
slices.Sort(arr)
|
||||
idx := int(float64(len(vals)) * rate)
|
||||
return arr[idx]
|
||||
}
|
||||
85
com/trailing_stop.go
Normal file
85
com/trailing_stop.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
type TRS struct {
|
||||
MaxLow float64
|
||||
MinHigh float64
|
||||
SLLong float64 // 止损的tr倍率,有持仓时,逐渐减小
|
||||
SLShort float64 // 止损的tr倍率,有持仓时,逐渐减小
|
||||
InitSLTR float64
|
||||
minSLTR float64
|
||||
stepSLTR float64
|
||||
}
|
||||
|
||||
func NewTRS(initSLTR, minRate, costStep float64) *TRS {
|
||||
var minSLTR = initSLTR * minRate
|
||||
var stepSLTR = (initSLTR - minSLTR) / costStep
|
||||
return &TRS{
|
||||
InitSLTR: initSLTR,
|
||||
minSLTR: minSLTR,
|
||||
stepSLTR: stepSLTR,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TRS) TrialLong(price float64) {
|
||||
m.SLLong = m.InitSLTR
|
||||
m.MaxLow = price
|
||||
}
|
||||
|
||||
func (m *TRS) TrialShort(price float64) {
|
||||
m.SLShort = m.InitSLTR
|
||||
m.MinHigh = price
|
||||
}
|
||||
|
||||
func (m *TRS) setTRS(e *ta.BarEnv, longNum, shortNum int) {
|
||||
h, l := e.High.Get(0), e.Low.Get(0)
|
||||
if longNum == 0 {
|
||||
if m.SLLong != m.InitSLTR {
|
||||
m.MaxLow = 0
|
||||
}
|
||||
} else {
|
||||
m.MaxLow = max(m.MaxLow, l)
|
||||
if m.SLLong > m.minSLTR+0.01 {
|
||||
m.SLLong -= m.stepSLTR
|
||||
}
|
||||
}
|
||||
if shortNum == 0 {
|
||||
if m.SLShort != m.InitSLTR {
|
||||
m.MinHigh = 0
|
||||
}
|
||||
} else {
|
||||
m.MinHigh = min(m.MinHigh, h)
|
||||
if m.SLShort > m.minSLTR+0.01 {
|
||||
m.SLShort -= m.stepSLTR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TRS) Calc(s *strat.StratJob, atr float64) (float64, float64) {
|
||||
m.setTRS(s.Env, len(s.LongOrders), len(s.ShortOrders))
|
||||
var stopLong, stopShort float64
|
||||
if m.MaxLow > 0 {
|
||||
stopLong = m.MaxLow - atr*m.SLLong
|
||||
}
|
||||
if m.MinHigh > 0 {
|
||||
stopShort = m.MinHigh + atr*m.SLShort
|
||||
}
|
||||
return stopLong, stopShort
|
||||
}
|
||||
|
||||
func (m *TRS) CalcSL(s *strat.StratJob, atr *ta.Series, lenAtr, lenAtrB int, pow, rate float64) (float64, float64) {
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), lenAtrB).Get(0)
|
||||
atrRate := math.Pow(atr.Get(0)/atrBase, pow)
|
||||
return m.Calc(s, atrBase*atrRate*rate)
|
||||
}
|
||||
|
||||
func (m *TRS) CalcSL2(s *strat.StratJob, lenAtr, lenAtrB int, pow, rate float64) (float64, float64) {
|
||||
e := s.Env
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
return m.CalcSL(s, atr, lenAtr, lenAtrB, pow, rate)
|
||||
}
|
||||
6
com/types.go
Normal file
6
com/types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package com
|
||||
|
||||
type CodeVal struct {
|
||||
Code string
|
||||
Val float64
|
||||
}
|
||||
51
com/utils.go
Normal file
51
com/utils.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func FastOpen(s *strat.StratJob, dirt, slLong, slShort, limit float64) {
|
||||
openTag := "long"
|
||||
stopLoss := slLong
|
||||
if dirt < 0 {
|
||||
openTag = "short"
|
||||
stopLoss = slShort
|
||||
}
|
||||
if !s.CanOpen(dirt < 0) {
|
||||
return
|
||||
}
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: openTag,
|
||||
Short: dirt < 0,
|
||||
Limit: limit,
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("open fail", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func MakePairInfos(warmNum int) func(s *strat.StratJob) []*strat.PairSub {
|
||||
return func(s *strat.StratJob) []*strat.PairSub {
|
||||
var bigTF string
|
||||
switch s.TimeFrame {
|
||||
case "1d":
|
||||
bigTF = "1w"
|
||||
case "4h":
|
||||
bigTF = "1d"
|
||||
case "1h":
|
||||
bigTF = "8h"
|
||||
case "15m":
|
||||
bigTF = "2h"
|
||||
case "5m":
|
||||
bigTF = "1h"
|
||||
case "1m":
|
||||
bigTF = "15m"
|
||||
}
|
||||
return []*strat.PairSub{
|
||||
{Pair: "_cur_", TimeFrame: bigTF, WarmupNum: warmNum},
|
||||
}
|
||||
}
|
||||
}
|
||||
79
demo/bar_agg.go
Normal file
79
demo/bar_agg.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
ta "github.com/banbox/banta"
|
||||
"go.uber.org/zap"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AggMore struct {
|
||||
bars []*banexg.Kline
|
||||
barsBig []*banexg.Kline
|
||||
}
|
||||
|
||||
func getBar(e *ta.BarEnv) *banexg.Kline {
|
||||
return &banexg.Kline{
|
||||
Time: e.TimeStart,
|
||||
Open: e.Open.Get(0),
|
||||
High: e.High.Get(0),
|
||||
Low: e.Low.Get(0),
|
||||
Close: e.Close.Get(0),
|
||||
Volume: e.Volume.Get(0),
|
||||
}
|
||||
}
|
||||
|
||||
func dumpKline(bars []*banexg.Kline, name string) *errs.Error {
|
||||
if len(bars) == 0 {
|
||||
return nil
|
||||
}
|
||||
outDir := config.GetDataDir()
|
||||
rows := utils.KlineToStr(bars, btime.UTCLocale)
|
||||
path := filepath.Join(outDir, "tmp", fmt.Sprintf("%s.csv", name))
|
||||
return utils.WriteCsvFile(path, rows, false)
|
||||
}
|
||||
|
||||
func BarAgg(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 1,
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "1h", 1},
|
||||
}
|
||||
},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &AggMore{}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
b := getBar(s.Env)
|
||||
m, _ := s.More.(*AggMore)
|
||||
m.bars = append(m.bars, b)
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
b := getBar(e)
|
||||
m, _ := s.More.(*AggMore)
|
||||
m.barsBig = append(m.barsBig, b)
|
||||
},
|
||||
OnShutDown: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*AggMore)
|
||||
code := strings.ReplaceAll(s.Symbol.Symbol, "/", "_")
|
||||
code = strings.ReplaceAll(code, ":", "_")
|
||||
err := dumpKline(m.bars, fmt.Sprintf("%s_%s", code, s.TimeFrame))
|
||||
if err != nil {
|
||||
log.Error("dump klines fail", zap.Error(err))
|
||||
}
|
||||
err = dumpKline(m.barsBig, fmt.Sprintf("%s_1h", code))
|
||||
if err != nil {
|
||||
log.Error("dump big klines fail", zap.Error(err))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
40
demo/bar_stat.go
Normal file
40
demo/bar_stat.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Stat struct {
|
||||
rates []float64
|
||||
}
|
||||
|
||||
func BarStat(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 50,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &Stat{}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*Stat)
|
||||
// rate := genCurFutProbs(s.Env)[0]
|
||||
m.rates = append(m.rates, 0)
|
||||
},
|
||||
OnShutDown: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*Stat)
|
||||
slices.Sort(m.rates)
|
||||
var sum float64
|
||||
var outs []string
|
||||
for _, v := range m.rates {
|
||||
sum += v
|
||||
outs = append(outs, fmt.Sprintf("%.2f", v*100))
|
||||
}
|
||||
log.Info("", zap.Float64("avg", sum/float64(len(m.rates))),
|
||||
zap.Strings("arr", outs))
|
||||
},
|
||||
}
|
||||
}
|
||||
48
demo/dca.go
Normal file
48
demo/dca.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
type DCAState struct {
|
||||
LastInvestTime int64 // 上次定投时间
|
||||
}
|
||||
|
||||
func DCA(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
intvDays := pol.DefInt("intvDays", 7, core.PNorm(1, 365))
|
||||
intvMSecs := int64(intvDays) * 86400000
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 1, // 定投策略不需要太多预热数据
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的状态
|
||||
s.More = &DCAState{
|
||||
LastInvestTime: 0,
|
||||
}
|
||||
},
|
||||
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
state, _ := s.More.(*DCAState)
|
||||
currentTime := s.Env.TimeStop
|
||||
|
||||
// 检查是否到了定投时间间隔
|
||||
if state.LastInvestTime > 0 && currentTime-state.LastInvestTime < intvMSecs {
|
||||
return // 还未到定投时间
|
||||
}
|
||||
|
||||
// 执行定投买入
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "buy",
|
||||
Log: true,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
// 更新上次定投时间
|
||||
state.LastInvestTime = currentTime
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
71
demo/demo.go
Normal file
71
demo/demo.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func Demo(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastPeriod := pol.DefInt("fastPeriod", 5, core.PNorm(3, 20))
|
||||
maRate := pol.Def("maRate", 4.0, core.PNorm(1.5, 9.0))
|
||||
|
||||
// 计算慢速均线周期
|
||||
slowPeriod := int(float64(fastPeriod) * maRate)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowPeriod + 10,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
maFast := ta.SMA(e.Close, fastPeriod) // 快速移动平均线
|
||||
maSlow := ta.SMA(e.Close, slowPeriod) // 慢速移动平均线
|
||||
maCrossState := maFast.Cross(maSlow) // 判断均线交叉状态
|
||||
|
||||
// 金叉:快线上穿慢线
|
||||
if maCrossState == 1 {
|
||||
// 先平掉空头持仓
|
||||
if len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "golden_cross_close_short",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
|
||||
// 再开多头仓位
|
||||
if len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "golden_cross_long",
|
||||
Short: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 死叉:快线下穿慢线
|
||||
if maCrossState == -1 {
|
||||
// 先平掉多头持仓
|
||||
if len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "death_cross_close_long",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
// 再开空头仓位
|
||||
if len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "death_cross_short",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
strat.StratMake["demo"] = Demo
|
||||
}
|
||||
43
demo/freqtrade/Strategy004.go
Normal file
43
demo/freqtrade/Strategy004.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package freqtrade
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
Strategy 004
|
||||
author@: Gerald Lonlas
|
||||
github@: https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/Strategy004.py
|
||||
*/
|
||||
func Strategy004(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
EachMaxLong: 1,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
adx := ta.ADX(e.High, e.Low, e.Close, 14).Get(0)
|
||||
adxSlow := ta.ADX(e.High, e.Low, e.Close, 35).Get(0)
|
||||
cci := ta.CCI(e.Close, 14).Get(0)
|
||||
fastD, _, fastK := ta.KDJBy(e.High, e.Low, e.Close, 5, 3, 3, "sma")
|
||||
fastDBig, _, fastKBig := ta.KDJBy(e.High, e.Low, e.Close, 50, 3, 3, "sma")
|
||||
ema := ta.EMA(e.Close, 5).Get(0)
|
||||
KDX := fastK.Cross(fastD)
|
||||
|
||||
adxOk := adx > 50 || adxSlow > 26
|
||||
fastKDOk := fastK.Get(1) < 20 && fastD.Get(1) < 20
|
||||
fastKDBigOk := fastKBig.Get(1) < 30 && fastDBig.Get(1) < 30
|
||||
|
||||
fastKDgt70 := fastK.Get(0) > 70 || fastD.Get(0) > 70
|
||||
if adxOk && cci < -100 && fastKDOk && fastKDBigOk && KDX == 1 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLossVal: ema * 0.1,
|
||||
})
|
||||
} else if adxSlow < 25 && fastKDgt70 && fastK.Get(1) < fastD.Get(1) && e.Close.Get(0) > ema {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exit"})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
26
demo/freqtrade/Strategy005.go
Normal file
26
demo/freqtrade/Strategy005.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package freqtrade
|
||||
|
||||
/*
|
||||
Strategy 005
|
||||
author@: Gerald Lonlas
|
||||
github@: https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/Strategy005.py
|
||||
*/
|
||||
|
||||
//func Strategy005(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// buyVolAvg := int(p.Def("buyVolAvg", 150, core.PNorm(50, 300)))
|
||||
// buyRsi := p.Def("buyRsi", 26, core.PNorm(1, 100))
|
||||
// buyFastD := p.Def("buyFastD", 1, core.PNorm(1, 100))
|
||||
// buyFishNo := p.Def("buyFishNo", 5, core.PNorm(1, 100))
|
||||
// sellRsi := p.Def("sellRsi", 74, core.PNorm(1, 100))
|
||||
// sellMiDi := p.Def("sellMiDi", 4, core.PNorm(1, 100))
|
||||
// sellFiRsiN := p.Def("sellFiRsiN", 30, core.PNorm(1, 100))
|
||||
// return &strat.TradeStrat{
|
||||
// WarmupNum: 100,
|
||||
// OnBar: func(s *strat.StratJob) {
|
||||
// e := s.Env
|
||||
// macdCols := ta.MACD(e.Close, 12, 26, 9).Cols
|
||||
// macd, macdSig := macdCols[0], macdCols[1]
|
||||
//
|
||||
// },
|
||||
// }
|
||||
//}
|
||||
11
demo/freqtrade/common.go
Normal file
11
demo/freqtrade/common.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package freqtrade
|
||||
|
||||
import "github.com/banbox/banbot/strat"
|
||||
|
||||
func init() {
|
||||
strat.AddStratGroup("freqtrade", map[string]strat.FuncMakeStrat{
|
||||
"Strategy001": Strategy001,
|
||||
"Strategy004": Strategy004,
|
||||
|
||||
})
|
||||
}
|
||||
46
demo/freqtrade/strategy001.go
Normal file
46
demo/freqtrade/strategy001.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package freqtrade
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
Strategy 001
|
||||
author@: Gerald Lonlas
|
||||
github@: https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/Strategy001.py
|
||||
*/
|
||||
|
||||
func Strategy001(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
lenSml := int(p.Def("lenSml", 20, core.PNorm(10, 30)))
|
||||
midRate := p.Def("midRate", 2.5, core.PNorm(1.5, 5))
|
||||
bigRate := p.Def("bigRate", 2, core.PNorm(1.5, 3))
|
||||
lenMid := int(float64(lenSml) * midRate)
|
||||
lenBig := int(float64(lenMid) * bigRate)
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 200,
|
||||
EachMaxLong: 1,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
maSml := ta.EMA(e.Close, lenSml)
|
||||
maMid := ta.EMA(e.Close, lenMid)
|
||||
maBig := ta.EMA(e.Close, lenBig)
|
||||
haO, _, _, haC := ta.HeikinAshi(e)
|
||||
haOpen := haO.Get(0)
|
||||
haClose := haC.Get(0)
|
||||
smlXmid := maSml.Cross(maMid)
|
||||
midXBig := maMid.Cross(maBig)
|
||||
|
||||
if smlXmid == 1 && haClose > maSml.Get(0) && haOpen < haClose {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLossVal: haClose * 0.1,
|
||||
})
|
||||
} else if midXBig == 1 && haClose < maSml.Get(0) && haOpen > haClose {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exit"})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
3
demo/here.java
Normal file
3
demo/here.java
Normal file
@@ -0,0 +1,3 @@
|
||||
public static void main (String[] args) {
|
||||
System.out.println("Hello World2!");
|
||||
}
|
||||
43
demo/hft.go
Normal file
43
demo/hft.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func hft(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WsSubs: map[string]string{
|
||||
core.WsSubDepth: "",
|
||||
core.WsSubTrade: "",
|
||||
core.WsSubKLine: "",
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
log.Info(fmt.Sprintf("OnBar %v: %v", e.TimeStop, e.Close.Get(0)))
|
||||
},
|
||||
OnWsKline: func(s *strat.StratJob, pair string, k *banexg.Kline) {
|
||||
log.Info(fmt.Sprintf("OnWsKline %v: %v", k.Time, k.Close))
|
||||
s.OpenOrder(&strat.EnterReq{Tag: "long"})
|
||||
_, _, err := biz.GetOdMgr(s.Account).ProcessOrders(s)
|
||||
if err != nil {
|
||||
log.Error("process order fail", zap.Error(err))
|
||||
}
|
||||
},
|
||||
OnWsTrades: func(s *strat.StratJob, pair string, trades []*banexg.Trade) {
|
||||
last := trades[len(trades)-1]
|
||||
log.Info(fmt.Sprintf("OnWsTrades %v %v, %v", last.Timestamp, last.Price, last.Amount))
|
||||
},
|
||||
OnWsDepth: func(s *strat.StratJob, dep *banexg.OrderBook) {
|
||||
bp1, bm1 := dep.Bids.Price[0], dep.Bids.Size[0]
|
||||
ap1, am1 := dep.Asks.Price[0], dep.Asks.Size[0]
|
||||
log.Info(fmt.Sprintf("OnWsDepth %v %v, %v,, %v, %v", dep.TimeStamp, bp1, bm1, ap1, am1))
|
||||
},
|
||||
}
|
||||
}
|
||||
19
demo/hold.go
Normal file
19
demo/hold.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
func Hold(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
EachMaxLong: 1,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
if len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "hold",
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
37
demo/holdSl.go
Normal file
37
demo/holdSl.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func HoldSL(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 20,
|
||||
EachMaxLong: 1,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
ma5 := ta.VWMA(e.Close, e.Volume, 5)
|
||||
ma20 := ta.VWMA(e.Close, e.Volume, 10)
|
||||
maCrx := ma5.Cross(ma20)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, 14).Get(0)
|
||||
|
||||
for _, od := range s.LongOrders {
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: e.Close.Get(0) - atr*0.5,
|
||||
})
|
||||
}
|
||||
if maCrx == -1 && len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exit", Dirt: core.OdDirtLong})
|
||||
} else if maCrx == 1 || len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLossVal: atr * 0.5,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
48
demo/limits.go
Normal file
48
demo/limits.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func DemoV1(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
dirt := -1.0
|
||||
limitRate := 0.001
|
||||
slRate := 0.005
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 30,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
if s.OrderNum == 0 {
|
||||
price := s.Env.Close.Get(0)
|
||||
limit := price * (1 - dirt*limitRate)
|
||||
stopLoss := price * (1 - dirt*slRate)
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "enter",
|
||||
Limit: limit,
|
||||
Short: dirt < 0,
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("open fail", zap.Error(err))
|
||||
} else {
|
||||
log.Info("new order", zap.Float64("limit", limit), zap.Float64("stopLoss", stopLoss))
|
||||
}
|
||||
}
|
||||
},
|
||||
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
||||
price := s.Env.Close.Get(0)
|
||||
stopLoss := price * (1 - dirt*slRate)
|
||||
sl := od.GetStopLoss()
|
||||
if sl != nil && (stopLoss-sl.Price)*dirt > 0 {
|
||||
// 有盈利,调整跟踪止损
|
||||
log.Info("set stoploss", zap.Float64("val", stopLoss))
|
||||
sl.Price = stopLoss
|
||||
od.DirtyInfo = true
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
31
demo/ma_er.go
Normal file
31
demo/ma_er.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func DemoER(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
smlLen := int(pol.Def("smlLen", 9, core.PNorm(3, 10)))
|
||||
bigLen := int(pol.Def("bigLen", 23, core.PNorm(10, 40)))
|
||||
erUpp := pol.Def("erUpp", 0.13, core.PNorm(0.1, 0.7))
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
ma5 := ta.SMA(e.Close, smlLen)
|
||||
ma20 := ta.SMA(e.Close, bigLen)
|
||||
maCrx := ma5.Cross(ma20)
|
||||
|
||||
er := ta.ER(e.Close, 50).Get(0)
|
||||
|
||||
if maCrx == 1 && er < erUpp {
|
||||
s.OpenOrder(&strat.EnterReq{Tag: "open"})
|
||||
} else if maCrx == -1 {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exit"})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
86
demo/made.go
Normal file
86
demo/made.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func Made(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastMAPeriod := pol.DefInt("fast_ma_period", 5, core.PUniform(3, 10))
|
||||
slowMAPeriod := pol.DefInt("slow_ma_period", 20, core.PUniform(10, 40))
|
||||
atrPeriod := pol.DefInt("atr_period", 14, core.PUniform(7, 21))
|
||||
slRate := pol.Def("sl_rate", 2.0, core.PUniform(1.0, 4.0))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
EachMaxLong: 1,
|
||||
EachMaxShort: 1,
|
||||
DrawDownExit: true, // 启用回撤止盈
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
maFast := ta.SMA(e.Close, fastMAPeriod)
|
||||
maSlow := ta.SMA(e.Close, slowMAPeriod)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
|
||||
// 计算均线交叉信号
|
||||
maCrossOver := maFast.Cross(maSlow) // 1 表示上穿
|
||||
maCrossUnder := maSlow.Cross(maFast) // 1 表示下穿
|
||||
|
||||
// 获取当前价格和ATR值
|
||||
currentPrice := e.Close.Get(0)
|
||||
currentATR := atr.Get(0)
|
||||
|
||||
// 多头入场信号:快线上穿慢线(金叉)
|
||||
if maCrossOver == 1 {
|
||||
if s.GetOrderNum(core.OdDirtShort) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{Dirt: core.OdDirtShort, Tag: "exit_short"})
|
||||
}
|
||||
if s.GetOrderNum(core.OdDirtLong) == 0 {
|
||||
stopLossPrice := currentPrice - currentATR*slRate
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLoss: stopLossPrice,
|
||||
StopLossTag: "atr_sl_long",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 空头入场信号:快线下穿慢线(死叉)
|
||||
if maCrossUnder == 1 {
|
||||
if s.GetOrderNum(core.OdDirtLong) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{Dirt: core.OdDirtLong, Tag: "exit_long"})
|
||||
}
|
||||
if s.GetOrderNum(core.OdDirtShort) == 0 {
|
||||
stopLossPrice := currentPrice + currentATR*slRate
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
Short: true,
|
||||
StopLoss: stopLossPrice,
|
||||
StopLossTag: "atr_sl_short",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 多头出场信号:快线下穿慢线(死叉)
|
||||
if maCrossUnder == 1 && s.GetOrderNum(core.OdDirtLong) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Dirt: core.OdDirtLong,
|
||||
Tag: "exit_long",
|
||||
})
|
||||
}
|
||||
|
||||
// 空头出场信号:快线上穿慢线(金叉)
|
||||
if maCrossOver == 1 && s.GetOrderNum(core.OdDirtShort) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Dirt: core.OdDirtShort,
|
||||
Tag: "exit_short",
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
26
demo/main.go
Normal file
26
demo/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
func init() {
|
||||
strat.AddStratGroup("demo", map[string]strat.FuncMakeStrat{
|
||||
"dca": DCA,
|
||||
"tmp": tmp,
|
||||
"01": DemoV1,
|
||||
"agg": BarAgg,
|
||||
"stat": BarStat,
|
||||
"demo": Demo,
|
||||
"demo_er": DemoER,
|
||||
"riskRw": fixRiskReward,
|
||||
"hold": Hold,
|
||||
"holdSL": HoldSL,
|
||||
"show": Show,
|
||||
"tmp1": Tmp1,
|
||||
"made": Made,
|
||||
"test": Test,
|
||||
"hft": hft,
|
||||
"TrailStop": TrailStop,
|
||||
})
|
||||
}
|
||||
97
demo/pairs_test.go
Normal file
97
demo/pairs_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banbot/goods"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func genVolPairs(timeMS int64) []string {
|
||||
err := biz.SetupComs(&config.CmdArgs{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = orm.LoadMarkets(exg.Default, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ft := goods.VolumePairFilter{
|
||||
BaseFilter: goods.BaseFilter{Name: "VolumePairFilter"},
|
||||
BackPeriod: "3d",
|
||||
}
|
||||
core.SetRunMode(core.RunModeBackTest)
|
||||
core.SetRunEnv(core.RunEnvProd)
|
||||
btime.CurTimeMS = timeMS
|
||||
config.PairMgr.Cron = ""
|
||||
pairs, err := ft.GenSymbols(btime.UTCStamp())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res := make([]string, 0, len(pairs))
|
||||
for _, pair := range pairs {
|
||||
_, quote, _, _ := core.SplitSymbol(pair)
|
||||
if _, ok := config.StakeCurrencyMap[quote]; ok {
|
||||
res = append(res, pair)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func TestRandPairs(t *testing.T) {
|
||||
timeMS, _ := btime.ParseTimeMS("2021-11-01 00:00")
|
||||
pairs := genVolPairs(timeMS)
|
||||
total := len(pairs)
|
||||
half := pairs[:total/3]
|
||||
for i := range half {
|
||||
j := rand.Intn(i + 1)
|
||||
if i != j {
|
||||
half[i], half[j] = half[j], half[i]
|
||||
}
|
||||
}
|
||||
cutNum := 50
|
||||
if len(half) > cutNum {
|
||||
half = half[:cutNum]
|
||||
}
|
||||
log.Info("pairs", zap.Int("total", total), zap.Strings("arr", half))
|
||||
}
|
||||
|
||||
func TestPairsLocs(t *testing.T) {
|
||||
pairs := genVolPairs(time.Now().UnixMilli())
|
||||
//pairs48 := []string{"1000FLOKI/USDT:USDT", "1000LUNC/USDT:USDT", "1000PEPE/USDT:USDT", "1000SHIB/USDT:USDT", "1000XEC/USDT:USDT", "ADA/USDT:USDT", "APT/USDT:USDT", "ARB/USDT:USDT", "AVAX/USDT:USDT", "BADGER/USDT:USDT", "BCH/USDT:USDT", "BNB/USDT:USDT", "BOME/USDT:USDT", "BTC/USDT:USDT", "CFX/USDT:USDT", "DOGE/USDT:USDT", "DOT/USDT:USDT", "ETC/USDT:USDT", "ETH/USDT:USDT", "ETHFI/USDT:USDT", "FET/USDT:USDT", "FIL/USDT:USDT", "FTM/USDT:USDT", "GALA/USDT:USDT", "HIFI/USDT:USDT", "ICP/USDT:USDT", "ID/USDT:USDT", "JUP/USDT:USDT", "LINK/USDT:USDT", "LTC/USDT:USDT", "LUNA2/USDT:USDT", "MATIC/USDT:USDT", "NEAR/USDT:USDT", "ONDO/USDT:USDT", "OP/USDT:USDT", "ORDI/USDT:USDT", "PENDLE/USDT:USDT", "PIXEL/USDT:USDT", "POLYX/USDT:USDT", "RSR/USDT:USDT", "SAND/USDT:USDT", "SOL/USDT:USDT", "STX/USDT:USDT", "SUI/USDT:USDT", "TOKEN/USDT:USDT", "TON/USDT:USDT", "WIF/USDT:USDT", "WLD/USDT:USDT", "XRP/USDT:USDT", "YGG/USDT:USDT"}
|
||||
pairs170 := []string{"1000BONK/USDT:USDT", "1000FLOKI/USDT:USDT", "1000LUNC/USDT:USDT", "1000PEPE/USDT:USDT", "1000RATS/USDT:USDT", "1000XEC/USDT:USDT", "1INCH/USDT:USDT", "AAVE/USDT:USDT", "ACH/USDT:USDT", "ADA/USDT:USDT", "AGIX/USDT:USDT", "ALGO/USDT:USDT", "ANKR/USDT:USDT", "APE/USDT:USDT", "API3/USDT:USDT", "APT/USDT:USDT", "AR/USDT:USDT", "ARB/USDT:USDT", "ARPA/USDT:USDT", "ASTR/USDT:USDT", "ATOM/USDT:USDT", "AUCTION/USDT:USDT", "AVAX/USDT:USDT", "AXL/USDT:USDT", "AXS/USDT:USDT", "BADGER/USDT:USDT", "BAKE/USDT:USDT", "BCH/USDT:USDT", "BEAMX/USDT:USDT", "BEL/USDT:USDT", "BICO/USDT:USDT", "BLUR/USDT:USDT", "BNB/USDT:USDT", "BNX/USDT:USDT", "BOND/USDT:USDT", "BSV/USDT:USDT", "BTC/USDT:USDT", "CAKE/USDT:USDT", "CELO/USDT:USDT", "CFX/USDT:USDT", "CHR/USDT:USDT", "CHZ/USDT:USDT", "COMP/USDT:USDT", "CYBER/USDT:USDT", "DOGE/USDT:USDT", "DOT/USDT:USDT", "DUSK/USDT:USDT", "DYM/USDT:USDT", "EGLD/USDT:USDT", "ENA/USDT:USDT", "ENJ/USDT:USDT", "EOS/USDT:USDT", "ETC/USDT:USDT", "ETH/USDT:USDT", "ETHW/USDT:USDT", "FET/USDT:USDT", "FIL/USDT:USDT", "FLM/USDT:USDT", "FLOW/USDT:USDT", "FRONT/USDT:USDT", "FTM/USDT:USDT", "GAL/USDT:USDT", "GALA/USDT:USDT", "GAS/USDT:USDT", "GLM/USDT:USDT", "GMT/USDT:USDT", "GMX/USDT:USDT", "GRT/USDT:USDT", "HBAR/USDT:USDT", "HIFI/USDT:USDT", "HOOK/USDT:USDT", "HOT/USDT:USDT", "ICP/USDT:USDT", "ID/USDT:USDT", "ILV/USDT:USDT", "IMX/USDT:USDT", "INJ/USDT:USDT", "IOTA/USDT:USDT", "IOTX/USDT:USDT", "JASMY/USDT:USDT", "JOE/USDT:USDT", "JTO/USDT:USDT", "JUP/USDT:USDT", "KAS/USDT:USDT", "KAVA/USDT:USDT", "KEY/USDT:USDT", "KNC/USDT:USDT", "KSM/USDT:USDT", "LDO/USDT:USDT", "LEVER/USDT:USDT", "LINA/USDT:USDT", "LINK/USDT:USDT", "LPT/USDT:USDT", "LQTY/USDT:USDT", "LRC/USDT:USDT", "LTC/USDT:USDT", "LUNA2/USDT:USDT", "MAGIC/USDT:USDT", "MANA/USDT:USDT", "MANTA/USDT:USDT", "MASK/USDT:USDT", "MATIC/USDT:USDT", "MAV/USDT:USDT", "MEME/USDT:USDT", "METIS/USDT:USDT", "MINA/USDT:USDT", "MKR/USDT:USDT", "MTL/USDT:USDT", "MYRO/USDT:USDT", "NEO/USDT:USDT", "NKN/USDT:USDT", "OCEAN/USDT:USDT", "OM/USDT:USDT", "ONDO/USDT:USDT", "ONE/USDT:USDT", "OP/USDT:USDT", "ORDI/USDT:USDT", "PENDLE/USDT:USDT", "PEOPLE/USDT:USDT", "POLYX/USDT:USDT", "PYTH/USDT:USDT", "QNT/USDT:USDT", "QTUM/USDT:USDT", "RDNT/USDT:USDT", "REN/USDT:USDT", "RIF/USDT:USDT", "RLC/USDT:USDT", "RSR/USDT:USDT", "RUNE/USDT:USDT", "RVN/USDT:USDT", "SEI/USDT:USDT", "SKL/USDT:USDT", "SNX/USDT:USDT", "SPELL/USDT:USDT", "SSV/USDT:USDT", "STG/USDT:USDT", "STMX/USDT:USDT", "STORJ/USDT:USDT", "STX/USDT:USDT", "SUI/USDT:USDT", "SXP/USDT:USDT", "T/USDT:USDT", "THETA/USDT:USDT", "TIA/USDT:USDT", "TOKEN/USDT:USDT", "TON/USDT:USDT", "TRU/USDT:USDT", "TRX/USDT:USDT", "UMA/USDT:USDT", "VET/USDT:USDT", "WAVES/USDT:USDT", "WAXP/USDT:USDT", "WIF/USDT:USDT", "WOO/USDT:USDT", "XAI/USDT:USDT", "XLM/USDT:USDT", "XRP/USDT:USDT", "XTZ/USDT:USDT", "XVG/USDT:USDT", "YFI/USDT:USDT", "YGG/USDT:USDT", "ZEC/USDT:USDT", "ZEN/USDT:USDT", "ZETA/USDT:USDT", "ZIL/USDT:USDT", "ZRX/USDT:USDT"}
|
||||
fails := make([]string, 0, 10)
|
||||
idArr := make([]int, 0, 100)
|
||||
for _, p := range pairs170 {
|
||||
idx := slices.Index(pairs, p)
|
||||
if idx < 0 {
|
||||
fails = append(fails, p)
|
||||
} else {
|
||||
idArr = append(idArr, idx)
|
||||
}
|
||||
}
|
||||
log.Warn("pairs", zap.Int("total", len(pairs)), zap.Strings("unknown", fails))
|
||||
sort.Slice(idArr, func(i, j int) bool {
|
||||
return idArr[i] < idArr[j]
|
||||
})
|
||||
step := 10
|
||||
end, num := step, 0
|
||||
for _, v := range idArr {
|
||||
if v >= end {
|
||||
log.Info("range", zap.Int("to", end), zap.Int("num", num))
|
||||
num = 0
|
||||
end += step
|
||||
}
|
||||
num += 1
|
||||
}
|
||||
}
|
||||
63
demo/risk_reward.go
Normal file
63
demo/risk_reward.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func fixRiskReward(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
rewardRate := 4.0
|
||||
return &strat.TradeStrat{
|
||||
EachMaxLong: 1,
|
||||
WarmupNum: 30,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, 14).Get(0)
|
||||
rsi := ta.RSI50(e.Close, 14).Get(0)
|
||||
ma := ta.SMA(e.Close, 5).Get(0)
|
||||
maMid := ta.SMA(e.Close, 10).Get(0)
|
||||
maBig := ta.SMA(e.Close, 40).Get(0)
|
||||
c := e.Close.Get(0)
|
||||
if s.OrderNum == 0 && maMid > maBig && rsi > 15 && c < ma {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLossVal: atr,
|
||||
StopLossTag: "init",
|
||||
})
|
||||
}
|
||||
},
|
||||
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
||||
sl := od.GetStopLoss()
|
||||
if sl.Tag == "take" {
|
||||
// 已设置止盈
|
||||
return nil
|
||||
}
|
||||
if sl.Tag == "init" {
|
||||
// 检查是否回本,如果是,设置止损到成本线
|
||||
if od.ProfitRate > 0 {
|
||||
// 保存初始止损空间
|
||||
od.SetInfo("initSL", sl.Range)
|
||||
feeRate := od.Enter.FeeQuote * 2 / od.EnterCost()
|
||||
slPrice := od.Enter.Average * (1 + feeRate)
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: slPrice,
|
||||
Tag: "break_even",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
//已设置成本止损,检查是否触发止盈
|
||||
slRange := od.GetInfoFloat64("initSL")
|
||||
if od.Profit > slRange*rewardRate {
|
||||
// 触发止盈线,设置止盈线止损
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: od.Enter.Average + slRange*rewardRate,
|
||||
Tag: "take",
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
25
demo/sample.go
Normal file
25
demo/sample.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func Sample(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
ma5 := ta.SMA(e.Close, 5)
|
||||
ma20 := ta.SMA(e.Close, 20)
|
||||
maCrx := ma5.Cross(ma20)
|
||||
|
||||
if maCrx == 1 {
|
||||
s.OpenOrder(&strat.EnterReq{Tag: "open"})
|
||||
} else if maCrx == -1 {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exit"})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
45
demo/show.go
Normal file
45
demo/show.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package demo
|
||||
|
||||
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"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func Show(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
pairMap := make(map[string]int)
|
||||
var copied []string
|
||||
var lastMS int64
|
||||
var lockPair sync.Mutex
|
||||
return &strat.TradeStrat{
|
||||
EachMaxLong: 1,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
lockPair.Lock()
|
||||
base, _, _, _ := core.SplitSymbol(s.Symbol.Symbol)
|
||||
pairMap[base] = 0
|
||||
lockPair.Unlock()
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
curMS := s.Env.TimeStop
|
||||
if curMS > lastMS {
|
||||
if len(copied) > 0 {
|
||||
date := btime.ToDateStr(lastMS, core.DefaultDateFmt)
|
||||
lockPair.Lock()
|
||||
totalNum := len(pairMap)
|
||||
lockPair.Unlock()
|
||||
log.Info("OnBar", zap.String("date", date), zap.Int("got", len(copied)),
|
||||
zap.Int("total", totalNum), zap.String("copied", strings.Join(copied, ",")))
|
||||
}
|
||||
lastMS = curMS
|
||||
copied = nil
|
||||
}
|
||||
base, _, _, _ := core.SplitSymbol(s.Symbol.Symbol)
|
||||
copied = append(copied, base)
|
||||
},
|
||||
}
|
||||
}
|
||||
45
demo/test.go
Normal file
45
demo/test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func Test(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
//- 超参(极简)
|
||||
highestDay := pol.DefInt("highest day", 20, core.PNorm(10, 40))
|
||||
atrN := pol.DefInt("atrn", 14, core.PNorm(10, 30))
|
||||
slNATR := pol.Def("sl natr", 2.0, core.PNorm(0.8, 4.0))
|
||||
tpNATR := pol.Def("tp natr", 2.5, core.PNorm(1.0, 5.0))
|
||||
//预热:技术指标所需长度的最大值 + 余量
|
||||
warm := max(highestDay+1, atrN+1, 20) + 2
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warm,
|
||||
RunTimeFrames: []string{"15m"},
|
||||
HedgeOff: true,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
if e.Close.Len() <= warm {
|
||||
return
|
||||
}
|
||||
last := e.Close.Get(0)
|
||||
hhPrev := ta.Highest(e.High, highestDay).Get(1)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrN).Get(0)
|
||||
//仅在没有任何多单(含未成交挂单)时考虑新挂单倮京沋塑
|
||||
if len(s.LongOrders) == 0 {
|
||||
//只在价格位于前高下方时挂“突破买入"的 stop 触发单
|
||||
if last < hhPrev && atr > 0 {
|
||||
entry := hhPrev
|
||||
_ = s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Stop: entry,
|
||||
StopLossVal: slNATR * atr,
|
||||
TakeProfitVal: tpNATR * atr,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
35
demo/tmp.go
Normal file
35
demo/tmp.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func tmp(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
price := 2.236
|
||||
return &strat.TradeStrat{
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "BUY",
|
||||
OrderType: core.OrderTypeLimit,
|
||||
Limit: price,
|
||||
Amount: 4.65000,
|
||||
TakeProfit: price * 1.01,
|
||||
TakeProfitLimit: price * 1.012,
|
||||
StopLoss: price * 0.99,
|
||||
Log: true,
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "all",
|
||||
})
|
||||
ods, err := strat.GetHistOrders(ormo.GetHistOrdersArgs{
|
||||
Limit: 3,
|
||||
})
|
||||
log.Info("get hist", zap.Int("num", len(ods)), zap.Error(err))
|
||||
},
|
||||
}
|
||||
}
|
||||
29
demo/tmp1.go
Normal file
29
demo/tmp1.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"math"
|
||||
)
|
||||
|
||||
func Tmp1(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
flag := math.Round(math.Mod(float64(e.TimeStop/e.TFMSecs), 2))
|
||||
if flag == 1 {
|
||||
s.OpenOrder(&strat.EnterReq{Tag: "long"})
|
||||
if len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exitS", Dirt: core.OdDirtShort})
|
||||
}
|
||||
} else {
|
||||
s.OpenOrder(&strat.EnterReq{Tag: "short", Short: true})
|
||||
if len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "exitL", Dirt: core.OdDirtLong})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
27
demo/trailing_stop.go
Normal file
27
demo/trailing_stop.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func TrailStop(p *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
TimeFrames: "1h",
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, 14).Get(0)
|
||||
atrPct := atr * 100 / e.Close.Get(0)
|
||||
k, d, _ := ta.KDJ(e.High, e.Low, e.Close, 9, 3, 3)
|
||||
kdCross := k.Cross(d)
|
||||
if len(s.LongOrders) == 0 && kdCross == 1 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
CallbackPct: min(10, 5*atrPct),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
124
ent/fund_rate.go
Normal file
124
ent/fund_rate.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banbot/orm"
|
||||
utils2 "github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/utils"
|
||||
"github.com/banbox/banstagy/com"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
/*
|
||||
永续合约资金费率结算后一段时间,如果资金费率<-0.0035,未来5分钟有较大概率下降
|
||||
*/
|
||||
|
||||
type FRate struct {
|
||||
Time int64
|
||||
Data []*com.CodeVal
|
||||
}
|
||||
|
||||
func runFundEnter() *errs.Error {
|
||||
exgId := "binance"
|
||||
exchange, err := exg.GetWith(exgId, banexg.MarketLinear, banexg.MarketSwap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = orm.LoadMarkets(exchange, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dumpDir := filepath.Join(config.GetDataDir(), "dump")
|
||||
err_ := utils2.EnsureDir(dumpDir, 0755)
|
||||
if err_ != nil {
|
||||
return errs.New(errs.CodeIOWriteFail, err_)
|
||||
}
|
||||
dumpPath := filepath.Join(dumpDir, exgId+"_fundingRate.json")
|
||||
his := make(map[string]map[int64]float64)
|
||||
err_ = utils.ReadJsonFile(dumpPath, &his, utils.JsonNumDefault)
|
||||
if err_ != nil {
|
||||
return errs.New(errs.CodeIOReadFail, err_)
|
||||
}
|
||||
dates := make(map[int64]*FRate)
|
||||
for code, timeMap := range his {
|
||||
for dateMS, val := range timeMap {
|
||||
sta, ok := dates[dateMS]
|
||||
if !ok {
|
||||
sta = &FRate{Time: dateMS}
|
||||
dates[dateMS] = sta
|
||||
}
|
||||
sta.Data = append(sta.Data, &com.CodeVal{
|
||||
Code: code,
|
||||
Val: val,
|
||||
})
|
||||
}
|
||||
}
|
||||
arr := make([]*FRate, 0, len(dates))
|
||||
for _, it := range dates {
|
||||
arr = append(arr, it)
|
||||
}
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
return arr[i].Time < arr[j].Time
|
||||
})
|
||||
// 按时间戳,对资金费率绝对值较大的,筛选K线并记录价格
|
||||
rows := make([][]string, 0)
|
||||
barNum := 20
|
||||
backOff := 5
|
||||
head := []string{"time", "code", "rate", "price"}
|
||||
for i := 0; i < barNum; i++ {
|
||||
sid := i - backOff
|
||||
if sid >= 0 {
|
||||
sid += 1
|
||||
}
|
||||
head = append(head, "p"+strconv.Itoa(sid))
|
||||
}
|
||||
rows = append(rows, head)
|
||||
for _, dt := range arr {
|
||||
sort.Slice(dt.Data, func(i, j int) bool {
|
||||
return math.Abs(dt.Data[i].Val) > math.Abs(dt.Data[j].Val)
|
||||
})
|
||||
startMS := dt.Time - int64(backOff)*60000
|
||||
for _, it := range dt.Data {
|
||||
if math.Abs(it.Val) < 0.0005 {
|
||||
break
|
||||
}
|
||||
exs, err := orm.GetExSymbol(exchange, it.Code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, bars, err := orm.GetOHLCV(exs, "1m", startMS, 0, barNum, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(bars) < barNum {
|
||||
continue
|
||||
}
|
||||
row := make([]string, 0, 4+barNum)
|
||||
initPrice := bars[backOff].Open
|
||||
row = append(row, btime.ToDateStr(dt.Time, core.DefaultDateFmt), it.Code,
|
||||
strconv.FormatFloat(it.Val, 'f', 6, 64),
|
||||
fmt.Sprintf("%.4e", initPrice))
|
||||
var pct float64
|
||||
for i, b := range bars {
|
||||
if i < backOff {
|
||||
pct = (1 - b.Open/initPrice) * 100
|
||||
} else {
|
||||
pct = (b.Close/initPrice - 1) * 100
|
||||
}
|
||||
row = append(row, strconv.FormatFloat(pct, 'f', 4, 64))
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
outPath := filepath.Join(dumpDir, exgId+"_fr.csv")
|
||||
return utils2.WriteCsvFile(outPath, rows, false)
|
||||
}
|
||||
137
ent/his_sharpe.go
Normal file
137
ent/his_sharpe.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/opt"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
utils2 "github.com/banbox/banexg/utils"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func hisSharpe(args []string) error {
|
||||
var configPaths config.ArrString
|
||||
var inPath string
|
||||
var sub = flag.NewFlagSet("fac", flag.ExitOnError)
|
||||
sub.Var(&configPaths, "config", "config path to use, Multiple -config options may be used")
|
||||
sub.StringVar(&inPath, "in", "", "path for all pairs orders e.g.: orders.gob")
|
||||
err_ := sub.Parse(args)
|
||||
if err_ != nil {
|
||||
return err_
|
||||
}
|
||||
core.SetRunMode(core.RunModeBackTest)
|
||||
err := biz.SetupComsExg(&config.CmdArgs{
|
||||
Configs: configPaths,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var startMs = config.TimeRange.StartMS
|
||||
var endMS = config.TimeRange.EndMS
|
||||
if len(config.StakeCurrency) == 0 {
|
||||
return errors.New("`stake_currency` in yml is required")
|
||||
}
|
||||
var tfSecs int
|
||||
var orders []*ormo.InOutOrder
|
||||
if inPath == "" {
|
||||
return errors.New("-in is required")
|
||||
} else {
|
||||
inPath = config.ParsePath(inPath)
|
||||
}
|
||||
orders, err = ormo.LoadOrdersGob(inPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var exsMap = make(map[int32]*orm.ExSymbol)
|
||||
var pairMap = make(map[string]int32)
|
||||
var totalCost float64
|
||||
var validOdNum int
|
||||
if len(orders) > 0 {
|
||||
startMs = orders[0].RealEnterMS()
|
||||
endMS = orders[0].RealExitMS()
|
||||
for _, od := range orders {
|
||||
curStart := od.RealEnterMS()
|
||||
if curStart < startMs && curStart > 0 {
|
||||
startMs = curStart
|
||||
}
|
||||
curEnd := od.RealExitMS()
|
||||
if curEnd > endMS {
|
||||
endMS = curEnd
|
||||
}
|
||||
tfSecs = max(tfSecs, utils2.TFToSecs(od.Timeframe))
|
||||
curCost := od.EnterCost()
|
||||
if curCost > 0 {
|
||||
totalCost += curCost
|
||||
validOdNum += 1
|
||||
}
|
||||
if _, ok := pairMap[od.Symbol]; !ok {
|
||||
exs, err := orm.GetExSymbolCur(od.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exsMap[exs.ID] = exs
|
||||
pairMap[od.Symbol] = exs.ID
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return errors.New("orders is empty")
|
||||
}
|
||||
if validOdNum == 0 {
|
||||
return errors.New("no valid orders")
|
||||
}
|
||||
if tfSecs == 0 {
|
||||
tfSecs = utils2.TFToSecs("1d")
|
||||
}
|
||||
tf := "1d"
|
||||
// 滚动测试因子
|
||||
rangeStart, _ := btime.ParseTimeMS("2022-11-19")
|
||||
rangeEnd, _ := btime.ParseTimeMS("2024-11-18")
|
||||
pairOrders, err := opt.CutOrdersInRange(orders, rangeStart, rangeEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pairOrders = map[string][]*ormo.InOutOrder{
|
||||
"WIF/USDT:USDT": pairOrders["WIF/USDT:USDT"],
|
||||
}
|
||||
pairInfos, err := opt.CalcPairStats(pairOrders, rangeStart, rangeEnd, tf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
initFund := config.WalletAmounts["USDT"]
|
||||
rangeSecs := (rangeEnd - rangeStart) / 1000
|
||||
for _, sta := range pairInfos {
|
||||
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 err
|
||||
}
|
||||
labels := make([]string, len(reals))
|
||||
for i := range reals {
|
||||
labels[i] = strconv.Itoa(i)
|
||||
}
|
||||
g := opt.Chart{
|
||||
Labels: labels,
|
||||
Datasets: []*opt.ChartDs{
|
||||
{
|
||||
Label: "pow(x, 1.5)",
|
||||
Data: reals,
|
||||
BorderColor: "rgb(75, 192, 192)",
|
||||
},
|
||||
},
|
||||
}
|
||||
g.DumpFile("D:/dump_pairs2.html")
|
||||
fmt.Printf("%s sharpe: %v", sta.Symbol, sharp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
76
ent/main.go
Normal file
76
ent/main.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/entry"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
"github.com/banbox/banstagy/arb"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
entry.AddCommand("tool", &cobra.Command{
|
||||
Use: "hisSharpe",
|
||||
DisableFlagParsing: true,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return hisSharpe(args)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func RunMyEntry(args []string) *errs.Error {
|
||||
if len(args) == 0 {
|
||||
log.Warn("sub command is require for `my`")
|
||||
return nil
|
||||
}
|
||||
var ag = &config.CmdArgs{}
|
||||
var f = flag.NewFlagSet("web", flag.ExitOnError)
|
||||
f.StringVar(&ag.LogLevel, "level", "info", "log level")
|
||||
f.StringVar(&ag.TimeZone, "tz", "", "timezone")
|
||||
f.StringVar(&ag.DataDir, "datadir", "", "Path to data dir.")
|
||||
f.StringVar(&ag.ConfigData, "config-data", "", "yaml config string")
|
||||
f.Var(&ag.Configs, "config", "config path to use, Multiple -config options may be used")
|
||||
if args == nil {
|
||||
args = os.Args
|
||||
}
|
||||
err_ := f.Parse(args[1:])
|
||||
if err_ != nil {
|
||||
return errs.New(errs.CodeRunTime, err_)
|
||||
}
|
||||
|
||||
err := biz.SetupComsExg(&config.CmdArgs{
|
||||
LogLevel: ag.LogLevel,
|
||||
DataDir: ag.DataDir,
|
||||
TimeZone: ag.TimeZone,
|
||||
Configs: ag.Configs,
|
||||
ConfigData: ag.ConfigData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch args[0] {
|
||||
case "fundRate":
|
||||
err = runFundEnter()
|
||||
case "moneyFlow":
|
||||
err = maxVolTest()
|
||||
case "maxChg":
|
||||
err = maxChgTest()
|
||||
case "twoPairRatio":
|
||||
err = arb.GenTwoPairRatio()
|
||||
case "profTuple":
|
||||
err = arb.ProfTuples()
|
||||
case "tupleSpike":
|
||||
err = arb.FindTupleSpikes()
|
||||
case "statGroup":
|
||||
err = arb.StatGroupPairs()
|
||||
case "balance":
|
||||
err = showBalance()
|
||||
default:
|
||||
log.Warn("unknown sub command for `my`")
|
||||
}
|
||||
return err
|
||||
}
|
||||
195
ent/max_chg.go
Normal file
195
ent/max_chg.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
utils2 "github.com/banbox/banexg/utils"
|
||||
"github.com/banbox/banstagy/com"
|
||||
"github.com/banbox/banta"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type chgSta struct {
|
||||
env *banta.BarEnv
|
||||
volMa *com.Ema
|
||||
chg float64 // 变动百分比
|
||||
stability float64 // 稳定性分数
|
||||
adx float64
|
||||
rsi float64
|
||||
er float64
|
||||
price float64
|
||||
weight float64
|
||||
nexts []*orm.InfoKline
|
||||
num int
|
||||
}
|
||||
|
||||
func maxChgTest() *errs.Error {
|
||||
exgId := "binance"
|
||||
tf := "1m"
|
||||
maAlpha := 0.03
|
||||
viewNextNum := 5
|
||||
tfMSecs := int64(utils2.TFToSecs(tf) * 1000)
|
||||
exsMap := orm.GetExSymbols(exgId, banexg.MarketLinear)
|
||||
exsList := make([]*orm.ExSymbol, 0, len(exsMap))
|
||||
envs := make(map[string]*chgSta)
|
||||
staList := make([]*chgSta, 0, len(exsMap))
|
||||
topStas := make([]*chgSta, 0, len(exsMap)/4)
|
||||
for _, exs := range exsMap {
|
||||
_, quote, _, _ := core.SplitSymbol(exs.Symbol)
|
||||
if quote != "USDT" {
|
||||
continue
|
||||
}
|
||||
exsList = append(exsList, exs)
|
||||
env := &chgSta{
|
||||
env: &banta.BarEnv{
|
||||
TimeFrame: tf,
|
||||
TFMSecs: tfMSecs,
|
||||
Exchange: exs.Exchange,
|
||||
MarketType: exs.Market,
|
||||
Symbol: exs.Symbol,
|
||||
MaxCache: 1000,
|
||||
},
|
||||
volMa: com.NewEMA(maAlpha),
|
||||
}
|
||||
envs[exs.Symbol] = env
|
||||
staList = append(staList, env)
|
||||
}
|
||||
barNum := 0
|
||||
barTimeMS := int64(0)
|
||||
var rows [][]string
|
||||
var head = []string{"time", "code", "chg", "weight", "price", "adx", "rsi", "er"}
|
||||
for i := 0; i < viewNextNum; i++ {
|
||||
head = append(head, "p"+strconv.Itoa(i+1))
|
||||
}
|
||||
rows = append(rows, head)
|
||||
updateWeights := func() {
|
||||
total := float64(0)
|
||||
for _, e := range envs {
|
||||
total += e.volMa.Val
|
||||
}
|
||||
if math.IsNaN(total) || total == 0 {
|
||||
return
|
||||
}
|
||||
for _, e := range envs {
|
||||
e.weight = (e.volMa.Val / total) * 100
|
||||
}
|
||||
sort.Slice(staList, func(i, j int) bool {
|
||||
return staList[i].weight > staList[j].weight
|
||||
})
|
||||
topStas = staList[:150]
|
||||
}
|
||||
checkBatch := func() {
|
||||
sort.Slice(topStas, func(i, j int) bool {
|
||||
return math.Abs(topStas[i].chg) > math.Abs(topStas[j].chg)
|
||||
})
|
||||
for i := 0; i < 3; i++ {
|
||||
e := topStas[i]
|
||||
row := []string{
|
||||
btime.ToDateStr(barTimeMS, core.DefaultDateFmt),
|
||||
e.env.Symbol,
|
||||
strconv.FormatFloat(e.chg, 'f', 2, 64),
|
||||
strconv.FormatFloat(e.weight, 'f', 2, 64),
|
||||
fmt.Sprintf("%.3e", e.price),
|
||||
strconv.FormatFloat(e.adx, 'f', 2, 64),
|
||||
strconv.FormatFloat(e.rsi, 'f', 2, 64),
|
||||
strconv.FormatFloat(e.er, 'f', 2, 64),
|
||||
}
|
||||
for j := 0; j < viewNextNum; j++ {
|
||||
rate := (e.nexts[j].Close/e.price - 1) * 100
|
||||
row = append(row, strconv.FormatFloat(rate, 'f', 2, 64))
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
totalSecs := (config.TimeRange.EndMS - config.TimeRange.StartMS) / 1000
|
||||
pbar := utils.NewPrgBar(int(totalSecs), "Run")
|
||||
defer pbar.Close()
|
||||
onBar := func(b *orm.InfoKline, nexts []*orm.InfoKline) {
|
||||
if b.Time > barTimeMS {
|
||||
if !b.IsWarmUp && barTimeMS > 0 {
|
||||
pbar.Add(int((b.Time - barTimeMS) / 1000))
|
||||
}
|
||||
barNum += 1
|
||||
barTimeMS = b.Time
|
||||
if barNum%20 == 1 {
|
||||
// 每隔20个bar,更新全局权重
|
||||
updateWeights()
|
||||
}
|
||||
if !b.IsWarmUp && barNum%5 == 0 {
|
||||
// 每隔5个bar,计算一次变化
|
||||
checkBatch()
|
||||
}
|
||||
}
|
||||
sta := envs[b.Symbol]
|
||||
sta.num += 1
|
||||
e := sta.env
|
||||
e.OnBar(b.Time, b.Open, b.High, b.Low, b.Close, b.Volume,
|
||||
b.Quote, b.BuyVolume, b.TradeNum)
|
||||
avgPrice := (b.High + b.Low + b.Close) / 3
|
||||
sta.volMa.Update(b.Volume * avgPrice)
|
||||
sta.chg = (b.Close/b.Open - 1) * 100
|
||||
sta.adx = banta.ADX(e.High, e.Low, e.Close, 3).Get(0)
|
||||
sta.rsi = banta.RSI(e.Close, 3).Get(0)
|
||||
sta.er = banta.ER(e.Close, 3).Get(0)
|
||||
if b.IsWarmUp {
|
||||
return
|
||||
}
|
||||
sta.nexts = nexts
|
||||
sta.price = b.Close
|
||||
}
|
||||
var replayErr *errs.Error
|
||||
verCh := make(chan int, 1)
|
||||
onData := func(evt *orm.DataSeries, nexts []*orm.DataSeries) {
|
||||
bar, err_ := orm.AsKline(evt)
|
||||
if err_ != nil {
|
||||
replayErr = errs.New(errs.CodeRunTime, err_)
|
||||
select {
|
||||
case verCh <- -1:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
futureBars := make([]*orm.InfoKline, 0, len(nexts))
|
||||
for _, next := range nexts {
|
||||
futureBar, convErr := orm.AsKline(next)
|
||||
if convErr != nil {
|
||||
replayErr = errs.New(errs.CodeRunTime, convErr)
|
||||
select {
|
||||
case verCh <- -1:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
futureBars = append(futureBars, futureBar)
|
||||
}
|
||||
onBar(bar, futureBars)
|
||||
}
|
||||
err := biz.RunHistSeries(&biz.RunHistSeriesArgs{
|
||||
ExsList: exsList,
|
||||
Start: config.TimeRange.StartMS,
|
||||
End: config.TimeRange.EndMS,
|
||||
ViewNextNum: viewNextNum,
|
||||
TfWarms: map[string]int{tf: 100},
|
||||
VerCh: verCh,
|
||||
OnData: onData,
|
||||
})
|
||||
if err == nil && replayErr != nil {
|
||||
err = replayErr
|
||||
}
|
||||
outPath := filepath.Join(config.GetDataDir(), "dump", "maxChg.csv")
|
||||
err2 := utils.WriteCsvFile(outPath, rows, false)
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
182
ent/max_vol.go
Normal file
182
ent/max_vol.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm"
|
||||
"github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banstagy/com"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type volSta struct {
|
||||
code string
|
||||
longVol *com.Ema
|
||||
shortVol *com.Ema
|
||||
longRate float64
|
||||
shortRate float64
|
||||
weight float64
|
||||
price float64
|
||||
nexts []*orm.InfoKline
|
||||
num int
|
||||
}
|
||||
|
||||
func maxVolTest() *errs.Error {
|
||||
exgId := "binance"
|
||||
tf := "1h"
|
||||
maAlpha := 0.03
|
||||
viewNextNum := 3
|
||||
exsMap := orm.GetExSymbols(exgId, banexg.MarketLinear)
|
||||
exsList := make([]*orm.ExSymbol, 0, len(exsMap))
|
||||
envs := make(map[string]*volSta)
|
||||
envList := make([]*volSta, 0, len(exsMap))
|
||||
for _, exs := range exsMap {
|
||||
_, quote, _, _ := core.SplitSymbol(exs.Symbol)
|
||||
if quote != "USDT" {
|
||||
continue
|
||||
}
|
||||
exsList = append(exsList, exs)
|
||||
env := &volSta{
|
||||
code: exs.Symbol,
|
||||
longVol: com.NewEMA(maAlpha),
|
||||
shortVol: com.NewEMA(maAlpha),
|
||||
}
|
||||
envs[exs.Symbol] = env
|
||||
envList = append(envList, env)
|
||||
}
|
||||
barNum := 0
|
||||
barTimeMS := int64(0)
|
||||
updateWeights := func() {
|
||||
total := float64(0)
|
||||
for _, e := range envs {
|
||||
total += e.longVol.Val + e.shortVol.Val
|
||||
}
|
||||
if math.IsNaN(total) {
|
||||
return
|
||||
}
|
||||
for _, e := range envs {
|
||||
wei := (e.longVol.Val + e.shortVol.Val) / total
|
||||
e.weight = math.Pow(wei, 0.5)
|
||||
}
|
||||
}
|
||||
var rows [][]string
|
||||
var head = []string{"time", "code", "longRt", "shortRt"}
|
||||
for i := 0; i < viewNextNum; i++ {
|
||||
head = append(head, "p"+strconv.Itoa(i+1))
|
||||
}
|
||||
rows = append(rows, head)
|
||||
checkBatch := func() {
|
||||
sort.Slice(envList, func(i, j int) bool {
|
||||
return envList[i].longRate > envList[j].shortRate
|
||||
})
|
||||
for i := 0; i < 3; i++ {
|
||||
e := envList[i]
|
||||
row := []string{
|
||||
btime.ToDateStr(barTimeMS, core.DefaultDateFmt),
|
||||
e.code,
|
||||
strconv.FormatFloat(e.longRate, 'f', 3, 64),
|
||||
strconv.FormatFloat(e.shortRate, 'f', 3, 64),
|
||||
}
|
||||
for j := 0; j < viewNextNum; j++ {
|
||||
rate := (e.nexts[j].Close/e.price - 1) * 100
|
||||
row = append(row, strconv.FormatFloat(rate, 'f', 2, 64))
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
totalSecs := (config.TimeRange.EndMS - config.TimeRange.StartMS) / 1000
|
||||
pbar := utils.NewPrgBar(int(totalSecs), "Run")
|
||||
defer pbar.Close()
|
||||
onBar := func(b *orm.InfoKline, nexts []*orm.InfoKline) {
|
||||
if b.Time > barTimeMS {
|
||||
if !b.IsWarmUp && barTimeMS > 0 {
|
||||
pbar.Add(int((b.Time - barTimeMS) / 1000))
|
||||
}
|
||||
barNum += 1
|
||||
barTimeMS = b.Time
|
||||
if barNum%20 == 1 {
|
||||
// 每隔20个bar,更新全局权重
|
||||
updateWeights()
|
||||
}
|
||||
if !b.IsWarmUp && barNum%5 == 0 {
|
||||
// 每隔5个bar,计算一次变化
|
||||
checkBatch()
|
||||
}
|
||||
}
|
||||
sta := envs[b.Symbol]
|
||||
sta.num += 1
|
||||
lineTot := (b.High - b.Low) - math.Abs(b.Open-b.Close)
|
||||
total := (b.High - b.Low) + lineTot + core.AmtDust
|
||||
longTot := lineTot
|
||||
if b.Open < b.Close {
|
||||
longTot += math.Abs(b.Open - b.Close)
|
||||
}
|
||||
avgPrice := (b.High + b.Low + b.Close) / 3
|
||||
quoteVol := avgPrice * b.Volume
|
||||
longRt := longTot / total
|
||||
longVol := quoteVol * longRt
|
||||
shortVol := quoteVol - longVol
|
||||
sta.longVol.Update(longVol)
|
||||
sta.shortVol.Update(shortVol)
|
||||
if b.IsWarmUp {
|
||||
return
|
||||
}
|
||||
// 暂时不考虑多空直接比例
|
||||
sta.longRate = longVol / sta.longVol.Val * sta.weight * math.Pow(longRt, 2)
|
||||
sta.shortRate = shortVol / sta.shortVol.Val * sta.weight * math.Pow(1-longRt, 2)
|
||||
sta.nexts = nexts
|
||||
sta.price = b.Close
|
||||
}
|
||||
var replayErr *errs.Error
|
||||
verCh := make(chan int, 1)
|
||||
onData := func(evt *orm.DataSeries, nexts []*orm.DataSeries) {
|
||||
bar, err_ := orm.AsKline(evt)
|
||||
if err_ != nil {
|
||||
replayErr = errs.New(errs.CodeRunTime, err_)
|
||||
select {
|
||||
case verCh <- -1:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
futureBars := make([]*orm.InfoKline, 0, len(nexts))
|
||||
for _, next := range nexts {
|
||||
futureBar, convErr := orm.AsKline(next)
|
||||
if convErr != nil {
|
||||
replayErr = errs.New(errs.CodeRunTime, convErr)
|
||||
select {
|
||||
case verCh <- -1:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
futureBars = append(futureBars, futureBar)
|
||||
}
|
||||
onBar(bar, futureBars)
|
||||
}
|
||||
err := biz.RunHistSeries(&biz.RunHistSeriesArgs{
|
||||
ExsList: exsList,
|
||||
Start: config.TimeRange.StartMS,
|
||||
End: config.TimeRange.EndMS,
|
||||
ViewNextNum: viewNextNum,
|
||||
TfWarms: map[string]int{tf: 100},
|
||||
VerCh: verCh,
|
||||
OnData: onData,
|
||||
})
|
||||
if err == nil && replayErr != nil {
|
||||
err = replayErr
|
||||
}
|
||||
outPath := filepath.Join(config.GetDataDir(), "dump", "moneyFlow.csv")
|
||||
err2 := utils.WriteCsvFile(outPath, rows, false)
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
31
ent/tools.go
Normal file
31
ent/tools.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/exg"
|
||||
"github.com/banbox/banexg"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/utils"
|
||||
)
|
||||
|
||||
func showBalance() *errs.Error {
|
||||
var account string
|
||||
for acc := range config.Accounts {
|
||||
account = acc
|
||||
break
|
||||
}
|
||||
exchange := exg.Default
|
||||
res, err := exchange.FetchBalance(map[string]interface{}{
|
||||
banexg.ParamAccount: account,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resstr, err_ := utils.MarshalString(res.Info)
|
||||
if err_ != nil {
|
||||
return errs.New(errs.CodeMarshalFail, err_)
|
||||
}
|
||||
fmt.Println(resstr)
|
||||
return nil
|
||||
}
|
||||
172
fac/best.go
Normal file
172
fac/best.go
Normal file
@@ -0,0 +1,172 @@
|
||||
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
|
||||
}
|
||||
88
filters/exp_pos.go
Normal file
88
filters/exp_pos.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/goods"
|
||||
"github.com/banbox/banexg/errs"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goods.RegisterFilter("ExpPosFilter", func(base goods.BaseFilter) goods.IFilter {
|
||||
return &ExpPosFilter{
|
||||
BaseFilter: base,
|
||||
Exp: 3,
|
||||
CutRate: 1,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type ExpPosFilter struct {
|
||||
goods.BaseFilter
|
||||
Rate float64 `yaml:"rate" mapstructure:"rate,omitempty"`
|
||||
// 输出的数量
|
||||
Limit int `yaml:"limit" mapstructure:"limit,omitempty"`
|
||||
// 偏斜因子 (k)。> 1.0 的值会使分布偏向较小的索引。默认2
|
||||
Exp float64 `yaml:"exp" mapstructure:"exp,omitempty"`
|
||||
// 允许的最大索引占总数的百分比 (例如 0.5 表示 50%)默认1
|
||||
CutRate float64 `yaml:"cut_rate" mapstructure:"cut_rate,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ExpPosFilter) Filter(pairs []string, timeMS int64) ([]string, *errs.Error) {
|
||||
outNum := float64(len(pairs))
|
||||
if e.Rate > 0 {
|
||||
outNum *= e.Rate
|
||||
}
|
||||
outNumV := int(outNum)
|
||||
if e.Limit > 0 && e.Limit < outNumV {
|
||||
outNumV = e.Limit
|
||||
}
|
||||
maxId := float64(len(pairs)) * e.CutRate
|
||||
outIds := calcRankings(int(maxId), outNumV, e.Exp)
|
||||
outPairs := make([]string, 0, len(outIds))
|
||||
hit := make(map[int]bool)
|
||||
codes := make([]string, 0, len(outIds))
|
||||
for _, id := range outIds {
|
||||
if _, ok := hit[id]; ok {
|
||||
continue
|
||||
}
|
||||
hit[id] = true
|
||||
outPairs = append(outPairs, pairs[id])
|
||||
base, _, _, _ := core.SplitSymbol(pairs[id])
|
||||
codes = append(codes, base)
|
||||
}
|
||||
dateStr := btime.ToDateStr(btime.TimeMS(), core.DefaultDateFmt)
|
||||
log.Info("ExpPosFilter", zap.String("date", dateStr), zap.Int("inNum", len(pairs)), zap.Ints("ids", outIds),
|
||||
zap.String("code", strings.Join(codes, ",")))
|
||||
return outPairs, nil
|
||||
}
|
||||
|
||||
func calcRankings(totalNum int, pickNum int, skewFactor float64) []int {
|
||||
if pickNum <= 1 {
|
||||
return []int{0}
|
||||
}
|
||||
|
||||
rankings := make([]int, pickNum)
|
||||
|
||||
for i := 1; i <= pickNum; i++ {
|
||||
// 2. 将源索引 (i) 归一化到 [0.0, 1.0] 范围
|
||||
proportion := float64(i) / float64(pickNum)
|
||||
|
||||
// 3. 应用幂函数 (k) 来“偏斜”分布
|
||||
// 这使得 0.1 -> 0.01, 0.5 -> 0.25 (当 k=2.0 时)
|
||||
// 导致更多早期项目聚集在较低的索引上
|
||||
skewedProportion := math.Pow(proportion, skewFactor)
|
||||
|
||||
// 4. 将偏斜后的比例 [0.0, 1.0] 映射到最终的索引范围 [0, totalNum-1]
|
||||
finalRank := skewedProportion * float64(totalNum-1)
|
||||
|
||||
// 转换为整数索引
|
||||
rankings[i-1] = int(math.Round(finalRank))
|
||||
}
|
||||
|
||||
return rankings
|
||||
}
|
||||
302
flux/grid/common.go
Normal file
302
flux/grid/common.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg/log"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
CheckPos在每个bar的close上检查位置,是否需要补仓或止盈。
|
||||
bar中间的high/low忽略。所以尽量在1m周期执行,避免一个bar的high/low突破网格,close又收回来导致未执行的问题
|
||||
*/
|
||||
|
||||
func init() {
|
||||
strat.AddStratGroup("grid", map[string]strat.FuncMakeStrat{
|
||||
"inv": InvGrid,
|
||||
"long": LongGrid,
|
||||
})
|
||||
}
|
||||
|
||||
func NewGrid(initRate, maxAdd int, slRate float64, debug bool) *Grid {
|
||||
return &Grid{
|
||||
InitRate: float64(initRate),
|
||||
MaxAdd: maxAdd,
|
||||
SLRate: slRate,
|
||||
Debug: debug,
|
||||
}
|
||||
}
|
||||
|
||||
type Grid struct {
|
||||
InitRate float64 // 初次打开网格投入倍数
|
||||
MaxAdd int // 最大加仓次数
|
||||
SLRate float64 // 止损相对Unit的倍数
|
||||
Dirt float64 // 当前是做多网格还是做空;1做多,-1做空
|
||||
HoldSize float64 // 持有仓位倍率;相对单位开仓大小
|
||||
Unit float64 // 网格价格单位
|
||||
OneAmt float64 // 一份的数量,初次入场时计算
|
||||
EntPrice float64 // 入场价格
|
||||
Pos float64 // 网格位置,整数,-6/-5/-4/-3/-2/-1/0/1/2/3/4/5/6
|
||||
OldPos float64 // 上次网格位置
|
||||
openNeed bool // 是否需要打开加仓单
|
||||
takeNeed bool // 是否需要追加止盈单
|
||||
|
||||
Debug bool
|
||||
barLogs []string
|
||||
barMS int64
|
||||
}
|
||||
|
||||
func (m *Grid) UpdatePos(e *ta.BarEnv, price float64) {
|
||||
offRaw := (price - m.EntPrice) / m.Unit
|
||||
rate := math.Floor(math.Abs(offRaw) + 0.1)
|
||||
if offRaw < 0 {
|
||||
rate = -rate
|
||||
}
|
||||
if rate != m.Pos {
|
||||
m.Pos = rate
|
||||
m.takeNeed = true
|
||||
m.openNeed = true
|
||||
if m.Debug {
|
||||
base := m.EntPrice + m.Pos*m.Unit
|
||||
m.log(e, "upper: %.2f lower: %.2f", base+m.Unit, base-m.Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) Open(s *strat.StratJob) {
|
||||
c := s.Env.Close.Get(0)
|
||||
var tag = "short"
|
||||
if m.Dirt > 0 {
|
||||
tag = "long"
|
||||
}
|
||||
m.EntPrice = 0
|
||||
m.Pos = 0
|
||||
m.OldPos = 0
|
||||
m.HoldSize = 0
|
||||
m.OneAmt = s.Strat.GetStakeAmount(s) / c
|
||||
m.openNeed = true
|
||||
m.takeNeed = true
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: tag,
|
||||
Amount: m.InitRate * m.OneAmt,
|
||||
Short: m.Dirt < 0,
|
||||
StopLossVal: m.Unit * m.SLRate,
|
||||
})
|
||||
if m.Debug {
|
||||
m.log(s.Env, "init open, dir: %v, size: %v, grid: %.3f, pct: %.1f",
|
||||
m.Dirt, m.InitRate, m.Unit, m.Unit*100/c)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) CheckPos(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
m.UpdatePos(e, e.Close.Get(0))
|
||||
if !m.openNeed && !m.takeNeed {
|
||||
return
|
||||
}
|
||||
odList := s.GetOrders(m.Dirt)
|
||||
// 取消未入场的触发订单
|
||||
cancels := make([]string, 0)
|
||||
var holds = make(map[string]int)
|
||||
for _, od := range odList {
|
||||
if od.Status < ormo.InOutStatusPartEnter {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "cancel", OrderID: od.ID, Force: true})
|
||||
if m.Debug {
|
||||
cancels = append(cancels, strconv.FormatFloat(od.Enter.Price, 'f', 4, 64))
|
||||
}
|
||||
} else if m.Debug {
|
||||
num := int(math.Round(od.HoldAmount() / m.OneAmt))
|
||||
row := strconv.FormatFloat(od.Enter.Average, 'f', 2, 64)
|
||||
old, _ := holds[row]
|
||||
holds[row] = old + num
|
||||
}
|
||||
}
|
||||
if m.HoldSize < 0.1 {
|
||||
if m.Debug {
|
||||
m.log(e, "hold reset\n")
|
||||
}
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "clear", Force: true})
|
||||
return
|
||||
}
|
||||
posChg := (m.Pos - m.OldPos) * m.Dirt
|
||||
m.OldPos = m.Pos
|
||||
jumpNum := math.Abs(posChg) - 1
|
||||
var jumpStr string
|
||||
if jumpNum > 0 {
|
||||
// 市场剧烈变化,此时最新价格已偏移多个网格
|
||||
extAmt := m.OneAmt * jumpNum
|
||||
tag := "jadd"
|
||||
if posChg > 0 {
|
||||
// 剧烈盈利,卖出额外持仓
|
||||
tag = "jexit"
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: tag,
|
||||
Amount: extAmt,
|
||||
FilledOnly: true,
|
||||
Force: true,
|
||||
})
|
||||
} else {
|
||||
// 剧烈亏损,买入额外持仓
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: tag,
|
||||
Amount: extAmt,
|
||||
Short: m.Dirt < 0,
|
||||
StopLoss: odList[0].GetStopLoss().Price,
|
||||
})
|
||||
}
|
||||
if m.Debug {
|
||||
jumpStr = fmt.Sprintf("jump %v %.0f ", tag, jumpNum)
|
||||
}
|
||||
}
|
||||
// 设置最新的止盈止损等待触发
|
||||
trigPrice := m.EntPrice + m.Pos*m.Unit
|
||||
openPrice, closePrice := trigPrice-m.Unit, trigPrice+m.Unit
|
||||
if m.Dirt < 0 {
|
||||
openPrice, closePrice = closePrice, openPrice
|
||||
}
|
||||
var openInfo string
|
||||
if m.openNeed {
|
||||
if -m.Pos*m.Dirt >= float64(m.MaxAdd) {
|
||||
// 达到最大加仓深度
|
||||
if m.Debug {
|
||||
openInfo = "open=nil "
|
||||
}
|
||||
} else {
|
||||
sl := odList[0].GetStopLoss()
|
||||
stopLoss := e.Close.Get(0) - m.Dirt*m.Unit*m.SLRate
|
||||
if sl != nil {
|
||||
stopLoss = sl.Price
|
||||
} else {
|
||||
log.Info("hit sl nil")
|
||||
}
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "add",
|
||||
Limit: openPrice,
|
||||
Amount: m.OneAmt,
|
||||
Short: m.Dirt < 0,
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if m.Debug {
|
||||
openInfo = fmt.Sprintf("open=%.4f ", openPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.openNeed = false
|
||||
if m.takeNeed {
|
||||
s.SetAllTakeProfit(m.Dirt, &ormo.ExitTrigger{
|
||||
Price: closePrice,
|
||||
Rate: 1 / m.HoldSize,
|
||||
Tag: "exit",
|
||||
})
|
||||
//s.CloseOrders(&strat.ExitReq{
|
||||
// Tag: "exit",
|
||||
// Limit: closePrice,
|
||||
// Amount: m.OneAmt,
|
||||
// Force: true,
|
||||
//})
|
||||
}
|
||||
m.takeNeed = false
|
||||
if m.Debug {
|
||||
var takes = make(map[string]int)
|
||||
for _, od := range odList {
|
||||
tp := od.GetTakeProfit()
|
||||
if tp != nil {
|
||||
tpNum := math.Round(od.HoldAmount() / m.OneAmt)
|
||||
if tp.Rate > 0 && tp.Rate < 1 {
|
||||
tpNum *= tp.Rate
|
||||
}
|
||||
tpPrice := strconv.FormatFloat(tp.Price, 'f', 2, 64)
|
||||
old, _ := takes[tpPrice]
|
||||
takes[tpPrice] = old + int(math.Round(tpNum))
|
||||
}
|
||||
}
|
||||
var cancel string
|
||||
if len(cancels) > 0 {
|
||||
cancel = "cancel: [" + strings.Join(cancels, ", ") + "] "
|
||||
}
|
||||
hold := printAmts(holds, "hold: ")
|
||||
take := printAmts(takes, "take: ")
|
||||
m.log(e, cancel+jumpStr+openInfo+hold+take)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) OnOrderChange(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
if chgType == strat.OdChgEnterFill && od.Enter.Filled > core.AmtDust {
|
||||
// 加仓入场完成事件
|
||||
e := s.Env
|
||||
if m.EntPrice == 0 {
|
||||
// 设置第一个下跌买入和第一个盈利卖出
|
||||
m.EntPrice = od.Enter.Average
|
||||
sl := od.GetStopLoss()
|
||||
stoploss := float64(0)
|
||||
if sl != nil {
|
||||
stoploss = sl.Price
|
||||
}
|
||||
upgaps := make([]string, 0, 5)
|
||||
dngaps := make([]string, 0, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
dist := m.Unit * float64(i+1)
|
||||
upgaps = append(upgaps, strconv.FormatFloat(m.EntPrice+dist, 'f', 4, 64))
|
||||
dngaps = append(dngaps, strconv.FormatFloat(m.EntPrice-dist, 'f', 4, 64))
|
||||
}
|
||||
if m.Debug {
|
||||
m.log(e, "p=%.4f sl=%.4f", m.EntPrice, stoploss)
|
||||
m.log(e, "up: %v", strings.Join(upgaps, " "))
|
||||
m.log(e, "dn: %v", strings.Join(dngaps, " "))
|
||||
}
|
||||
}
|
||||
addNum := math.Round(od.Enter.Filled / m.OneAmt)
|
||||
m.HoldSize += addNum
|
||||
m.openNeed = addNum > 0
|
||||
if m.Debug {
|
||||
m.log(e, "[enter] %.4f %.0f hold %.0f",
|
||||
od.Enter.Average, addNum, m.HoldSize)
|
||||
}
|
||||
} else if chgType == strat.OdChgExitFill && od.Exit.Filled > core.AmtDust {
|
||||
e := s.Env
|
||||
exitNum := math.Round(od.Exit.Filled / m.OneAmt)
|
||||
m.HoldSize -= exitNum
|
||||
m.takeNeed = exitNum > 0
|
||||
if m.Debug {
|
||||
m.log(e, "[exit] %v %.4f -> %.4f amt %.0f hold %.0f",
|
||||
od.ExitTag, od.Enter.Average, od.Exit.Average, exitNum, m.HoldSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) log(e *ta.BarEnv, tpl string, args ...any) {
|
||||
if e.TimeStart > m.barMS {
|
||||
content := strings.Join(m.barLogs, "\n\t")
|
||||
fmt.Printf(content)
|
||||
fmt.Printf("\n")
|
||||
date := btime.ToDateStr(e.TimeStart, core.DefaultDateFmt)
|
||||
m.barLogs = []string{date + fmt.Sprintf(" h: %.2f l: %.2f c: %.2f",
|
||||
e.High.Get(0), e.Low.Get(0), e.Close.Get(0))}
|
||||
m.barMS = e.TimeStart
|
||||
}
|
||||
m.barLogs = append(m.barLogs, fmt.Sprintf(tpl, args...))
|
||||
}
|
||||
|
||||
func printAmts(amts map[string]int, prefix string) string {
|
||||
if len(amts) == 0 {
|
||||
return ""
|
||||
}
|
||||
ents := utils.KeysOfMap(amts)
|
||||
slices.Sort(ents)
|
||||
for i, k := range ents {
|
||||
if amts[k] > 1 {
|
||||
ents[i] += " * " + strconv.Itoa(amts[k])
|
||||
}
|
||||
}
|
||||
return prefix + "[" + strings.Join(ents, ", ") + "] "
|
||||
}
|
||||
81
flux/grid/inv.go
Normal file
81
flux/grid/inv.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
多头网格,下跌买入,上涨卖出。适合波动性较强,方向不明的市场。
|
||||
|
||||
以大周期atrBase的一定比例作为网格的单位大小
|
||||
初始倍率: InitRate
|
||||
|
||||
初次开仓条件:
|
||||
大周期ER20 < 0.3
|
||||
小周期MA5 < MA20 看多网格;否则看空网格
|
||||
持有仓位:
|
||||
|
||||
*/
|
||||
|
||||
type GridV1 struct {
|
||||
*Grid
|
||||
bigER float64 // 大周期ER系数
|
||||
}
|
||||
|
||||
func InvGrid(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(float64(lenAtr) * 4.3)
|
||||
unitRate := pol.Def("unitRate", 2, core.PNorm(0.5, 5))
|
||||
initRate := int(pol.Def("initRate", 4, core.PNorm(0.7, 7.3)))
|
||||
maxAdd := 5
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
StopEnterBars: 9999999,
|
||||
TimeFrames: "1m",
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", 100},
|
||||
}
|
||||
},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &GridV1{Grid: NewGrid(initRate, maxAdd, 6, false)}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*GridV1)
|
||||
if s.OrderNum == 0 {
|
||||
if m.Unit == 0 || m.bigER >= 0.1 || s.IsWarmUp {
|
||||
return
|
||||
}
|
||||
m.Open(s)
|
||||
} else {
|
||||
m.CheckPos(s)
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
m, _ := s.More.(*GridV1)
|
||||
m.bigER = ta.ER(e.Close, 20).Get(0)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
ma5 := ta.SMA(e.Close, 10).Get(0)
|
||||
ma20 := ta.SMA(e.Close, 50).Get(0)
|
||||
if s.OrderNum == 0 && !math.IsNaN(atrBase) {
|
||||
// 未持仓时更新网格单元大小
|
||||
m.Unit = atrBase * unitRate
|
||||
m.Dirt = core.OdDirtShort
|
||||
if ma5 < ma20 {
|
||||
m.Dirt = core.OdDirtLong
|
||||
}
|
||||
}
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
m, _ := s.More.(*GridV1)
|
||||
m.OnOrderChange(s, od, chgType)
|
||||
},
|
||||
}
|
||||
}
|
||||
83
flux/grid/long.go
Normal file
83
flux/grid/long.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
多头网格,下跌买入,上涨卖出。适合波动性较强,方向不明的市场。
|
||||
|
||||
以大周期atrBase的一定比例作为网格的单位大小
|
||||
初始倍率: InitRate
|
||||
|
||||
初次开仓条件:
|
||||
大周期ER20 < 0.3
|
||||
小周期MA5 < MA20 看多网格;否则看空网格
|
||||
持有仓位:
|
||||
|
||||
*/
|
||||
|
||||
type GridLong struct {
|
||||
*Grid
|
||||
bigER float64 // 大周期ER系数
|
||||
}
|
||||
|
||||
func LongGrid(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(float64(lenAtr) * 4.3)
|
||||
unitRate := pol.Def("unitRate", 2, core.PNorm(0.5, 5))
|
||||
initRate := int(pol.Def("initRate", 4, core.PNorm(0.7, 7.3)))
|
||||
maxAdd := 5
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
StopEnterBars: 9999999,
|
||||
TimeFrames: "1m",
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", 100},
|
||||
}
|
||||
},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
g := NewGrid(initRate, maxAdd, 6, false)
|
||||
s.More = &GridLong{Grid: g}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*GridLong)
|
||||
if s.OrderNum == 0 {
|
||||
if m.Unit == 0 || s.IsWarmUp {
|
||||
return
|
||||
}
|
||||
m.Open(s)
|
||||
} else {
|
||||
m.CheckPos(s)
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
m, _ := s.More.(*GridLong)
|
||||
m.bigER = ta.ER(e.Close, 20).Get(0)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
//ma5 := ta.SMA(e.Close, 10).Get(0)
|
||||
//ma20 := ta.SMA(e.Close, 50).Get(0)
|
||||
if s.OrderNum == 0 && !math.IsNaN(atrBase) {
|
||||
// 未持仓时更新网格单元大小
|
||||
m.Unit = atrBase * unitRate
|
||||
m.Dirt = core.OdDirtLong
|
||||
//m.Dirt = core.OdDirtShort
|
||||
//if ma5 < ma20 {
|
||||
// m.Dirt = core.OdDirtLong
|
||||
//}
|
||||
}
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
m, _ := s.More.(*GridLong)
|
||||
m.OnOrderChange(s, od, chgType)
|
||||
},
|
||||
}
|
||||
}
|
||||
147
go.mod
Normal file
147
go.mod
Normal file
@@ -0,0 +1,147 @@
|
||||
module github.com/banbox/banstagy
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.9
|
||||
|
||||
replace github.com/banbox/banbot => ../banbot
|
||||
|
||||
replace github.com/banbox/banexg => ../banexg
|
||||
|
||||
replace github.com/banbox/banta => ../banta
|
||||
|
||||
require (
|
||||
github.com/banbox/banbot v0.2.31
|
||||
github.com/banbox/banexg v0.2.60
|
||||
github.com/banbox/banta v0.3.7
|
||||
github.com/sasha-s/go-deadlock v0.3.6
|
||||
go.uber.org/zap v1.27.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/anyongjin/go-bayesopt v1.0.2 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/banbox/bntp v0.1.0 // indirect
|
||||
github.com/banbox/cron/v3 v3.1.2 // indirect
|
||||
github.com/beevik/ntp v1.4.3 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/cloudwego/eino v0.6.0 // indirect
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.1 // indirect
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250918130948-16e3a249e721 // indirect
|
||||
github.com/ebitengine/purego v0.9.0 // indirect
|
||||
github.com/eino-contrib/jsonschema v1.0.2 // indirect
|
||||
github.com/evanphx/json-patch v0.5.2 // indirect
|
||||
github.com/fasthttp/websocket v1.5.12 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
github.com/flopp/go-findfont v0.1.0 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.118.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.5 // indirect
|
||||
github.com/go-telegram/bot v1.17.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofiber/contrib/websocket v1.3.4 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/goph/emperror v0.17.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/yaml v0.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nikolalohinski/gonja v1.5.3 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.4 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yargevad/filepathx v1.0.0 // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/image v0.32.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.66.10 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.39.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/c-bata/goptuna v0.9.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.28.0 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.52.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.6 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
//github.com/pkujhd/goloader v0.0.0-20240113094056-ff3a1e01ffcb // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.18.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.9 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.64.0 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/excelize/v2 v2.10.0 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
gonum.org/v1/gonum v0.16.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
435
go.sum
Normal file
435
go.sum
Normal file
@@ -0,0 +1,435 @@
|
||||
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/anyongjin/go-bayesopt v1.0.2 h1:845NHAdxk4x1FxeOS5McoaWO33ZfE2AR434j9lnEHAM=
|
||||
github.com/anyongjin/go-bayesopt v1.0.2/go.mod h1:KB64n3O9sPBql6pPnOKh0w8bnOSG1TIMNb+9c/9iQs0=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/banbox/bntp v0.1.0 h1:kZROpbb3OME8XM8QeM+2SeQWQrHRDFN05okr8OITKAU=
|
||||
github.com/banbox/bntp v0.1.0/go.mod h1:w0WP7GROKQo07wHFBm/FVgH4xlZuOYCoro0ky4iTC3M=
|
||||
github.com/banbox/cron/v3 v3.1.2 h1:gBNnz/OBxIMU7iBCtObZKhb85rzbaXr8AZLZb40CgeM=
|
||||
github.com/banbox/cron/v3 v3.1.2/go.mod h1:SZzzT03qsZ1y8/Pt2VqKb9Av/HfnuSargOQRmql7n18=
|
||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/mockey v1.2.14 h1:KZaFgPdiUwW+jOWFieo3Lr7INM1P+6adO3hxZhDswY8=
|
||||
github.com/bytedance/mockey v1.2.14/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/c-bata/goptuna v0.9.0 h1:JUO13AVxM4YmO9/NMoaNWegfwaM4wAlkrQ+oN/fgfrg=
|
||||
github.com/c-bata/goptuna v0.9.0/go.mod h1:X2UFBRhqjEI+x2xw/LbaGRaFa/TRBH9w8QjknAaEavo=
|
||||
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cloudwego/eino v0.6.0 h1:pobGKMOfcQHVNhD9UT/HrvO0eYG6FC2ML/NKY2Eb9+Q=
|
||||
github.com/cloudwego/eino v0.6.0/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.1 h1:VRdUDcnfi/T8F0jcuovhdADU9Io/oMqiKpY2ZJTBc1o=
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.1/go.mod h1:VwAXEY1ik2K9KFPZvymnkfBQQKgLHbpg90yg+7hrTt8=
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250918130948-16e3a249e721 h1:5Hd8GxNEmu+ppTGCRBU6kLKfCQNXPMwi31xA83PzEqo=
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250918130948-16e3a249e721/go.mod h1:fHn/6OqPPY1iLLx9wzz+MEVT5Dl9gwuZte1oLEnCoYw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
|
||||
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/eino-contrib/jsonschema v1.0.2 h1:HaxruBMUdnXa7Lg/lX8g0Hk71ZIfdTZXmBQz0e3esr8=
|
||||
github.com/eino-contrib/jsonschema v1.0.2/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4=
|
||||
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
|
||||
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||
github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU=
|
||||
github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
|
||||
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-telegram/bot v1.17.0 h1:Hs0kGxSj97QFqOQP0zxduY/4tSx8QDzvNI9uVRS+zmY=
|
||||
github.com/go-telegram/bot v1.17.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/gofiber/contrib/websocket v1.3.4 h1:tWeBdbJ8q0WFQXariLN4dBIbGH9KBU75s0s7YXplOSg=
|
||||
github.com/gofiber/contrib/websocket v1.3.4/go.mod h1:kTFBPC6YENCnKfKx0BoOFjgXxdz7E85/STdkmZPEmPs=
|
||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
|
||||
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
||||
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0 h1:nIohpHs1ViKR0SVgW/cbBstHjmnqFZDM9RqgX9m9Xu8=
|
||||
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
|
||||
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
|
||||
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
|
||||
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU=
|
||||
github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI=
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
|
||||
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
||||
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
|
||||
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
189
llmjob/128.go
Normal file
189
llmjob/128.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func J128(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
longEntryThreshold := pol.Def("long_entry_threshold", 60.0, core.PNorm(50.0, 70.0))
|
||||
thresholdRange := pol.Def("threshold_range", 14.0, core.PNorm(10.0, 25.0))
|
||||
smaPeriod := pol.DefInt("sma_period", 3, core.PNorm(2, 8))
|
||||
atrPeriod := pol.DefInt("atr_period", 14, core.PNorm(7, 21))
|
||||
tp1ATR := pol.Def("tp1_atr", 3.0, core.PNorm(1.5, 5.0))
|
||||
tp2ATR := pol.Def("tp2_atr", 8.0, core.PNorm(5.0, 12.0))
|
||||
tp3ATR := pol.Def("tp3_atr", 14.0, core.PNorm(10.0, 20.0))
|
||||
tp1Percent := pol.Def("tp1_percent", 12.0, core.PNorm(5.0, 25.0))
|
||||
tp2Percent := pol.Def("tp2_percent", 12.0, core.PNorm(5.0, 25.0))
|
||||
tp3Percent := pol.Def("tp3_percent", 12.0, core.PNorm(5.0, 25.0))
|
||||
useTakeProfit := pol.Def("use_take_profit", 0.0, core.PNorm(0.0, 1.0)) // 0=false, 1=true
|
||||
tradingDirection := pol.Def("trading_direction", 2.0, core.PNorm(0.0, 2.0)) // 0=Long, 1=Short, 2=Both
|
||||
|
||||
// 计算衍生参数
|
||||
shortEntryThreshold := longEntryThreshold - thresholdRange
|
||||
longExitThreshold := shortEntryThreshold
|
||||
shortExitThreshold := longEntryThreshold
|
||||
|
||||
// 确定最长预热周期
|
||||
warmupNum := 55 + smaPeriod + 10
|
||||
|
||||
return &strat.TradeStrat{
|
||||
Name: "FATR",
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1,
|
||||
EachMaxShort: 1,
|
||||
RunTimeFrames: []string{"4h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算真实高低点和真实波动范围
|
||||
trueLow := e.Low.Min(e.Close.Back(1))
|
||||
trueHigh := e.High.Max(e.Close.Back(1))
|
||||
trueRange := trueHigh.Sub(trueLow)
|
||||
|
||||
// 计算买入压力(BP)
|
||||
bp := e.Close.Sub(trueLow)
|
||||
|
||||
// 计算不同周期的压力比率
|
||||
calcRatio := func(len int) *ta.Series {
|
||||
sumBP := ta.Sum(bp, len)
|
||||
sumTR := ta.Sum(trueRange, len)
|
||||
return sumBP.Div(sumTR).Mul(100)
|
||||
}
|
||||
|
||||
// 计算Fibonacci数列周期的压力比率
|
||||
ratio8 := calcRatio(8)
|
||||
ratio13 := calcRatio(13)
|
||||
ratio21 := calcRatio(21)
|
||||
ratio34 := calcRatio(34)
|
||||
ratio55 := calcRatio(55)
|
||||
|
||||
// 计算加权平均值
|
||||
weightedAvg := ratio8.Mul(5).Add(
|
||||
ratio13.Mul(4)).Add(
|
||||
ratio21.Mul(3)).Add(
|
||||
ratio34.Mul(2)).Add(
|
||||
ratio55).Div(15)
|
||||
|
||||
// 应用SMA平滑
|
||||
weightedAvgSMA := ta.SMA(weightedAvg, smaPeriod)
|
||||
|
||||
// 计算ATR值
|
||||
atrValue := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
|
||||
// 获取当前值
|
||||
atrVal := atrValue.Get(0)
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 生成交易信号
|
||||
longCondition := weightedAvgSMA.Cross(longEntryThreshold) == 1
|
||||
shortCondition := weightedAvgSMA.Cross(shortEntryThreshold) == -1
|
||||
longExit := weightedAvgSMA.Cross(longExitThreshold) == -1
|
||||
shortExit := weightedAvgSMA.Cross(shortExitThreshold) == 1
|
||||
|
||||
// 执行交易逻辑
|
||||
isLongEnabled := tradingDirection == 0 || tradingDirection == 2
|
||||
isShortEnabled := tradingDirection == 1 || tradingDirection == 2
|
||||
|
||||
// 多头交易逻辑
|
||||
if isLongEnabled {
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
// 开多单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
})
|
||||
|
||||
// 设置止盈订单
|
||||
if useTakeProfit > 0.5 {
|
||||
tpPrice1 := closePrice + tp1ATR*atrVal
|
||||
tpPrice2 := closePrice + tp2ATR*atrVal
|
||||
tpPrice3 := closePrice + tp3ATR*atrVal
|
||||
|
||||
// 设置多个止盈订单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP1_Long",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: tp1Percent / 100,
|
||||
Limit: tpPrice1,
|
||||
})
|
||||
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP2_Long",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: tp2Percent / 100,
|
||||
Limit: tpPrice2,
|
||||
})
|
||||
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP3_Long",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: tp3Percent / 100,
|
||||
Limit: tpPrice3,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if longExit && len(s.LongOrders) > 0 {
|
||||
// 平多单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Long_Exit",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 空头交易逻辑
|
||||
if isShortEnabled {
|
||||
if shortCondition && len(s.ShortOrders) == 0 {
|
||||
// 开空单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
})
|
||||
|
||||
// 设置止盈订单
|
||||
if useTakeProfit > 0.5 {
|
||||
tpPrice1 := closePrice - tp1ATR*atrVal
|
||||
tpPrice2 := closePrice - tp2ATR*atrVal
|
||||
tpPrice3 := closePrice - tp3ATR*atrVal
|
||||
|
||||
// 设置多个止盈订单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP1_Short",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: tp1Percent / 100,
|
||||
Limit: tpPrice1,
|
||||
})
|
||||
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP2_Short",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: tp2Percent / 100,
|
||||
Limit: tpPrice2,
|
||||
})
|
||||
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP3_Short",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: tp3Percent / 100,
|
||||
Limit: tpPrice3,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if shortExit && len(s.ShortOrders) > 0 {
|
||||
// 平空单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Short_Exit",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
59
llmjob/1357.go
Normal file
59
llmjob/1357.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func J1357(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
fastLength := pol.DefInt("fastLength", 16, core.PNorm(5, 20))
|
||||
slowLength := pol.DefInt("slowLength", 26, core.PNorm(15, 50))
|
||||
signalLength := pol.DefInt("signalLength", 9, core.PNorm(5, 15))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowLength + signalLength,
|
||||
EachMaxLong: 3,
|
||||
EachMaxShort: 3,
|
||||
HedgeOff: true,
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算MACD指标
|
||||
macd, signal := ta.MACD(e.Close, fastLength, slowLength, signalLength)
|
||||
|
||||
// 检测MACD线与信号线的交叉
|
||||
buySignal := macd.Cross(signal)
|
||||
sellSignal := signal.Cross(macd)
|
||||
|
||||
// 买入信号:MACD线上穿信号线
|
||||
if buySignal == 1 {
|
||||
// 开多单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long Trigger",
|
||||
})
|
||||
// 平空单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Close Short Trigger",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
|
||||
// 卖出信号:MACD线下穿信号线
|
||||
if sellSignal == 1 {
|
||||
// 开空单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short Trigger",
|
||||
Short: true,
|
||||
})
|
||||
// 平多单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Close Long Trigger",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
525
llmjob/1525.go
Normal file
525
llmjob/1525.go
Normal file
@@ -0,0 +1,525 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
ta "github.com/banbox/banta"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"go.uber.org/zap"
|
||||
"math"
|
||||
)
|
||||
|
||||
/*
|
||||
根据AtrBase基于不同品种权重,限制范围后更好
|
||||
从4h根据行情自动调整加仓阈值和止盈阈值后:
|
||||
之前:夏普0.93,显示回撤35.8%,最大回撤95.4%,最大资金占用3.2W,总收益119.6%
|
||||
之后:夏普1.38,显示回撤5.66%,最大回撤36.2%,最大资金占用1.64W,总收益79.4%
|
||||
|
||||
尝试使用公式0.383*pow(1+target_qty, x)加仓,符合测试的数据,但效果更差
|
||||
*/
|
||||
|
||||
// State1525S 存储每个品种的策略状态
|
||||
type State1525S struct {
|
||||
avgPrice float64 // 平均持仓成本
|
||||
positionQty float64 // 当前持仓数量
|
||||
tpLimit float64
|
||||
score float64 // 下跌行情力度分数,值域[0,6]
|
||||
}
|
||||
|
||||
func J1525(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
smaPeriod := pol.DefInt("sma_period", 18, core.PNorm(5, 50))
|
||||
targetPercRaw := pol.Def("target_perc", -0.14, core.PNorm(-0.3, -0.05))
|
||||
takeProfitRaw := pol.Def("take_profit", 0.09, core.PNorm(0.05, 0.3))
|
||||
targetQty := pol.Def("target_qty", 0.65, core.PNorm(0.2, 1.0))
|
||||
// 加仓次数,超过20后变差;低于19后有段时间不稳定
|
||||
maxAdd := pol.DefInt("max_add", 19, core.PNorm(1, 30))
|
||||
maxOpenPair := pol.DefInt("max_open_pair", 25, core.PNorm(1, 30)) // 最大同时交易品种
|
||||
|
||||
openPairs := make(map[*strat.StratJob]int)
|
||||
pairLock := deadlock.Mutex{}
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
|
||||
|
||||
targetPerc := targetPercRaw
|
||||
takeProfit := takeProfitRaw
|
||||
|
||||
printMS := int64(0)
|
||||
maxPairs := 0
|
||||
// 加仓倍率,基于targetQty: 1, 0.6, 1 1.7 3 4.7 7.7 12.8 21.1 35 57.5 95 157 256 385 464 767
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: max(smaPeriod, baseAtrLen),
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"5m"},
|
||||
BatchInfo: true,
|
||||
OrderOnRotation: "open",
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &State1525S{}
|
||||
},
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", 20},
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*State1525S)
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 不利行情下调整参数
|
||||
targetPercJob, takeProfitJob := targetPercRaw, takeProfitRaw
|
||||
if state.score >= 5 {
|
||||
targetPercJob = targetPercRaw * 2
|
||||
takeProfitJob = takeProfitRaw * 0.6
|
||||
} else if state.score >= 4 {
|
||||
targetPercJob = targetPercRaw * 1.7
|
||||
takeProfitJob = takeProfitRaw * 0.7
|
||||
} else if state.score >= 3 {
|
||||
targetPercJob = targetPercRaw * 1.3
|
||||
takeProfitJob = takeProfitRaw * 0.8
|
||||
}
|
||||
targetPercCur := (targetPercJob + targetPerc) / 2
|
||||
takeProfitCur := (takeProfitJob + takeProfit) / 2
|
||||
|
||||
// 计算SMA
|
||||
ma := ta.SMA(e.Close, smaPeriod)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
openRate := 0.006 * closePrice / atrBase
|
||||
if openRate < 1 {
|
||||
openRate = math.Sqrt(openRate)
|
||||
}
|
||||
openRate = max(0.7, min(1.5, openRate))
|
||||
|
||||
// 计算当前持仓盈亏比例
|
||||
var pnl float64
|
||||
if state.positionQty > 0 && state.avgPrice > 0 {
|
||||
pnl = (closePrice - state.avgPrice) / state.avgPrice
|
||||
}
|
||||
|
||||
// 计算止盈价格
|
||||
if state.positionQty > 0 && state.avgPrice > 0 {
|
||||
curTakeProfit := state.avgPrice * (1 + takeProfitCur)
|
||||
if curTakeProfit != state.tpLimit {
|
||||
state.tpLimit = curTakeProfit
|
||||
// 设置限价单止盈
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Take Profit",
|
||||
Limit: state.tpLimit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 首次入场条件:价格上穿SMA且无持仓
|
||||
if e.Close.Cross(ma) == 1 && state.positionQty == 0 {
|
||||
pairLock.Lock()
|
||||
_, allow := openPairs[s]
|
||||
if !allow && len(openPairs) < maxOpenPair {
|
||||
openPairs[s] = 1
|
||||
maxPairs = max(maxPairs, len(openPairs))
|
||||
allow = true
|
||||
}
|
||||
pairLock.Unlock()
|
||||
if allow {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
CostRate: openRate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 加仓条件:亏损达到目标比例且有持仓
|
||||
if state.positionQty > 0 && pnl <= targetPercCur && len(s.LongOrders) < maxAdd {
|
||||
// 计算加仓数量,确保是整数
|
||||
odNum := len(s.LongOrders)
|
||||
addQty := math.Floor(state.positionQty * targetQty)
|
||||
pairLock.Lock()
|
||||
openPairs[s] = odNum
|
||||
pairLock.Unlock()
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: fmt.Sprintf("%v", odNum),
|
||||
Amount: addQty,
|
||||
})
|
||||
if odNum > 5 {
|
||||
printMS = e.TimeStop
|
||||
}
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
score := float64(0)
|
||||
if ta.RSI(e.Close, 14).Get(0) < 30 {
|
||||
score += 1
|
||||
}
|
||||
roc := ta.ROC(e.Close, 20).Get(0)
|
||||
if roc < -20 {
|
||||
score += 2
|
||||
} else if roc < -10 {
|
||||
score += 1
|
||||
}
|
||||
plusDi, minusDi := ta.PluMinDI(e.High, e.Low, e.Close, 14)
|
||||
adx := ta.ADX(e.High, e.Low, e.Close, 14).Get(0)
|
||||
if minusDi.Get(0) > plusDi.Get(0) {
|
||||
if adx > 40 {
|
||||
score += 2
|
||||
} else if adx > 25 {
|
||||
score += 1
|
||||
}
|
||||
}
|
||||
ma25 := ta.SMA(e.Close, 25).Get(0)
|
||||
ma99 := ta.SMA(e.Close, 99).Get(0)
|
||||
if ma99 > ma25 {
|
||||
score += 1
|
||||
}
|
||||
state, _ := s.More.(*State1525S)
|
||||
state.score = score
|
||||
},
|
||||
OnBatchInfos: func(tf string, jobs map[string]*strat.JobEnv) {
|
||||
jobNum := len(jobs)
|
||||
if jobNum < 3 {
|
||||
return
|
||||
}
|
||||
downNum, downHeavyNum := float64(0), float64(0)
|
||||
timeStop := int64(0)
|
||||
var last *strat.JobEnv
|
||||
for _, j := range jobs {
|
||||
state, _ := j.Job.More.(*State1525S)
|
||||
if state.score >= 5 {
|
||||
downHeavyNum += 1
|
||||
}
|
||||
if state.score >= 3 {
|
||||
downNum += 1
|
||||
}
|
||||
timeStop = j.Env.TimeStop
|
||||
last = j
|
||||
}
|
||||
downRate := downNum / float64(len(jobs))
|
||||
downHeavyRate := downHeavyNum / float64(len(jobs))
|
||||
if printMS > 0 {
|
||||
printMS = 0
|
||||
dateStr := btime.ToDateStr(timeStop, core.DefaultDateFmt)
|
||||
fmt.Printf("%v downRate: %.3f downHeavyRate: %.3f\n", dateStr, downRate*100, downHeavyRate*100)
|
||||
}
|
||||
if downHeavyRate >= 0.8 {
|
||||
// 极端下跌
|
||||
targetPerc = targetPercRaw * 2
|
||||
takeProfit = takeProfitRaw * 0.5
|
||||
} else if downRate >= 0.85 && downHeavyRate >= 0.6 {
|
||||
// 严重下跌
|
||||
targetPerc = targetPercRaw * 1.8
|
||||
takeProfit = takeProfitRaw * 0.5
|
||||
} else if downRate >= 0.7 && downRate <= 0.85 {
|
||||
// 市场中度下跌
|
||||
targetPerc = targetPercRaw * 1.6
|
||||
takeProfit = takeProfitRaw * 0.65
|
||||
} else if downRate >= 0.5 && downRate <= 0.7 {
|
||||
// 轻度下跌
|
||||
targetPerc = targetPercRaw * 1.3
|
||||
takeProfit = takeProfitRaw * 0.8
|
||||
} else {
|
||||
// 轻度或未下跌
|
||||
targetPerc = targetPercRaw
|
||||
takeProfit = takeProfitRaw
|
||||
}
|
||||
pairLock.Lock()
|
||||
totNum := 0
|
||||
for _, num := range openPairs {
|
||||
totNum += num
|
||||
}
|
||||
pairLock.Unlock()
|
||||
if totNum > 0 {
|
||||
last.Job.Strat.WriteOutput(fmt.Sprintf("pairNum: %v", totNum), true)
|
||||
}
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
state, _ := s.More.(*State1525S)
|
||||
|
||||
// 当订单入场时更新持仓状态
|
||||
if chgType == strat.OdChgEnterFill && od.EnterTag != "" {
|
||||
// 计算新的平均成本
|
||||
if state.positionQty == 0 {
|
||||
// 首次开仓
|
||||
state.avgPrice = od.InitPrice
|
||||
state.positionQty = od.HoldAmount()
|
||||
} else {
|
||||
// 加仓,更新平均成本
|
||||
oldQty := state.positionQty
|
||||
oldAvgPrice := state.avgPrice
|
||||
newQty := od.HoldAmount()
|
||||
newAvgPrice := od.InitPrice
|
||||
|
||||
// 计算新的平均成本
|
||||
totalQty := oldQty + newQty
|
||||
totalCost := oldQty*oldAvgPrice + newQty*newAvgPrice
|
||||
state.avgPrice = totalCost / totalQty
|
||||
state.positionQty = totalQty
|
||||
}
|
||||
}
|
||||
|
||||
// 当订单完全平仓时重置持仓状态
|
||||
if chgType == strat.OdChgExitFill && od.ExitTag != "" && od.Status == ormo.InOutStatusFullExit {
|
||||
state.avgPrice = 0
|
||||
state.positionQty = 0
|
||||
state.tpLimit = 0
|
||||
pairLock.Lock()
|
||||
delete(openPairs, s)
|
||||
pairLock.Unlock()
|
||||
}
|
||||
},
|
||||
OnShutDown: func(s *strat.StratJob) {
|
||||
if maxPairs == 0 {
|
||||
return
|
||||
}
|
||||
log.Info("max open pairs", zap.Int("num", maxPairs))
|
||||
maxPairs = 0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// State1525Sim 存储每个品种的策略状态
|
||||
type State1525Sim struct {
|
||||
totAmount float64
|
||||
totCost float64
|
||||
tpLimit float64 // 止盈出场价格
|
||||
odNum int
|
||||
upFlag float64 // 1: ma20 > ma99 && ma20 > ma20[2]
|
||||
score float64 // 下跌行情力度分数,值域[0,6]
|
||||
}
|
||||
|
||||
func J1525Sim(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 最多30个品种,单笔30,初始3W
|
||||
// 定义可调超参数
|
||||
smaPeriod := pol.DefInt("sma_period", 18, core.PNorm(5, 50))
|
||||
targetPercRaw := pol.Def("target_perc", -0.14, core.PNorm(-0.3, -0.05))
|
||||
takeProfitRaw := pol.Def("take_profit", 0.09, core.PNorm(0.05, 0.3))
|
||||
targetQty := pol.Def("target_qty", 0.6, core.PNorm(0.2, 1.0))
|
||||
// 加仓次数,超过20后变差;低于19后有段时间不稳定
|
||||
maxAdd := pol.DefInt("max_add", 19, core.PNorm(1, 30))
|
||||
startLvlRaw := pol.DefInt("start_lvl", 3, core.PNorm(1, 15))
|
||||
maxOpenPair := pol.DefInt("max_open_pair", 20, core.PNorm(1, 30)) // 最大同时交易品种
|
||||
|
||||
openPairs := make(map[*strat.StratJob]int)
|
||||
pairLock := deadlock.Mutex{}
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
|
||||
|
||||
targetPerc := targetPercRaw
|
||||
takeProfit := takeProfitRaw
|
||||
startLvl := startLvlRaw
|
||||
|
||||
// 加仓倍率,基于targetQty: 1, 0.6, 1 1.7 3 4.7 7.7 12.8 21.1 35 57.5 95 157 256 385 464 767
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: max(smaPeriod, baseAtrLen),
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"5m"},
|
||||
BatchInfo: true,
|
||||
StakeRate: 0.25,
|
||||
OrderOnRotation: "open", // 品种切换时允许开单
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &State1525Sim{}
|
||||
},
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", 20},
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*State1525Sim)
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 不利行情下调整参数
|
||||
targetPercJob, takeProfitJob := targetPercRaw, takeProfitRaw
|
||||
if state.score >= 5 {
|
||||
targetPercJob = targetPercRaw * 2
|
||||
takeProfitJob = takeProfitRaw * 0.6
|
||||
} else if state.score >= 4 {
|
||||
targetPercJob = targetPercRaw * 1.7
|
||||
takeProfitJob = takeProfitRaw * 0.7
|
||||
} else if state.score >= 3 {
|
||||
targetPercJob = targetPercRaw * 1.3
|
||||
takeProfitJob = takeProfitRaw * 0.8
|
||||
}
|
||||
targetPercCur := (targetPercJob + targetPerc) / 2
|
||||
takeProfitCur := (takeProfitJob + takeProfit) / 2
|
||||
|
||||
// 计算SMA
|
||||
ma := ta.SMA(e.Close, smaPeriod)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
openRate := 0.006 * closePrice / atrBase
|
||||
if openRate < 1 {
|
||||
openRate = math.Sqrt(openRate)
|
||||
}
|
||||
openRate = max(0.7, min(1.5, openRate))
|
||||
|
||||
// 检查止盈触发
|
||||
if state.tpLimit > 0 && state.tpLimit <= e.High.Get(0) {
|
||||
state.totAmount = 0
|
||||
state.totCost = 0
|
||||
state.tpLimit = 0
|
||||
state.odNum = 0
|
||||
pairLock.Lock()
|
||||
delete(openPairs, s)
|
||||
pairLock.Unlock()
|
||||
}
|
||||
|
||||
// 计算当前持仓盈亏比例、止盈
|
||||
var pnl, avgPrice float64
|
||||
if state.totAmount > 0 {
|
||||
avgPrice = state.totCost / state.totAmount
|
||||
pnl = closePrice/avgPrice - 1
|
||||
curTP := avgPrice * (1 + takeProfitCur)
|
||||
if curTP != state.tpLimit {
|
||||
state.tpLimit = curTP
|
||||
// 设置限价单止盈
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Take Profit",
|
||||
Limit: curTP,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 首次入场条件:价格上穿SMA且无持仓
|
||||
if e.Close.Cross(ma) == 1 && state.totAmount == 0 && openRate > 0 {
|
||||
state.odNum = 1
|
||||
state.totAmount = openRate
|
||||
state.totCost = closePrice * openRate
|
||||
}
|
||||
|
||||
// 加仓条件:亏损达到目标比例且有持仓
|
||||
if avgPrice > 0 && pnl <= targetPercCur && state.odNum < maxAdd {
|
||||
amt := state.totAmount * targetQty
|
||||
state.totAmount += amt
|
||||
state.totCost += amt * closePrice
|
||||
curStart := startLvl
|
||||
if state.upFlag < 0 {
|
||||
// 下降趋势上升3个阶梯,和全局阈值取平均
|
||||
curStart = (startLvl + startLvlRaw + 3) / 2
|
||||
}
|
||||
if state.odNum >= curStart {
|
||||
pairLock.Lock()
|
||||
_, allow := openPairs[s]
|
||||
if !allow && len(openPairs) < maxOpenPair {
|
||||
allow = true
|
||||
}
|
||||
if allow {
|
||||
openPairs[s] = len(s.LongOrders)
|
||||
}
|
||||
pairLock.Unlock()
|
||||
if allow {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: fmt.Sprintf("%v", state.odNum),
|
||||
CostRate: amt,
|
||||
})
|
||||
}
|
||||
}
|
||||
state.odNum += 1
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
score := float64(0)
|
||||
if ta.RSI(e.Close, 14).Get(0) < 30 {
|
||||
score += 1
|
||||
}
|
||||
roc := ta.ROC(e.Close, 20).Get(0)
|
||||
if roc < -20 {
|
||||
score += 2
|
||||
} else if roc < -10 {
|
||||
score += 1
|
||||
}
|
||||
plusDi, minusDi := ta.PluMinDI(e.High, e.Low, e.Close, 14)
|
||||
adx := ta.ADX(e.High, e.Low, e.Close, 14).Get(0)
|
||||
if minusDi.Get(0) > plusDi.Get(0) {
|
||||
if adx > 40 {
|
||||
score += 2
|
||||
} else if adx > 25 {
|
||||
score += 1
|
||||
}
|
||||
}
|
||||
ma7 := ta.SMA(e.Close, 7).Get(0)
|
||||
ma20 := ta.SMA(e.Close, 20)
|
||||
ma20V := ma20.Get(0)
|
||||
ma99 := ta.SMA(e.Close, 99).Get(0)
|
||||
if ma99 > ma20V {
|
||||
score += 1
|
||||
}
|
||||
state, _ := s.More.(*State1525Sim)
|
||||
state.score = score
|
||||
state.upFlag = 0
|
||||
if ma20V > ma99 && ma20V > ma20.Get(2) {
|
||||
state.upFlag = 1
|
||||
} else if ma7 < min(ma20V, ma99) {
|
||||
state.upFlag = -1
|
||||
}
|
||||
},
|
||||
OnBatchInfos: func(tf string, jobs map[string]*strat.JobEnv) {
|
||||
jobNum := len(jobs)
|
||||
if jobNum < 3 {
|
||||
return
|
||||
}
|
||||
downNum, downHeavyNum, sumUp := float64(0), float64(0), float64(0)
|
||||
var last *strat.JobEnv
|
||||
for _, j := range jobs {
|
||||
state, _ := j.Job.More.(*State1525Sim)
|
||||
if state.score >= 5 {
|
||||
downHeavyNum += 1
|
||||
}
|
||||
if state.score >= 3 {
|
||||
downNum += 1
|
||||
}
|
||||
sumUp += max(0, state.upFlag)
|
||||
last = j
|
||||
}
|
||||
downRate := downNum / float64(len(jobs))
|
||||
downHeavyRate := downHeavyNum / float64(len(jobs))
|
||||
sumUp /= float64(len(jobs))
|
||||
if downHeavyRate >= 0.8 {
|
||||
// 极端下跌
|
||||
targetPerc = targetPercRaw * 2
|
||||
takeProfit = takeProfitRaw * 0.5
|
||||
} else if downRate >= 0.85 && downHeavyRate >= 0.6 {
|
||||
// 严重下跌
|
||||
targetPerc = targetPercRaw * 1.8
|
||||
takeProfit = takeProfitRaw * 0.5
|
||||
} else if downRate >= 0.7 && downRate <= 0.85 {
|
||||
// 市场中度下跌
|
||||
targetPerc = targetPercRaw * 1.6
|
||||
takeProfit = takeProfitRaw * 0.65
|
||||
} else if downRate >= 0.5 && downRate <= 0.7 {
|
||||
// 轻度下跌
|
||||
targetPerc = targetPercRaw * 1.3
|
||||
takeProfit = takeProfitRaw * 0.8
|
||||
} else {
|
||||
// 轻度或未下跌
|
||||
targetPerc = targetPercRaw
|
||||
takeProfit = takeProfitRaw
|
||||
}
|
||||
// 调整最小开仓级别
|
||||
if sumUp > 0.7 {
|
||||
startLvl = startLvlRaw
|
||||
} else if sumUp > 0.5 && downRate <= 0.5 {
|
||||
startLvl = startLvlRaw + 2
|
||||
} else if sumUp > 0.3 && downRate <= 0.7 {
|
||||
startLvl = startLvlRaw + 4
|
||||
} else {
|
||||
startLvl = startLvlRaw + 6
|
||||
}
|
||||
pairLock.Lock()
|
||||
totNum := 0
|
||||
for _, num := range openPairs {
|
||||
totNum += num
|
||||
}
|
||||
pairLock.Unlock()
|
||||
if totNum > 0 {
|
||||
last.Job.Strat.WriteOutput(fmt.Sprintf("pairNum: %v", totNum), true)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
105
llmjob/1569.go
Normal file
105
llmjob/1569.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func J1569(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastPeriod := pol.DefInt("fastPeriod", 10, core.PNorm(5, 20))
|
||||
smaRatio := pol.Def("smaRatio", 2.0, core.PNorm(1.5, 4.0))
|
||||
supportResistancePeriod := pol.DefInt("supportResistancePeriod", 12, core.PNorm(5, 20))
|
||||
confirmPeriod := pol.DefInt("confirmPeriod", 3, core.PNorm(2, 5))
|
||||
riskRewardRatio := pol.Def("riskRewardRatio", 2.2, core.PNorm(1.5, 3.0))
|
||||
|
||||
// 计算慢速SMA周期
|
||||
slowPeriod := int(float64(fastPeriod) * smaRatio)
|
||||
|
||||
// 确保预热K线数量足够计算所有指标
|
||||
warmupNum := slowPeriod
|
||||
if supportResistancePeriod > warmupNum {
|
||||
warmupNum = supportResistancePeriod
|
||||
}
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多1个多头订单
|
||||
EachMaxShort: 1, // 最多1个空头订单
|
||||
RunTimeFrames: []string{"1h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算移动平均线
|
||||
smaFast := ta.SMA(e.Close, fastPeriod)
|
||||
smaSlow := ta.SMA(e.Close, slowPeriod)
|
||||
|
||||
// 计算支撑阻力位(过去10根K线的最高价和最低价)
|
||||
recentLow := ta.Lowest(e.Low, supportResistancePeriod)
|
||||
recentHigh := ta.Highest(e.High, supportResistancePeriod)
|
||||
|
||||
// 计算确认条件所需的指标
|
||||
highestSmaSlow := ta.Highest(smaSlow, confirmPeriod)
|
||||
lowestSmaSlow := ta.Lowest(smaSlow, confirmPeriod)
|
||||
lowestLow3 := ta.Lowest(e.Low, confirmPeriod)
|
||||
highestHigh3 := ta.Highest(e.High, confirmPeriod)
|
||||
|
||||
// 获取当前值
|
||||
closePrice := e.Close.Get(0)
|
||||
smaSlowVal := smaSlow.Get(0)
|
||||
recentLowVal := recentLow.Get(0)
|
||||
recentHighVal := recentHigh.Get(0)
|
||||
highestSmaSlowVal := highestSmaSlow.Get(0)
|
||||
lowestSmaSlowVal := lowestSmaSlow.Get(0)
|
||||
lowestLow3Val := lowestLow3.Get(0)
|
||||
highestHigh3Val := highestHigh3.Get(0)
|
||||
|
||||
// 生成交易信号 - 只计算一次交叉信号
|
||||
crossSignal := smaFast.Cross(smaSlow)
|
||||
bullishCross := crossSignal == 1 // 10周期SMA上穿20周期SMA
|
||||
bearishCross := crossSignal == -1 // 10周期SMA下穿20周期SMA
|
||||
breakoutUp := closePrice > highestSmaSlowVal // 收盘价突破过去3根K线的20周期SMA最高点
|
||||
breakoutDown := closePrice < lowestSmaSlowVal // 收盘价跌破过去3根K线的20周期SMA最低点
|
||||
retestUp := lowestLow3Val > smaSlowVal // 过去3根K线的最低价保持在20周期SMA之上
|
||||
retestDown := highestHigh3Val < smaSlowVal // 过去3根K线的最高价保持在20周期SMA之下
|
||||
|
||||
// 三重确认条件
|
||||
buyCondition := bullishCross && breakoutUp && retestUp
|
||||
sellCondition := bearishCross && breakoutDown && retestDown
|
||||
|
||||
// 计算止损止盈
|
||||
longSL := recentLowVal // 买入止损设为过去10根K线的最低价
|
||||
shortSL := recentHighVal // 卖出止损设为过去10根K线的最高价
|
||||
|
||||
riskSizeLong := closePrice - longSL // 买入交易的风险大小
|
||||
riskSizeShort := shortSL - closePrice // 卖出交易的风险大小
|
||||
|
||||
longTP := closePrice + (riskSizeLong * riskRewardRatio) // 买入交易的止盈价格
|
||||
shortTP := closePrice - (riskSizeShort * riskRewardRatio) // 卖出交易的止盈价格
|
||||
|
||||
// 执行交易
|
||||
if buyCondition && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLoss: longSL,
|
||||
TakeProfit: longTP,
|
||||
})
|
||||
}
|
||||
|
||||
if sellCondition && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLoss: shortSL,
|
||||
TakeProfit: shortTP,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
197
llmjob/1662.go
Normal file
197
llmjob/1662.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// 修复后的完整策略代码
|
||||
// 请勿修改package包名和任意函数名
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
/*
|
||||
因止盈止损触发价未检查是否合理的bug导致虚假净值曲线
|
||||
*/
|
||||
|
||||
// SuperTrendData 存储每个品种的Supertrend相关数据
|
||||
type SuperTrendData struct {
|
||||
trend int // 当前趋势方向,1表示上升趋势,-1表示下降趋势
|
||||
longTrailStop float64 // 多头仓位的移动止损价格
|
||||
shortTrailStop float64 // 空头仓位的移动止损价格
|
||||
up float64 // 上轨值
|
||||
dn float64 // 下轨值
|
||||
}
|
||||
|
||||
func J1662(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
atrPeriod := pol.DefInt("atrPeriod", 8, core.PNorm(5, 30))
|
||||
atrMultiplier := pol.Def("atrMultiplier", 2.5, core.PNorm(1.5, 5.0))
|
||||
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: baseAtrLen, // 预热K线数量,确保有足够数据计算指标
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的Supertrend数据
|
||||
s.More = &SuperTrendData{
|
||||
trend: 1, // 初始趋势设为上升
|
||||
longTrailStop: math.NaN(),
|
||||
shortTrailStop: math.NaN(),
|
||||
up: math.NaN(),
|
||||
dn: math.NaN(),
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
data, _ := s.More.(*SuperTrendData)
|
||||
|
||||
// 计算数据源 (hl2 = (high + low) / 2)
|
||||
src := ta.HL2(e.High, e.Low)
|
||||
|
||||
// 计算ATR值
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
|
||||
// 计算Supertrend指标
|
||||
upVal := src.Get(0) - atr.Get(0)*atrMultiplier
|
||||
dnVal := src.Get(0) + atr.Get(0)*atrMultiplier
|
||||
|
||||
atr2 := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr2, lenAtr), baseAtrLen).Get(0)
|
||||
|
||||
// 获取历史值
|
||||
up1 := data.up
|
||||
if math.IsNaN(up1) {
|
||||
up1 = upVal
|
||||
}
|
||||
dn1 := data.dn
|
||||
if math.IsNaN(dn1) {
|
||||
dn1 = dnVal
|
||||
}
|
||||
|
||||
// 更新上轨和下轨
|
||||
close1 := e.Close.Get(1)
|
||||
if !math.IsNaN(close1) {
|
||||
if close1 > up1 {
|
||||
upVal = math.Max(upVal, up1)
|
||||
}
|
||||
if close1 < dn1 {
|
||||
dnVal = math.Min(dnVal, dn1)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前上下轨值
|
||||
data.up = upVal
|
||||
data.dn = dnVal
|
||||
|
||||
// 判断趋势方向
|
||||
prevTrend := data.trend
|
||||
closePrice := e.Close.Get(0)
|
||||
if !math.IsNaN(closePrice) && !math.IsNaN(dn1) && !math.IsNaN(up1) && !math.IsNaN(atrBase) {
|
||||
if data.trend == -1 && closePrice > dn1 {
|
||||
data.trend = 1 // 趋势由下降转为上升
|
||||
} else if data.trend == 1 && closePrice < up1 {
|
||||
data.trend = -1 // 趋势由上升转为下降
|
||||
}
|
||||
}
|
||||
|
||||
// 生成交易信号
|
||||
buySignal := data.trend == 1 && prevTrend == -1
|
||||
sellSignal := data.trend == -1 && prevTrend == 1
|
||||
|
||||
// 执行交易策略
|
||||
if buySignal {
|
||||
// 开仓多头,使用下轨作为触发价格
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Stop: dnVal,
|
||||
Log: true,
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "XShort",
|
||||
Dirt: core.OdDirtShort,
|
||||
Log: true,
|
||||
})
|
||||
}
|
||||
|
||||
if sellSignal {
|
||||
// 开仓空头,使用上轨作为触发价格
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
Stop: upVal,
|
||||
Log: true,
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "XLong",
|
||||
Dirt: core.OdDirtLong,
|
||||
Log: true,
|
||||
})
|
||||
}
|
||||
|
||||
pipValue := atrBase * 0
|
||||
trailingValue := atrBase * 0
|
||||
|
||||
// 移动止损逻辑
|
||||
if s.OrderNum > 0 {
|
||||
// 多头移动止损
|
||||
if len(s.LongOrders) > 0 {
|
||||
// 获取持仓均价
|
||||
avgPrice := s.PositionAvgPrice(core.OdDirtLong, "")
|
||||
|
||||
if avgPrice > 0 {
|
||||
if math.IsNaN(data.longTrailStop) || closePrice > avgPrice+trailingValue {
|
||||
data.longTrailStop = e.High.Get(0) - trailingValue
|
||||
}
|
||||
|
||||
// 计算初始止损
|
||||
longStopLevel := upVal - pipValue
|
||||
// 使用更严格的止损(更接近当前价格的止损)
|
||||
finalStop := math.Max(data.longTrailStop, longStopLevel)
|
||||
if finalStop >= closePrice {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "SLLong",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
} else {
|
||||
s.SetAllStopLoss(core.OdDirtLong, &ormo.ExitTrigger{
|
||||
Price: finalStop,
|
||||
Tag: "sl",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空头移动止损
|
||||
if len(s.ShortOrders) > 0 {
|
||||
// 获取持仓均价
|
||||
avgPrice := s.PositionAvgPrice(core.OdDirtShort, "")
|
||||
|
||||
if avgPrice > 0 {
|
||||
if math.IsNaN(data.shortTrailStop) || closePrice < avgPrice-trailingValue {
|
||||
data.shortTrailStop = e.Low.Get(0) + trailingValue
|
||||
}
|
||||
|
||||
// 计算初始止损
|
||||
shortStopLevel := dnVal + pipValue
|
||||
// 使用更严格的止损(更接近当前价格的止损)
|
||||
finalStop := math.Min(data.shortTrailStop, shortStopLevel)
|
||||
if finalStop < closePrice {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "SLShort",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
} else {
|
||||
s.SetAllStopLoss(core.OdDirtShort, &ormo.ExitTrigger{
|
||||
Price: finalStop,
|
||||
Tag: "sl",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
278
llmjob/1665.go
Normal file
278
llmjob/1665.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
// StrategyState 保存每个品种的交易状态
|
||||
type StrategyState struct {
|
||||
isLong bool // 当前是否持有多头仓位
|
||||
isShort bool // 当前是否持有空头仓位
|
||||
partialTpTriggered bool // 第一止盈是否已触发
|
||||
stopPrice float64 // 止损价格
|
||||
firstTpPrice float64 // 第一止盈价格
|
||||
secondTpPrice float64 // 第二止盈价格
|
||||
firstTpQty float64 // 第一止盈数量
|
||||
positionSize float64 // 仓位大小
|
||||
entryPrice float64 // 入场价格
|
||||
}
|
||||
|
||||
func J1665(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastLength := pol.DefInt("fastLength", 5, core.PNorm(3, 15))
|
||||
emaRate := pol.Def("emaRate", 3.0, core.PNorm(1.5, 6.0))
|
||||
rsiLength := pol.DefInt("rsiLength", 7, core.PNorm(3, 21))
|
||||
atrPeriod := pol.DefInt("atrPeriod", 10, core.PNorm(5, 20))
|
||||
stopLossRate := pol.Def("stopLossRate", 5.0, core.PNorm(2.0, 10.0))
|
||||
firstTakeProfitRate := pol.Def("firstTakeProfitRate", 8.0, core.PNorm(4.0, 16.0))
|
||||
secondTakeProfitRate := pol.Def("secondTakeProfitRate", 12.0, core.PNorm(6.0, 24.0))
|
||||
firstTakeProfitQtyRate := pol.Def("firstTakeProfitQtyRate", 50.0, core.PNorm(25.0, 75.0))
|
||||
leverageRate := pol.Def("leverageRate", 3.0, core.PNorm(1.0, 5.0))
|
||||
|
||||
// 计算慢速EMA长度
|
||||
slowLength := int(float64(fastLength) * emaRate)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowLength + 26, // 确保有足够数据计算所有指标
|
||||
HedgeOff: true, // 单向持仓
|
||||
EachMaxLong: 1, // 最多一个多单
|
||||
EachMaxShort: 1, // 最多一个空单
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &StrategyState{
|
||||
isLong: false,
|
||||
isShort: false,
|
||||
partialTpTriggered: false,
|
||||
stopPrice: math.NaN(),
|
||||
firstTpPrice: math.NaN(),
|
||||
secondTpPrice: math.NaN(),
|
||||
firstTpQty: math.NaN(),
|
||||
positionSize: math.NaN(),
|
||||
entryPrice: math.NaN(),
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*StrategyState)
|
||||
|
||||
// 计算技术指标
|
||||
fastEMA := ta.EMA(e.Close, fastLength)
|
||||
slowEMA := ta.EMA(e.Close, slowLength)
|
||||
rsi := ta.RSI(e.Close, rsiLength)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
macd, signal := ta.MACD(e.Close, 12, 26, 9)
|
||||
|
||||
// 获取当前值
|
||||
closePrice := e.Close.Get(0)
|
||||
prevClosePrice := e.Close.Get(1)
|
||||
highPrice := e.High.Get(0)
|
||||
lowPrice := e.Low.Get(0)
|
||||
if math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检测MACD交叉
|
||||
macdCross := macd.Cross(signal)
|
||||
macdGolden := macdCross == 1 // MACD金叉
|
||||
macdDeath := macdCross == -1 // MACD死叉
|
||||
|
||||
// 生成交易信号
|
||||
longCondition := fastEMA.Get(0) > slowEMA.Get(0) && macdGolden && rsi.Get(0) < 70
|
||||
shortCondition := fastEMA.Get(0) < slowEMA.Get(0) && macdDeath && rsi.Get(0) > 30
|
||||
|
||||
// 平仓信号
|
||||
exitLong := shortCondition || fastEMA.Get(0) < slowEMA.Get(0)
|
||||
exitShort := longCondition || fastEMA.Get(0) > slowEMA.Get(0)
|
||||
|
||||
// 计算仓位大小
|
||||
if !math.IsNaN(atr.Get(0)) && atr.Get(0) > 0 {
|
||||
maxRiskPerTrade := 0.05
|
||||
// 修复错误1:使用动态账户权益而非固定值1000
|
||||
stakeAmount := s.Strat.GetStakeAmount(s)
|
||||
basePosition := stakeAmount * maxRiskPerTrade
|
||||
atrAmount := atr.Get(0) * closePrice
|
||||
riskPosition := basePosition / atrAmount * leverageRate
|
||||
maxPosition := stakeAmount * 0.4 / closePrice
|
||||
state.positionSize = math.Min(riskPosition, maxPosition)
|
||||
// 修复错误6:将firstTpQty的初始化移到仓位计算函数内部
|
||||
state.firstTpQty = state.positionSize * (firstTakeProfitQtyRate / 100.0)
|
||||
}
|
||||
|
||||
// 多头入场
|
||||
if longCondition && !state.isLong && !state.isShort && !math.IsNaN(state.positionSize) {
|
||||
state.entryPrice = closePrice
|
||||
state.stopPrice = closePrice * (1 - stopLossRate/100)
|
||||
state.firstTpPrice = closePrice * (1 + firstTakeProfitRate/100)
|
||||
state.secondTpPrice = closePrice * (1 + secondTakeProfitRate/100)
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
Amount: state.positionSize,
|
||||
StopLoss: state.stopPrice,
|
||||
StopLossTag: "stop_loss",
|
||||
})
|
||||
state.isLong = true
|
||||
state.partialTpTriggered = false
|
||||
}
|
||||
|
||||
// 空头入场
|
||||
if shortCondition && !state.isShort && !state.isLong && !math.IsNaN(state.positionSize) {
|
||||
state.entryPrice = closePrice
|
||||
state.stopPrice = closePrice * (1 + stopLossRate/100)
|
||||
state.firstTpPrice = closePrice * (1 - firstTakeProfitRate/100)
|
||||
state.secondTpPrice = closePrice * (1 - secondTakeProfitRate/100)
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
Short: true,
|
||||
Amount: state.positionSize,
|
||||
StopLoss: state.stopPrice,
|
||||
StopLossTag: "stop_loss",
|
||||
})
|
||||
state.isShort = true
|
||||
state.partialTpTriggered = false
|
||||
}
|
||||
|
||||
// 修复错误2:在每个K线更新时重新计算止盈止损价格
|
||||
if state.isLong {
|
||||
state.stopPrice = state.entryPrice * (1 - stopLossRate/100)
|
||||
state.firstTpPrice = state.entryPrice * (1 + firstTakeProfitRate/100)
|
||||
if !state.partialTpTriggered {
|
||||
state.secondTpPrice = state.entryPrice * (1 + secondTakeProfitRate/100)
|
||||
}
|
||||
|
||||
// 修复错误3:在触发第一止盈时立即调整第二止盈价格
|
||||
if !state.partialTpTriggered && (!math.IsNaN(prevClosePrice) && prevClosePrice >= state.firstTpPrice || highPrice >= state.firstTpPrice) {
|
||||
state.partialTpTriggered = true
|
||||
state.secondTpPrice = highPrice * (1 + 0.04) // 基于当前最高价再上涨4%
|
||||
}
|
||||
}
|
||||
|
||||
if state.isShort {
|
||||
state.stopPrice = state.entryPrice * (1 + stopLossRate/100)
|
||||
state.firstTpPrice = state.entryPrice * (1 - firstTakeProfitRate/100)
|
||||
if !state.partialTpTriggered {
|
||||
state.secondTpPrice = state.entryPrice * (1 - secondTakeProfitRate/100)
|
||||
}
|
||||
|
||||
// 修复错误3:在触发第一止盈时立即调整第二止盈价格
|
||||
if !state.partialTpTriggered && (!math.IsNaN(prevClosePrice) && prevClosePrice <= state.firstTpPrice || lowPrice <= state.firstTpPrice) {
|
||||
state.partialTpTriggered = true
|
||||
state.secondTpPrice = lowPrice * (1 - 0.04) // 基于当前最低价再下跌4%
|
||||
}
|
||||
}
|
||||
|
||||
// 多头平仓条件
|
||||
if exitLong && state.isLong {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "exit_long",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
state.isLong = false
|
||||
state.partialTpTriggered = false
|
||||
}
|
||||
|
||||
// 空头平仓条件
|
||||
if exitShort && state.isShort {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "exit_short",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
state.isShort = false
|
||||
state.partialTpTriggered = false
|
||||
}
|
||||
},
|
||||
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
||||
state, _ := s.More.(*StrategyState)
|
||||
prevClosePrice := s.Env.Close.Get(1)
|
||||
highPrice := s.Env.High.Get(0)
|
||||
lowPrice := s.Env.Low.Get(0)
|
||||
|
||||
if od.Short {
|
||||
// 空仓管理
|
||||
if state.isShort {
|
||||
// 修复错误7:使用前一根K线的收盘价和当前K线的最高价/最低价检查止损
|
||||
if (!math.IsNaN(prevClosePrice) && prevClosePrice >= state.stopPrice) || highPrice >= state.stopPrice {
|
||||
return &strat.ExitReq{
|
||||
Tag: "short_stop_loss",
|
||||
OrderID: od.ID,
|
||||
}
|
||||
}
|
||||
|
||||
// 修复错误7:使用前一根K线的收盘价和当前K线的最低价检查第一止盈
|
||||
if !state.partialTpTriggered && ((!math.IsNaN(prevClosePrice) && prevClosePrice <= state.firstTpPrice) || lowPrice <= state.firstTpPrice) {
|
||||
return &strat.ExitReq{
|
||||
Tag: "short_first_take_profit",
|
||||
OrderID: od.ID,
|
||||
Amount: state.firstTpQty,
|
||||
}
|
||||
}
|
||||
|
||||
// 修复错误7:使用前一根K线的收盘价和当前K线的最低价检查第二止盈
|
||||
if state.partialTpTriggered && ((!math.IsNaN(prevClosePrice) && prevClosePrice <= state.secondTpPrice) || lowPrice <= state.secondTpPrice) {
|
||||
state.isShort = false
|
||||
state.partialTpTriggered = false
|
||||
return &strat.ExitReq{
|
||||
Tag: "short_second_take_profit",
|
||||
OrderID: od.ID,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 多仓管理
|
||||
if state.isLong {
|
||||
// 修复错误7:使用前一根K线的收盘价和当前K线的最低价检查止损
|
||||
if (!math.IsNaN(prevClosePrice) && prevClosePrice <= state.stopPrice) || lowPrice <= state.stopPrice {
|
||||
return &strat.ExitReq{
|
||||
Tag: "long_stop_loss",
|
||||
OrderID: od.ID,
|
||||
}
|
||||
}
|
||||
|
||||
// 修复错误7:使用前一根K线的收盘价和当前K线的最高价检查第一止盈
|
||||
if !state.partialTpTriggered && ((!math.IsNaN(prevClosePrice) && prevClosePrice >= state.firstTpPrice) || highPrice >= state.firstTpPrice) {
|
||||
return &strat.ExitReq{
|
||||
Tag: "long_first_take_profit",
|
||||
OrderID: od.ID,
|
||||
Amount: state.firstTpQty,
|
||||
}
|
||||
}
|
||||
|
||||
// 修复错误7:使用前一根K线的收盘价和当前K线的最高价检查第二止盈
|
||||
if state.partialTpTriggered && ((!math.IsNaN(prevClosePrice) && prevClosePrice >= state.secondTpPrice) || highPrice >= state.secondTpPrice) {
|
||||
state.isLong = false
|
||||
state.partialTpTriggered = false
|
||||
return &strat.ExitReq{
|
||||
Tag: "long_second_take_profit",
|
||||
OrderID: od.ID,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
state, _ := s.More.(*StrategyState)
|
||||
|
||||
// 订单状态变化时更新策略状态
|
||||
if chgType == strat.OdChgExitFill {
|
||||
if od.Short {
|
||||
// 空单完全平仓
|
||||
state.isShort = false
|
||||
state.partialTpTriggered = false
|
||||
} else {
|
||||
// 多单完全平仓
|
||||
state.isLong = false
|
||||
state.partialTpTriggered = false
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
116
llmjob/199.go
Normal file
116
llmjob/199.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J199(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
emaShortPeriod := pol.DefInt("emaShortPeriod", 12, core.PNorm(5, 20))
|
||||
emaPeriodRatio := pol.Def("emaPeriodRatio", 2.5, core.PNorm(1.5, 4))
|
||||
rsiOverbought := pol.DefInt("rsiOverbought", 70, core.PNorm(60, 85))
|
||||
rsiOversold := pol.DefInt("rsiOversold", 30, core.PNorm(15, 40))
|
||||
|
||||
// 计算长期EMA周期
|
||||
emaLongPeriod := int(float64(emaShortPeriod) * emaPeriodRatio)
|
||||
|
||||
// MACD参数
|
||||
macdFastLength := 12
|
||||
macdSlowLength := 26
|
||||
macdSignalSmoothing := 9
|
||||
|
||||
// RSI参数
|
||||
rsiLength := 14
|
||||
|
||||
// 成交量移动平均周期
|
||||
volumeMAPeriod := 20
|
||||
|
||||
// 计算预热数量,取所有指标中最大的周期
|
||||
warmupNum := emaLongPeriod
|
||||
if macdSlowLength > warmupNum {
|
||||
warmupNum = macdSlowLength
|
||||
}
|
||||
if rsiLength > warmupNum {
|
||||
warmupNum = rsiLength
|
||||
}
|
||||
if volumeMAPeriod > warmupNum {
|
||||
warmupNum = volumeMAPeriod
|
||||
}
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"4h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
emaShort := ta.EMA(e.Close, emaShortPeriod)
|
||||
emaLong := ta.EMA(e.Close, emaLongPeriod)
|
||||
|
||||
macdLine, signalLine := ta.MACD(e.Close, macdFastLength, macdSlowLength, macdSignalSmoothing)
|
||||
|
||||
rsi := ta.RSI(e.Close, rsiLength)
|
||||
|
||||
volumeMA := ta.SMA(e.Volume, volumeMAPeriod)
|
||||
|
||||
// 获取当前价格
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成交易信号 - 修复重复计算EMA交叉的问题
|
||||
emaCross := emaShort.Cross(emaLong) // 只计算一次EMA交叉
|
||||
emaCrossUp := emaCross == 1 // 短期EMA上穿长期EMA
|
||||
emaCrossDown := emaCross == -1 // 短期EMA下穿长期EMA
|
||||
macdBullish := macdLine.Get(0) > signalLine.Get(0) // MACD多头信号
|
||||
macdBearish := macdLine.Get(0) < signalLine.Get(0) // MACD空头信号
|
||||
rsiNotOverbought := rsi.Get(0) < float64(rsiOverbought) // RSI未超买
|
||||
rsiNotOversold := rsi.Get(0) > float64(rsiOversold) // RSI未超卖
|
||||
volumeHigh := e.Volume.Get(0) > volumeMA.Get(0) // 成交量高于平均
|
||||
|
||||
// 交易条件判断
|
||||
buyCondition := emaCrossUp && macdBullish && rsiNotOverbought && volumeHigh
|
||||
sellCondition := emaCrossDown && macdBearish && rsiNotOversold && volumeHigh
|
||||
|
||||
// 计算止损止盈价格 - 修复为使用固定比例,与原始策略一致
|
||||
//longStopLoss := closePrice * 0.95
|
||||
//longTakeProfit := closePrice * 1.05
|
||||
shortStopLoss := closePrice * 1.05
|
||||
shortTakeProfit := closePrice * 0.95
|
||||
|
||||
// 执行交易
|
||||
_ = buyCondition
|
||||
//if buyCondition && len(s.LongOrders) == 0 {
|
||||
// s.OpenOrder(&strat.EnterReq{
|
||||
// Tag: "Buy",
|
||||
// OrderType: core.OrderTypeMarket,
|
||||
// StopLoss: longStopLoss,
|
||||
// TakeProfit: longTakeProfit,
|
||||
// StopLossTag: "Stop Loss",
|
||||
// TakeProfitTag: "Take Profit",
|
||||
// })
|
||||
//}
|
||||
|
||||
if sellCondition && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLoss: shortStopLoss,
|
||||
TakeProfit: shortTakeProfit,
|
||||
StopLossTag: "Stop Loss",
|
||||
TakeProfitTag: "Take Profit",
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
68
llmjob/2251.go
Normal file
68
llmjob/2251.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func J2251(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastPeriod := pol.DefInt("fastPeriod", 21, core.PNorm(5, 50))
|
||||
slowPeriod := pol.DefInt("slowPeriod", 55, core.PNorm(30, 100))
|
||||
slMultiplier := pol.Def("slMultiplier", 1.5, core.PNorm(0.8, 3))
|
||||
tpMultiplier := pol.Def("tpMultiplier", 1, core.PNorm(0.5, 2.5))
|
||||
atrLength := pol.DefInt("atrLength", 14, core.PNorm(7, 30))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowPeriod, // 预热数量取较长的EMA周期
|
||||
HedgeOff: true, // 单向持仓模式,与PineScript一致
|
||||
RunTimeFrames: []string{"4h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
fastMA := ta.EMA(e.Close, fastPeriod)
|
||||
slowMA := ta.EMA(e.Close, slowPeriod)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrLength)
|
||||
|
||||
// 获取最新值
|
||||
atrVal := atr.Get(0)
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 检测EMA交叉信号
|
||||
//goLong := fastMA.Cross(slowMA) == 1
|
||||
goShort := fastMA.Cross(slowMA) == -1
|
||||
|
||||
// 金叉信号:做多
|
||||
//if goLong && s.OrderNum == 0 {
|
||||
// // 计算止损和止盈价格
|
||||
// slPrice := closePrice - atrVal*slMultiplier
|
||||
// tpPrice := closePrice + atrVal*tpMultiplier*2
|
||||
// s.OpenOrder(&strat.EnterReq{
|
||||
// Tag: "Long",
|
||||
// Short: false,
|
||||
// Limit: closePrice - atrVal*slMultiplier*0.7,
|
||||
// StopBars: 7,
|
||||
// StopLoss: slPrice, // 做多止损价格
|
||||
// TakeProfit: tpPrice, // 做多止盈价格
|
||||
// })
|
||||
//}
|
||||
|
||||
// 死叉信号:做空
|
||||
if goShort && s.OrderNum == 0 {
|
||||
// 计算止损和止盈价格
|
||||
slPrice := closePrice + atrVal*slMultiplier
|
||||
tpPrice := closePrice - atrVal*tpMultiplier
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
StopLoss: slPrice, // 做空止损价格
|
||||
TakeProfit: tpPrice, // 做空止盈价格
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
174
llmjob/2391.go
Normal file
174
llmjob/2391.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
// Sta2391 用于保存每个品种的策略状态
|
||||
type Sta2391 struct {
|
||||
posReversal123 float64 // 123反转策略的信号值
|
||||
posmStochCross float64 // 随机指标交叉策略的信号值
|
||||
}
|
||||
|
||||
func J2391(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 123反转策略参数
|
||||
length := pol.DefInt("length", 40, core.PNorm(5, 30))
|
||||
kSmoothing := pol.DefInt("kSmoothing", 1, core.PNorm(1, 5))
|
||||
dLength := pol.DefInt("dLength", 3, core.PNorm(1, 5))
|
||||
level := pol.Def("level", 30, core.PNorm(30, 70))
|
||||
|
||||
// 随机指标交叉策略参数
|
||||
lengthSC := pol.DefInt("lengthSC", 20, core.PNorm(3, 21))
|
||||
dLengthSC := pol.DefInt("dLengthSC", 3, core.PNorm(1, 5))
|
||||
oversold := pol.Def("oversold", 10, core.PNorm(10, 30))
|
||||
overbought := pol.Def("overbought", 90, core.PNorm(70, 90))
|
||||
reverse := pol.Def("reverse", 0, core.PUniform(0, 1)) // 0表示不反转,1表示反转
|
||||
|
||||
// 计算预热数量,取两个策略中需要的历史数据最大值
|
||||
warmupNum := length
|
||||
if lengthSC > warmupNum {
|
||||
warmupNum = lengthSC
|
||||
}
|
||||
if kSmoothing+dLength > warmupNum {
|
||||
warmupNum = kSmoothing + dLength
|
||||
}
|
||||
if dLengthSC > warmupNum {
|
||||
warmupNum = dLengthSC
|
||||
}
|
||||
// 额外需要2个历史K线判断价格反转
|
||||
warmupNum += 2
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
TimeFrames: "8h",
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &Sta2391{
|
||||
posReversal123: 0,
|
||||
posmStochCross: 0,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*Sta2391)
|
||||
|
||||
// 获取价格数据
|
||||
close0 := e.Close.Get(0) // 当前收盘价
|
||||
close1 := e.Close.Get(1) // 前一个收盘价
|
||||
close2 := e.Close.Get(2) // 前两个收盘价
|
||||
|
||||
// 1. 计算123反转策略信号
|
||||
// 计算随机指标
|
||||
kFast := ta.Stoch(e.High, e.Low, e.Close, length)
|
||||
// 对K值进行平滑
|
||||
vFast := ta.SMA(kFast, kSmoothing)
|
||||
// 计算D值
|
||||
vSlow := ta.SMA(vFast, dLength)
|
||||
|
||||
// 获取当前值
|
||||
vFastVal := vFast.Get(0)
|
||||
vSlowVal := vSlow.Get(0)
|
||||
|
||||
// 判断123反转策略信号
|
||||
if close2 < close1 && close1 < close0 && vFastVal < vSlowVal && vFastVal > level {
|
||||
// 买入信号:两天内收盘价从低点转为高点,且随机指标快速线在慢速线下方但高于阈值
|
||||
state.posReversal123 = 1
|
||||
} else if close2 > close1 && close1 > close0 && vFastVal > vSlowVal && vFastVal < level {
|
||||
// 卖出信号:两天内收盘价从高点转为低点,且随机指标快速线在慢速线上方但低于阈值
|
||||
state.posReversal123 = -1
|
||||
}
|
||||
// 否则保持之前的信号(不需要else,因为不改变state.posReversal123的值)
|
||||
|
||||
// 2. 计算随机指标交叉策略信号
|
||||
// 计算随机指标
|
||||
kFastSC := ta.Stoch(e.High, e.Low, e.Close, lengthSC)
|
||||
// 计算D值
|
||||
vSlowSC := ta.SMA(kFastSC, dLengthSC)
|
||||
|
||||
// 获取当前值
|
||||
vFastSCVal := kFastSC.Get(0)
|
||||
vSlowSCVal := vSlowSC.Get(0)
|
||||
|
||||
// 判断随机指标交叉策略信号
|
||||
if vFastSCVal >= vSlowSCVal && vFastSCVal < oversold && vSlowSCVal < oversold {
|
||||
// 买入信号:快速线在慢速线上方,且两者都处于超卖区域
|
||||
state.posmStochCross = 1
|
||||
} else if vFastSCVal < vSlowSCVal && vFastSCVal > overbought && vSlowSCVal > overbought {
|
||||
// 卖出信号:快速线在慢速线下方,且两者都处于超买区域
|
||||
state.posmStochCross = -1
|
||||
}
|
||||
// 否则保持之前的信号(不需要else,因为不改变state.posmStochCross的值)
|
||||
|
||||
// 3. 组合信号
|
||||
var pos float64
|
||||
if state.posReversal123 == 1 && state.posmStochCross == 1 {
|
||||
// 两个策略都给出买入信号
|
||||
pos = 1
|
||||
} else if state.posReversal123 == -1 && state.posmStochCross == -1 {
|
||||
// 两个策略都给出卖出信号
|
||||
pos = -1
|
||||
} else {
|
||||
// 信号不一致,无交易信号
|
||||
pos = 0
|
||||
}
|
||||
|
||||
// 4. 处理信号反转
|
||||
var possig float64
|
||||
if reverse == 1 { // 明确检查reverse是否为1
|
||||
if pos == 1 {
|
||||
possig = -1 // 反转买入信号为卖出信号
|
||||
} else if pos == -1 {
|
||||
possig = 1 // 反转卖出信号为买入信号
|
||||
} else {
|
||||
possig = 0
|
||||
}
|
||||
} else {
|
||||
possig = pos // 不反转信号
|
||||
}
|
||||
|
||||
// 5. 执行交易
|
||||
if possig == 1 {
|
||||
// 买入信号
|
||||
if len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
})
|
||||
}
|
||||
// 如果有做空持仓,平仓
|
||||
if len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "close_short",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
} else if possig == -1 {
|
||||
// 卖出信号
|
||||
if len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
// 如果有做多持仓,平仓
|
||||
if len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "close_long",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
} else if possig == 0 {
|
||||
// 无信号,平仓所有持仓
|
||||
if len(s.LongOrders) > 0 || len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "close_all",
|
||||
Dirt: core.OdDirtBoth,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
144
llmjob/2439.go
Normal file
144
llmjob/2439.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WeekendData struct {
|
||||
FridayClose float64
|
||||
HasLong bool
|
||||
TotCost float64
|
||||
TotAmt float64
|
||||
}
|
||||
|
||||
func J2439(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
entryThreshold := pol.Def("entryThreshold", 0.02, core.PNorm(0.01, 0.1))
|
||||
profitTakingPercentThreshold := pol.Def("profitTakingPercentThreshold", 0.06, core.PNorm(0.01, 0.08))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 7, // 需要最多7天的数据来获取周五收盘价
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"5m"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &WeekendData{FridayClose: math.NaN()}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
data, _ := s.More.(*WeekendData)
|
||||
currentTime := time.Unix(e.TimeStop/1000, 0)
|
||||
weekday := currentTime.Weekday()
|
||||
|
||||
// 计算距离周五的天数
|
||||
var daysSinceFriday int
|
||||
switch weekday {
|
||||
case time.Saturday:
|
||||
daysSinceFriday = 1
|
||||
case time.Sunday:
|
||||
daysSinceFriday = 0
|
||||
case time.Monday:
|
||||
daysSinceFriday = 6
|
||||
case time.Tuesday:
|
||||
daysSinceFriday = 5
|
||||
case time.Wednesday:
|
||||
daysSinceFriday = 4
|
||||
case time.Thursday:
|
||||
daysSinceFriday = 3
|
||||
case time.Friday:
|
||||
daysSinceFriday = 2
|
||||
}
|
||||
|
||||
// 获取周五收盘价 - 使用日线数据
|
||||
if e.Close.Len() > daysSinceFriday {
|
||||
data.FridayClose = e.Close.Get(daysSinceFriday)
|
||||
}
|
||||
|
||||
// 如果没有有效的周五收盘价,不执行交易逻辑
|
||||
if math.IsNaN(data.FridayClose) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查止盈条件
|
||||
if s.EnteredNum > 0 {
|
||||
totalProfitRate := 0.0
|
||||
for _, od := range s.LongOrders {
|
||||
if od.Status >= ormo.InOutStatusPartEnter {
|
||||
totalProfitRate += od.ProfitRate
|
||||
}
|
||||
}
|
||||
for _, od := range s.ShortOrders {
|
||||
if od.Status >= ormo.InOutStatusPartEnter {
|
||||
totalProfitRate += od.ProfitRate
|
||||
}
|
||||
}
|
||||
|
||||
if totalProfitRate > profitTakingPercentThreshold {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "profit_take",
|
||||
Dirt: core.OdDirtBoth,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 周末交易逻辑(周日和周一)
|
||||
if weekday == time.Sunday || weekday == time.Monday {
|
||||
currentPrice := e.Close.Get(0)
|
||||
if math.IsNaN(currentPrice) {
|
||||
return
|
||||
}
|
||||
|
||||
upperThreshold := data.FridayClose * (1 + entryThreshold)
|
||||
lowerThreshold := data.FridayClose * (1 - entryThreshold)
|
||||
|
||||
// 没有持仓时的入场逻辑
|
||||
if s.OrderNum == 0 {
|
||||
if currentPrice > upperThreshold {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short_entry",
|
||||
Short: true,
|
||||
})
|
||||
} else if currentPrice < lowerThreshold {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long_entry",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 已有持仓时的加仓逻辑
|
||||
for _, od := range s.ShortOrders {
|
||||
if od.Status >= ormo.InOutStatusPartEnter && currentPrice > od.InitPrice*(1+entryThreshold) {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "add_short",
|
||||
Short: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, od := range s.LongOrders {
|
||||
if od.Status >= ormo.InOutStatusPartEnter {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "add_long",
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 周二平仓逻辑
|
||||
if weekday == time.Tuesday {
|
||||
if s.OrderNum > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "monday_close",
|
||||
Dirt: core.OdDirtBoth,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
103
llmjob/2523.go
Normal file
103
llmjob/2523.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
/*
|
||||
以大止损实现的高胜率,订单太少
|
||||
尝试使用atrBase针对CostRate、TakeProfit、StopLoss进行自适应,效果都更差。
|
||||
*/
|
||||
|
||||
func J2523(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
maLen := pol.DefInt("maLen", 240, core.PNorm(50, 300))
|
||||
adxThres := pol.Def("adxThres", 20, core.PNorm(15, 35))
|
||||
slRate := pol.Def("slRate", 2.5, core.PNorm(0.8, 5.0))
|
||||
|
||||
// 计算预热K线数,确保所有指标都能正确计算
|
||||
warmupNum := maLen + 20
|
||||
|
||||
//rates := make([]float64, 0, 10)
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
RunTimeFrames: []string{"3d"},
|
||||
StakeRate: 20,
|
||||
EachMaxLong: 1,
|
||||
EachMaxShort: 1,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
maBig := ta.SMA(e.Close, maLen)
|
||||
rsi := ta.RSI(e.Close, 14)
|
||||
// 使用DMI计算ADX,与原始策略保持一致
|
||||
adx := ta.ADX(e.High, e.Low, e.Close, 14)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, 14)
|
||||
|
||||
// 获取当前值和历史值,避免重复计算
|
||||
curClose := e.Close.Get(0)
|
||||
curMA := maBig.Get(0)
|
||||
curRSI := rsi.Get(0)
|
||||
curADX := adx.Get(0)
|
||||
curATR := atr.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(curClose) || math.IsNaN(curMA) || math.IsNaN(curRSI) ||
|
||||
math.IsNaN(curADX) || math.IsNaN(curATR) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查2根K线前是否发生交叉
|
||||
closeXBig := e.Close.Cross(maBig)
|
||||
rsi2 := rsi.Get(2)
|
||||
adxOk := adx.Get(2) > adxThres
|
||||
buyConfirmation := closeXBig == 3 && (rsi2 > 40) && adxOk
|
||||
sellConfirmation := closeXBig == -3 && (rsi2 < 60) && adxOk
|
||||
|
||||
// 执行交易逻辑
|
||||
if buyConfirmation {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
TakeProfitVal: curClose * 0.02, // 2%止盈
|
||||
StopLossVal: slRate * curATR,
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "XShort",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
|
||||
if sellConfirmation {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
Short: true,
|
||||
TakeProfitVal: curClose * 0.02, // 2%止盈
|
||||
StopLossVal: slRate * curATR,
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "XLong",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
},
|
||||
//OnShutDown: func(s *strat.StratJob) {
|
||||
// if len(rates) == 0 {
|
||||
// return
|
||||
// }
|
||||
// sort.Float64s(rates)
|
||||
// size := len(rates) / 10
|
||||
// for i := 0; i < len(rates); i += size {
|
||||
// block := rates[i:min(len(rates), i+size)]
|
||||
// avg := floats.Sum(block) / float64(len(block))
|
||||
// fmt.Printf("rate %v: %.3f\n", i, avg)
|
||||
// }
|
||||
// rates = nil
|
||||
//},
|
||||
}
|
||||
}
|
||||
168
llmjob/2625.go
Normal file
168
llmjob/2625.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
// AtrRsiStrategy 存储每个品种的持仓期间最高价和最低价
|
||||
type AtrRsiStrategy struct {
|
||||
IntraTradeHigh float64
|
||||
IntraTradeLow float64
|
||||
}
|
||||
|
||||
func J2625(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
atrLength := pol.DefInt("atr_length", 18, core.PNorm(10, 50))
|
||||
atrMaRate := pol.Def("atr_ma_rate", 1.8, core.PNorm(1.2, 3.0))
|
||||
rsiLength := pol.DefInt("rsi_length", 9, core.PNorm(7, 30))
|
||||
rsiEntry := pol.DefInt("rsi_entry", 7, core.PNorm(5, 20))
|
||||
atrMaNormMin := pol.Def("atr_ma_norm_min", 0.3, core.PNorm(0.1, 0.5))
|
||||
atrMaNormMax := pol.Def("atr_ma_norm_max", 0.7, core.PNorm(0.5, 1.0))
|
||||
trailingPercent := pol.Def("trailing_percent", 1.3, core.PNorm(0.5, 3.0))
|
||||
|
||||
// 计算ATR移动平均线周期
|
||||
atrMaLength := int(float64(atrLength) * atrMaRate)
|
||||
|
||||
// 计算RSI买入和卖出阈值
|
||||
rsiBuy := 50 + float64(rsiEntry)
|
||||
rsiSell := 50 - float64(rsiEntry)
|
||||
|
||||
// 确定预热数量,取最大参数值
|
||||
warmupNum := atrMaLength
|
||||
if rsiLength > warmupNum {
|
||||
warmupNum = rsiLength
|
||||
}
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"1h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的持仓期间最高价和最低价
|
||||
s.More = &AtrRsiStrategy{
|
||||
IntraTradeHigh: 0,
|
||||
IntraTradeLow: 0,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
m, _ := s.More.(*AtrRsiStrategy)
|
||||
|
||||
// 计算技术指标
|
||||
atrValue := ta.ATR(e.High, e.Low, e.Close, atrLength)
|
||||
atrMa := ta.SMA(atrValue, atrMaLength)
|
||||
rsiValue := ta.RSI(e.Close, rsiLength)
|
||||
|
||||
// 获取当前价格
|
||||
closePrice := e.Close.Get(0)
|
||||
highPrice := e.High.Get(0)
|
||||
lowPrice := e.Low.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) || math.IsNaN(highPrice) || math.IsNaN(lowPrice) ||
|
||||
math.IsNaN(atrValue.Get(0)) || math.IsNaN(atrMa.Get(0)) || math.IsNaN(rsiValue.Get(0)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算ATR移动平均线的标准化值
|
||||
atrMaNorm := atrMa.Get(0) / closePrice * 100
|
||||
|
||||
// 获取当前持仓状态
|
||||
longOrders := s.LongOrders
|
||||
shortOrders := s.ShortOrders
|
||||
hasLongPosition := len(longOrders) > 0
|
||||
hasShortPosition := len(shortOrders) > 0
|
||||
|
||||
// 无持仓状态
|
||||
if !hasLongPosition && !hasShortPosition {
|
||||
// 重置持仓期间最高价和最低价
|
||||
m.IntraTradeHigh = highPrice
|
||||
m.IntraTradeLow = lowPrice
|
||||
|
||||
// 检查波动率是否在适合交易范围内
|
||||
if atrMaNorm >= atrMaNormMin && atrMaNorm <= atrMaNormMax {
|
||||
// 检查当前波动率是否高于平均波动率
|
||||
if atrValue.Get(0) > atrMa.Get(0) {
|
||||
// 检查RSI信号
|
||||
if rsiValue.Get(0) > rsiBuy {
|
||||
// RSI超买,做多
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "B1",
|
||||
OrderType: core.OrderTypeLimit,
|
||||
Limit: closePrice + 5,
|
||||
})
|
||||
} else if rsiValue.Get(0) < rsiSell {
|
||||
// RSI超卖,做空
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "S1",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeLimit,
|
||||
Limit: closePrice - 5,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if hasLongPosition {
|
||||
// 多头持仓状态
|
||||
// 更新持仓期间最高价
|
||||
if highPrice > m.IntraTradeHigh {
|
||||
m.IntraTradeHigh = highPrice
|
||||
}
|
||||
m.IntraTradeLow = lowPrice
|
||||
|
||||
// 计算多头追踪止损价格
|
||||
longTp := m.IntraTradeHigh * (1 - trailingPercent/100)
|
||||
|
||||
// 获取入场价格
|
||||
enterPrice := longOrders[0].InitPrice
|
||||
|
||||
// 设置止损和止盈
|
||||
for _, od := range longOrders {
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: longTp,
|
||||
Rate: 1.0, // 全部退出
|
||||
Tag: "trailing_stop",
|
||||
})
|
||||
|
||||
od.SetTakeProfit(&ormo.ExitTrigger{
|
||||
Price: enterPrice * 1.03,
|
||||
Rate: 1.0, // 全部退出
|
||||
Tag: "take_profit",
|
||||
})
|
||||
}
|
||||
} else if hasShortPosition {
|
||||
// 空头持仓状态
|
||||
m.IntraTradeHigh = highPrice
|
||||
// 更新持仓期间最低价
|
||||
if lowPrice < m.IntraTradeLow {
|
||||
m.IntraTradeLow = lowPrice
|
||||
}
|
||||
|
||||
// 计算空头追踪止损价格
|
||||
shortTp := m.IntraTradeLow * (1 + trailingPercent/100)
|
||||
|
||||
// 获取入场价格
|
||||
enterPrice := shortOrders[0].InitPrice
|
||||
|
||||
// 设置止损和止盈
|
||||
for _, od := range shortOrders {
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: shortTp,
|
||||
Rate: 1.0, // 全部退出
|
||||
Tag: "trailing_stop",
|
||||
})
|
||||
|
||||
od.SetTakeProfit(&ormo.ExitTrigger{
|
||||
Price: enterPrice * 0.94,
|
||||
Rate: 1.0, // 全部退出
|
||||
Tag: "take_profit",
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
104
llmjob/2775.go
Normal file
104
llmjob/2775.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
type MacdRsiState struct {
|
||||
above50 bool // 标记RSI是否上穿50
|
||||
below50 bool // 标记RSI是否下穿50
|
||||
longEntryTime int64 // 记录多头入场时间
|
||||
shortEntryTime int64 // 记录空头入场时间
|
||||
}
|
||||
|
||||
func J2775(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
fastPeriod := pol.DefInt("fastPeriod", 15, core.PNorm(5, 20))
|
||||
macdRate := pol.Def("macdRate", 2.5, core.PNorm(1.5, 4))
|
||||
rsiPeriod := pol.DefInt("rsiPeriod", 16, core.PNorm(7, 21))
|
||||
exitBars := pol.DefInt("exitBars", 5, core.PNorm(1, 10))
|
||||
signalSmoothing := pol.DefInt("signalSmoothing", 9, core.PNorm(5, 15))
|
||||
|
||||
// 计算MACD慢线长度
|
||||
slowPeriod := int(float64(fastPeriod) * macdRate)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowPeriod + signalSmoothing + rsiPeriod,
|
||||
HedgeOff: true, // 单向持仓
|
||||
EachMaxLong: 1, // 最多一个多头订单
|
||||
EachMaxShort: 1, // 最多一个空头订单
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &MacdRsiState{
|
||||
above50: false,
|
||||
below50: false,
|
||||
longEntryTime: 0,
|
||||
shortEntryTime: 0,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*MacdRsiState)
|
||||
|
||||
// 计算MACD指标
|
||||
macdLine, signalLine := ta.MACD(e.Close, fastPeriod, slowPeriod, signalSmoothing)
|
||||
|
||||
// 计算RSI指标
|
||||
rsi := ta.RSI(e.Close, rsiPeriod)
|
||||
|
||||
// 检测MACD金叉和死叉
|
||||
bullishCross := macdLine.Cross(signalLine) == 1
|
||||
bearishCross := macdLine.Cross(signalLine) == -1
|
||||
|
||||
// 更新RSI状态
|
||||
if rsi.Get(0) > 50 && rsi.Get(1) <= 50 {
|
||||
state.above50 = true
|
||||
}
|
||||
if rsi.Get(0) < 50 && rsi.Get(1) >= 50 {
|
||||
state.below50 = true
|
||||
}
|
||||
|
||||
// 执行交易信号
|
||||
if bullishCross && state.above50 && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
})
|
||||
state.longEntryTime = e.TimeStop
|
||||
}
|
||||
|
||||
if bearishCross && state.below50 && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
})
|
||||
state.shortEntryTime = e.TimeStop
|
||||
}
|
||||
|
||||
// 检查退出条件
|
||||
if len(s.LongOrders) > 0 && state.longEntryTime > 0 {
|
||||
holdBars := e.BarCount(state.longEntryTime)
|
||||
if holdBars >= float64(exitBars) {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Exit Long",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
state.longEntryTime = 0
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ShortOrders) > 0 && state.shortEntryTime > 0 {
|
||||
holdBars := e.BarCount(state.shortEntryTime)
|
||||
if holdBars >= float64(exitBars) {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Exit Short",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
state.shortEntryTime = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
94
llmjob/2874.go
Normal file
94
llmjob/2874.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J2874(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
rsiPeriod := pol.DefInt("rsiPeriod", 70, core.PNorm(20, 100))
|
||||
longExitThreshold := pol.Def("longExitThreshold", 0.65, core.PNorm(0.55, 0.8))
|
||||
shortExitThreshold := pol.Def("shortExitThreshold", 0.35, core.PNorm(0.2, 0.45))
|
||||
allowLong := pol.Def("allowLong", 1.0, core.PUniform(0, 1)) > 0.5
|
||||
allowShort := pol.Def("allowShort", 1.0, core.PUniform(0, 1)) > 0.5
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: rsiPeriod, // 预热期至少为RSI周期
|
||||
HedgeOff: true, // 单向持仓模式,与原PineScript策略一致
|
||||
RunTimeFrames: []string{"4h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算各种价格组合
|
||||
ohlc4 := ta.HLC3(e.High, e.Low, e.Close).Add(e.Open).Div(4)
|
||||
hlc3 := ta.HLC3(e.High, e.Low, e.Close)
|
||||
hl2 := ta.HL2(e.High, e.Low)
|
||||
|
||||
// 计算各种RSI值并归一化(除以100)
|
||||
rsiOhlc4 := ta.RSI(ohlc4, rsiPeriod).Div(100)
|
||||
rsiClose := ta.RSI(e.Close, rsiPeriod).Div(100)
|
||||
rsiOpen := ta.RSI(e.Open, rsiPeriod).Div(100)
|
||||
rsiHigh := ta.RSI(e.High, rsiPeriod).Div(100)
|
||||
rsiHlc3 := ta.RSI(hlc3, rsiPeriod).Div(100)
|
||||
rsiHl2 := ta.RSI(hl2, rsiPeriod).Div(100)
|
||||
|
||||
// 计算RSI均值
|
||||
rsiAvg := rsiOhlc4.Add(rsiClose).Add(rsiOpen).Add(rsiHigh).Add(rsiHlc3).Add(rsiHl2).Div(6)
|
||||
rsiAvgVal := rsiAvg.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(rsiAvgVal) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检测RSI均值与0.5的交叉
|
||||
entryThreshold := 0.5
|
||||
crossSignal := rsiAvg.Cross(entryThreshold)
|
||||
|
||||
// 生成交易信号
|
||||
longSignal := crossSignal == 1 // RSI均值上穿0.5
|
||||
shortSignal := crossSignal == -1 // RSI均值下穿0.5
|
||||
|
||||
// 生成平仓信号
|
||||
longExit := rsiAvgVal >= longExitThreshold || shortSignal
|
||||
shortExit := rsiAvgVal <= shortExitThreshold || longSignal
|
||||
|
||||
// 执行交易逻辑
|
||||
if allowLong && longSignal && len(s.LongOrders) == 0 {
|
||||
// 开多头仓位
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
})
|
||||
}
|
||||
|
||||
if allowShort && shortSignal && len(s.ShortOrders) == 0 {
|
||||
// 开空头仓位
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
|
||||
if allowLong && len(s.LongOrders) > 0 && longExit {
|
||||
// 平多头仓位
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "long_exit",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
if allowShort && len(s.ShortOrders) > 0 && shortExit {
|
||||
// 平空头仓位
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "short_exit",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
110
llmjob/2876.go
Normal file
110
llmjob/2876.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J2876(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
frequency := pol.DefInt("frequency", 28, core.PNorm(5, 30))
|
||||
rsiFrequency := pol.DefInt("rsiFrequency", 220, core.PNorm(14, 100))
|
||||
buyZoneDistance := pol.Def("buyZoneDistance", 7, core.PNorm(2, 15))
|
||||
avgDownATRSum := pol.DefInt("avgDownATRSum", 3, core.PNorm(1, 10))
|
||||
useAbsoluteRSIBarrier := pol.Def("useAbsoluteRSIBarrier", 1, core.PUniform(0, 1)) // 1表示true,0表示false
|
||||
barrierLevel := pol.Def("barrierLevel", 45, core.PNorm(30, 70))
|
||||
|
||||
// 计算预热数量,取RSI周期和均线周期中的较大值
|
||||
warmupNum := rsiFrequency
|
||||
if frequency > rsiFrequency {
|
||||
warmupNum = frequency
|
||||
}
|
||||
// 再加上ATR周期
|
||||
warmupNum += 20
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"15m"},
|
||||
EachMaxLong: 3, // 最多允许3个多头订单,对应原策略的pyramiding=3
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
momentumRSI := ta.RSI(e.Close, rsiFrequency)
|
||||
momentumRSI_slow := ta.SMA(momentumRSI, frequency)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, 20)
|
||||
|
||||
// 获取当前值
|
||||
rsiValue := momentumRSI.Get(0)
|
||||
rsiSlowValue := momentumRSI_slow.Get(0)
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(rsiValue) || math.IsNaN(rsiSlowValue) || math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算买卖区域
|
||||
buyZoneLower := rsiSlowValue * (1 - buyZoneDistance/100)
|
||||
|
||||
// 生成交易信号
|
||||
useBarrier := useAbsoluteRSIBarrier > 0.5
|
||||
momentumRSISoftClose := (rsiValue > rsiSlowValue) && (rsiValue > barrierLevel || !useBarrier)
|
||||
isClose := momentumRSISoftClose
|
||||
|
||||
// 计算风险管理条件
|
||||
riskCondition := true
|
||||
if len(s.LongOrders) > 0 {
|
||||
// 计算平均持仓价格
|
||||
totalCost := 0.0
|
||||
totalAmount := 0.0
|
||||
for _, od := range s.LongOrders {
|
||||
if od.Status >= ormo.InOutStatusPartEnter {
|
||||
totalCost += od.EnterCost()
|
||||
totalAmount += od.HoldAmount()
|
||||
}
|
||||
}
|
||||
if totalAmount > 0 {
|
||||
avgPrice := totalCost / totalAmount
|
||||
// 计算过去avgDownATRSum个周期的ATR值之和
|
||||
atrSum := 0.0
|
||||
for i := 0; i < avgDownATRSum; i++ {
|
||||
atrVal := atr.Get(i)
|
||||
if !math.IsNaN(atrVal) {
|
||||
atrSum += atrVal
|
||||
}
|
||||
}
|
||||
// 计算风险条件:平均持仓价格 - ATR求和值 * 开仓数量 > 当前价格
|
||||
riskCondition = avgPrice-atrSum*float64(len(s.LongOrders)) > closePrice
|
||||
}
|
||||
}
|
||||
|
||||
// 买入信号:RSI低于买入区域下限且满足风险条件
|
||||
isBuy := rsiValue < buyZoneLower && (riskCondition || len(s.LongOrders) == 0)
|
||||
|
||||
// 执行交易操作
|
||||
if isBuy && len(s.LongOrders) < 3 { // 限制最多3个多头订单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
Short: false,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
}
|
||||
|
||||
// 平仓信号
|
||||
if isClose && len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Close",
|
||||
Dirt: core.OdDirtLong,
|
||||
OrderType: core.OrderTypeLimit,
|
||||
Limit: closePrice,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
269
llmjob/2984.go
Normal file
269
llmjob/2984.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
// ZeroLagEMA 计算零滞后指数移动平均线
|
||||
func ZeroLagEMA(source *ta.Series, length int) *ta.Series {
|
||||
res := source.To("_zlema", length)
|
||||
if res.Cached() {
|
||||
return res
|
||||
}
|
||||
if source.Len() < length {
|
||||
return res.Append(math.NaN())
|
||||
}
|
||||
ema1 := ta.EMA(source, length)
|
||||
ema2 := ta.EMA(ema1, length)
|
||||
c1 := ema1.Get(0) - ema2.Get(0)
|
||||
zlema := ema1.Get(0) + c1
|
||||
return res.Append(zlema)
|
||||
}
|
||||
|
||||
// ZlemaState 存储每个品种的策略状态
|
||||
type ZlemaState struct {
|
||||
goLong bool
|
||||
sectionLongs int
|
||||
sectionShorts int
|
||||
sectionLongConditions int
|
||||
sectionShortConditions int
|
||||
totalLongs float64
|
||||
totalShorts float64
|
||||
averageLongs float64
|
||||
averageShorts float64
|
||||
lastHigh float64
|
||||
lastLow float64
|
||||
inLong bool
|
||||
inShort bool
|
||||
}
|
||||
|
||||
func J2984(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastPeriod := pol.DefInt("fastPeriod", 10, core.PNorm(3, 20))
|
||||
maRate := pol.Def("maRate", 2.4, core.PNorm(1.5, 5))
|
||||
takeProfitRate := pol.Def("takeProfitRate", 3.5, core.PNorm(1, 10))
|
||||
trailingStopRate := pol.Def("trailingStopRate", 3.5, core.PNorm(1, 8))
|
||||
trailingActivationRate := pol.Def("trailingActivationRate", 16, core.PNorm(5, 25))
|
||||
stopLossPercent := pol.Def("stopLossPercent", 7.5, core.PNorm(1, 15))
|
||||
|
||||
// 金字塔加仓参数
|
||||
pyramidingLess := pol.DefInt("pyramidingLess", 1, core.PNorm(0, 5))
|
||||
pyramidingEqual := pol.DefInt("pyramidingEqual", 0, core.PNorm(0, 5))
|
||||
pyramidingGreater := pol.DefInt("pyramidingGreater", 1000000, core.PNorm(100, 1000000))
|
||||
|
||||
// 交易方向控制参数
|
||||
longsOnly := pol.Def("longsOnly", 0, core.PUniform(0, 1)) > 0.5
|
||||
shortsOnly := pol.Def("shortsOnly", 0, core.PUniform(0, 1)) > 0.5
|
||||
flipOpens := pol.Def("flipOpens", 0, core.PUniform(0, 1)) > 0.5
|
||||
|
||||
// 计算慢线周期
|
||||
slowPeriod := int(float64(fastPeriod) * maRate)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowPeriod * 2, // 确保有足够的数据计算指标
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多开1单多仓
|
||||
EachMaxShort: 1, // 最多开1单空仓
|
||||
DrawDownExit: false, // 不使用回撤止盈
|
||||
RunTimeFrames: []string{"8h"},
|
||||
StopLoss: 0.075,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &ZlemaState{
|
||||
goLong: true,
|
||||
sectionLongs: 0,
|
||||
sectionShorts: 0,
|
||||
sectionLongConditions: 0,
|
||||
sectionShortConditions: 0,
|
||||
totalLongs: 0,
|
||||
totalShorts: 0,
|
||||
averageLongs: 0,
|
||||
averageShorts: 0,
|
||||
lastHigh: 0,
|
||||
lastLow: 0,
|
||||
inLong: false,
|
||||
inShort: false,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*ZlemaState)
|
||||
|
||||
// 计算Zero-Lag EMA
|
||||
zlemaFast := ZeroLagEMA(e.Close, fastPeriod)
|
||||
zlemaSlow := ZeroLagEMA(e.Close, slowPeriod)
|
||||
|
||||
// 获取当前值
|
||||
fastVal := zlemaFast.Get(0)
|
||||
slowVal := zlemaSlow.Get(0)
|
||||
fastVal1 := zlemaFast.Get(1)
|
||||
slowVal1 := zlemaSlow.Get(1)
|
||||
closePrice := e.Close.Get(0)
|
||||
highPrice := e.High.Get(0)
|
||||
lowPrice := e.Low.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(fastVal) || math.IsNaN(slowVal) || math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成基础交易信号
|
||||
longSignal := fastVal > slowVal && fastVal1 <= slowVal1 // 快线上穿慢线
|
||||
shortSignal := fastVal < slowVal && fastVal1 >= slowVal1 // 快线下穿慢线
|
||||
|
||||
// 应用交易方向翻转功能
|
||||
if flipOpens {
|
||||
longSignal, shortSignal = shortSignal, longSignal
|
||||
}
|
||||
|
||||
// 应用仅做多/仅做空选项
|
||||
if longsOnly {
|
||||
shortSignal = false
|
||||
}
|
||||
if shortsOnly {
|
||||
longSignal = false
|
||||
}
|
||||
|
||||
// 更新连续信号计数
|
||||
if longSignal {
|
||||
state.sectionLongs++
|
||||
state.sectionShorts = 0
|
||||
} else if shortSignal {
|
||||
state.sectionShorts++
|
||||
state.sectionLongs = 0
|
||||
}
|
||||
|
||||
// 应用金字塔式加仓条件
|
||||
longCondition := longSignal && (state.sectionLongs <= pyramidingLess || state.sectionLongs >= pyramidingGreater || state.sectionLongs == pyramidingEqual)
|
||||
shortCondition := shortSignal && (state.sectionShorts <= pyramidingLess || state.sectionShorts >= pyramidingGreater || state.sectionShorts == pyramidingEqual)
|
||||
|
||||
// 更新开仓价格和数量
|
||||
if longCondition {
|
||||
state.totalLongs += closePrice
|
||||
state.sectionLongConditions++
|
||||
state.averageLongs = state.totalLongs / float64(state.sectionLongConditions)
|
||||
state.inLong = true
|
||||
state.inShort = false
|
||||
} else if shortCondition {
|
||||
state.totalShorts += closePrice
|
||||
state.sectionShortConditions++
|
||||
state.averageShorts = state.totalShorts / float64(state.sectionShortConditions)
|
||||
state.inShort = true
|
||||
state.inLong = false
|
||||
}
|
||||
|
||||
// 更新最高价/最低价追踪
|
||||
if state.inLong {
|
||||
if state.lastHigh == 0 || highPrice > state.lastHigh {
|
||||
state.lastHigh = highPrice
|
||||
}
|
||||
} else if state.inShort {
|
||||
if state.lastLow == 0 || lowPrice < state.lastLow {
|
||||
state.lastLow = lowPrice
|
||||
}
|
||||
}
|
||||
|
||||
// 执行交易
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
// 设置止损止盈 - 基于平均开仓价计算
|
||||
stopLossVal := state.averageLongs * (1 - stopLossPercent/100)
|
||||
takeProfitVal := state.averageLongs * (1 + takeProfitRate/100)
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
Short: false,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLossVal: stopLossVal,
|
||||
TakeProfitVal: takeProfitVal,
|
||||
StopLossTag: "stop_loss",
|
||||
TakeProfitTag: "take_profit",
|
||||
})
|
||||
} else if shortCondition && len(s.ShortOrders) == 0 {
|
||||
// 设置止损止盈 - 基于平均开仓价计算
|
||||
stopLossVal := state.averageShorts * (1 + stopLossPercent/100)
|
||||
takeProfitVal := state.averageShorts * (1 - takeProfitRate/100)
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLossVal: stopLossVal,
|
||||
TakeProfitVal: takeProfitVal,
|
||||
StopLossTag: "stop_loss",
|
||||
TakeProfitTag: "take_profit",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查平仓条件
|
||||
if state.inLong && shortSignal {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "trend_reverse",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
} else if state.inShort && longSignal {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "trend_reverse",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*ZlemaState)
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
if od.Short {
|
||||
// 做空订单检查
|
||||
if state.inShort {
|
||||
// 检查追踪止损条件 - 包含激活条件判断
|
||||
if state.lastLow > 0 &&
|
||||
state.lastLow <= state.averageShorts*(1-trailingActivationRate/100) && // 激活条件
|
||||
closePrice >= state.lastLow*(1+trailingStopRate/100) { // 追踪止损条件
|
||||
return &strat.ExitReq{Tag: "trailing_stop"}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 做多订单检查
|
||||
if state.inLong {
|
||||
// 检查追踪止损条件 - 包含激活条件判断
|
||||
if state.lastHigh > 0 &&
|
||||
state.lastHigh >= state.averageLongs*(1+trailingActivationRate/100) && // 激活条件
|
||||
closePrice <= state.lastHigh*(1-trailingStopRate/100) { // 追踪止损条件
|
||||
return &strat.ExitReq{Tag: "trailing_stop"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
state, _ := s.More.(*ZlemaState)
|
||||
|
||||
// 订单状态变化时更新状态
|
||||
if chgType == strat.OdChgExitFill {
|
||||
if od.Short {
|
||||
// 做空订单已平仓
|
||||
state.inShort = false
|
||||
state.averageShorts = 0
|
||||
state.totalShorts = 0
|
||||
state.sectionShorts = 0
|
||||
state.sectionShortConditions = 0
|
||||
state.lastLow = 0
|
||||
} else {
|
||||
// 做多订单已平仓
|
||||
state.inLong = false
|
||||
state.averageLongs = 0
|
||||
state.totalLongs = 0
|
||||
state.sectionLongs = 0
|
||||
state.sectionLongConditions = 0
|
||||
state.lastHigh = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
154
llmjob/3087.go
Normal file
154
llmjob/3087.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
/*
|
||||
基于初始的3087进行了简化,只保留盈利较好的14-26
|
||||
*/
|
||||
|
||||
// DCAStrategyState 存储每个品种的策略状态
|
||||
type DCAStrategyState struct {
|
||||
currentSO int // 当前安全订单计数器
|
||||
firstPrice float64 // 初始订单价格
|
||||
totalQtys float64 // 总交易数量
|
||||
totalCost float64 // 累计花费
|
||||
realTotQtys float64 // 总交易数量
|
||||
realTotCost float64 // 累计花费
|
||||
}
|
||||
|
||||
// calculateSafetyOrderThreshold 计算安全订单触发价格阈值
|
||||
func calculateSafetyOrderThreshold(initPrice, priceDeviation, stepScale float64, currentSO int) float64 {
|
||||
curSO := float64(currentSO)
|
||||
if stepScale == 1.0 {
|
||||
return initPrice - initPrice*priceDeviation*stepScale*curSO
|
||||
}
|
||||
rate := priceDeviation*math.Pow(stepScale, curSO) - priceDeviation
|
||||
return initPrice - initPrice*(rate/(stepScale-1))
|
||||
}
|
||||
|
||||
// DynamicDCA 动态DCA策略
|
||||
func J3087(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
priceDeviation := pol.Def("priceDeviation", 0.82, core.PNorm(0.5, 3.0))
|
||||
volumeScale := pol.Def("volumeScale", 1.16, core.PNorm(1.05, 1.5))
|
||||
stepScale := pol.Def("stepScale", 1.06, core.PNorm(1.05, 1.3))
|
||||
takeProfit := pol.Def("takeProfit", 1.15, core.PNorm(0.5, 2.5))
|
||||
baseOrder := pol.Def("baseOrder", 10.0, core.PNorm(5, 20))
|
||||
startLvl := pol.DefInt("startLvl", 14.0, core.PNorm(1, 20))
|
||||
endLvl := pol.DefInt("endLvl", 26.0, core.PNorm(3, 30))
|
||||
maxSafeOrders := pol.DefInt("maxSafeOrders", 30, core.PNorm(5, 50))
|
||||
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
|
||||
var rates []float64
|
||||
// 14/15/16/17/18/19/20/21/22/23/24/25/26
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 1,
|
||||
HedgeOff: true, // 单向持仓
|
||||
RunTimeFrames: []string{"15m"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &DCAStrategyState{}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*DCAStrategyState)
|
||||
|
||||
// 计算OHLC4加权均价
|
||||
price := (e.High.Get(0) + e.Low.Get(0) + e.Close.Get(0) + e.Open.Get(0)) / 4
|
||||
closeP := e.Close.Get(0)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
openRate := 0.004 * closeP / atrBase
|
||||
if openRate < 1 {
|
||||
openRate = math.Sqrt(openRate)
|
||||
}
|
||||
openRate = max(0.7, min(1.5, openRate))
|
||||
|
||||
// 计算止盈水平
|
||||
hasOrders := state.currentSO > 0
|
||||
var avgPrice, realAvgPrice float64
|
||||
if hasOrders && state.totalQtys > 0 {
|
||||
avgPrice = state.totalCost / state.totalQtys
|
||||
realAvgPrice = state.realTotCost / state.realTotQtys
|
||||
}
|
||||
takeProfitLevel := avgPrice * (1 + takeProfit/100)
|
||||
|
||||
// 初始建仓逻辑
|
||||
if state.currentSO == 0 && price > 0 {
|
||||
// 计算买入数量
|
||||
qty := baseOrder / price
|
||||
// 首次买入表现不佳,禁止实际买入
|
||||
state.totalQtys += qty
|
||||
state.totalCost += qty * closeP
|
||||
state.firstPrice = price
|
||||
state.currentSO = 1
|
||||
}
|
||||
|
||||
// 计算安全订单触发价格阈值
|
||||
priceRate := priceDeviation / 100
|
||||
threshold := calculateSafetyOrderThreshold(state.firstPrice, priceRate, stepScale, state.currentSO)
|
||||
|
||||
// 动态补仓逻辑
|
||||
if hasOrders && price <= threshold && state.currentSO <= maxSafeOrders {
|
||||
// 计算补仓数量
|
||||
orderQty := baseOrder * openRate * math.Pow(volumeScale, float64(state.currentSO-1)) / price
|
||||
|
||||
// endLvl用于实际下单。maxSafeOrders用于止盈价格
|
||||
if state.currentSO >= startLvl && state.currentSO <= endLvl {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: fmt.Sprintf("%v", state.currentSO),
|
||||
Amount: orderQty,
|
||||
Log: true,
|
||||
})
|
||||
state.realTotQtys += orderQty
|
||||
state.realTotCost += orderQty * closeP
|
||||
}
|
||||
|
||||
state.totalQtys += orderQty
|
||||
state.totalCost += orderQty * closeP
|
||||
state.currentSO += 1
|
||||
}
|
||||
|
||||
// 止盈平仓逻辑
|
||||
if price >= takeProfitLevel && hasOrders {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TakeProfit",
|
||||
})
|
||||
if realAvgPrice > 0 {
|
||||
rates = append(rates, takeProfitLevel/realAvgPrice)
|
||||
}
|
||||
|
||||
// 重置状态变量
|
||||
state.currentSO = 0
|
||||
state.totalQtys = 0.0
|
||||
state.totalCost = 0
|
||||
state.realTotQtys = 0
|
||||
state.realTotCost = 0
|
||||
state.firstPrice = 0
|
||||
}
|
||||
},
|
||||
OnShutDown: func(s *strat.StratJob) {
|
||||
if len(rates) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Float64s(rates)
|
||||
step := len(rates) / 7
|
||||
for i := 0; i < len(rates); i += step {
|
||||
block := rates[i:min(len(rates), i+step)]
|
||||
avg := floats.Sum(block) / float64(len(block))
|
||||
fmt.Printf("rate: %v: %v\n", i, avg)
|
||||
}
|
||||
rates = nil
|
||||
},
|
||||
}
|
||||
}
|
||||
88
llmjob/3121.go
Normal file
88
llmjob/3121.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3121(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
bbLength := pol.DefInt("bbLength", 20, core.PNorm(10, 50))
|
||||
bbStdDev := pol.Def("bbStdDev", 1.8, core.PNorm(1.0, 3.0))
|
||||
cmoLength := pol.DefInt("cmoLength", 10, core.PNorm(5, 30))
|
||||
cmoOverbought := pol.Def("cmoOverbought", 45.0, core.PNorm(30.0, 70.0))
|
||||
cmoOversold := pol.Def("cmoOversold", -50.0, core.PNorm(-70.0, -30.0))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: bbLength, // 预热期设置为布林带周期长度
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多一个多头订单
|
||||
EachMaxShort: 1, // 最多一个空头订单
|
||||
RunTimeFrames: []string{"5m"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算布林带
|
||||
bbUpper, bbBasis, bbLower := ta.BBANDS(e.Close, bbLength, bbStdDev, bbStdDev)
|
||||
|
||||
// 计算钱德动量振荡器
|
||||
cmo := ta.CMO(e.Close, cmoLength)
|
||||
|
||||
// 获取当前值
|
||||
closePrice := e.Close.Get(0)
|
||||
cmoValue := cmo.Get(0)
|
||||
bbUpperValue := bbUpper.Get(0)
|
||||
bbLowerValue := bbLower.Get(0)
|
||||
bbBasisValue := bbBasis.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) || math.IsNaN(cmoValue) ||
|
||||
math.IsNaN(bbUpperValue) || math.IsNaN(bbLowerValue) || math.IsNaN(bbBasisValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// 做多条件:价格下穿布林带下轨且CMO低于超卖阈值
|
||||
longCondition := e.Close.Cross(bbLower) == -1 && cmoValue < cmoOversold
|
||||
|
||||
// 做空条件:价格上穿布林带上轨且CMO高于超买阈值
|
||||
shortCondition := e.Close.Cross(bbUpper) == 1 && cmoValue > cmoOverbought
|
||||
|
||||
// 平多条件:价格上穿布林带中轨或CMO高于超买阈值
|
||||
exitLong := e.Close.Cross(bbBasis) == 1 || cmoValue > cmoOverbought
|
||||
|
||||
// 平空条件:价格下穿布林带中轨或CMO低于超卖阈值
|
||||
exitShort := e.Close.Cross(bbBasis) == -1 || cmoValue < cmoOversold
|
||||
|
||||
// 执行交易操作
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
})
|
||||
}
|
||||
|
||||
if shortCondition && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
|
||||
if exitLong && len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "exit_long",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
if exitShort && len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "exit_short",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
55
llmjob/3127.go
Normal file
55
llmjob/3127.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// 修复后的完整策略代码
|
||||
// 请勿修改package包名和任意函数名
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3127(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
buyThreshold := pol.Def("buyThreshold", 0.025, core.PNorm(0.005, 0.05))
|
||||
sellThreshold := pol.Def("sellThreshold", 0.025, core.PNorm(0.005, 0.05))
|
||||
lookbackPeriod := pol.DefInt("lookbackPeriod", 16, core.PNorm(5, 20))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: lookbackPeriod,
|
||||
HedgeOff: true,
|
||||
EachMaxLong: 1,
|
||||
RunTimeFrames: []string{"15m"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
if math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 修复:使用收盘价计算历史极值
|
||||
highest := ta.Highest(e.Close, lookbackPeriod).Get(0)
|
||||
lowest := ta.Lowest(e.Close, lookbackPeriod).Get(0)
|
||||
|
||||
buyTrigger := highest * (1 - buyThreshold)
|
||||
sellTrigger := lowest * (1 + sellThreshold)
|
||||
|
||||
if closePrice < buyTrigger && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Achat",
|
||||
Short: false,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
}
|
||||
|
||||
if closePrice > sellTrigger && len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Vente",
|
||||
Dirt: core.OdDirtLong,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
127
llmjob/3137.go
Normal file
127
llmjob/3137.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
type DDLState struct {
|
||||
lastbull float64 // 最后一个涨停价(连续上涨时的最高点)
|
||||
lastbear float64 // 最后一个跌停价(连续下跌时的最低点)
|
||||
lot float64 // 计算后的开仓数量
|
||||
}
|
||||
|
||||
func J3137(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
signalshort := pol.Def("signalshort", 5.1, core.PNorm(1.0, 10.0))
|
||||
signallong := pol.Def("signallong", -5.1, core.PNorm(-10.0, -1.0))
|
||||
capital := pol.Def("capital", 145.0, core.PNorm(10.0, 200.0))
|
||||
needlong := pol.Def("needlong", 1.0, core.PNorm(0.0, 1.0))
|
||||
needshort := pol.Def("needshort", 1.0, core.PNorm(0.0, 1.0))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 2, // 至少需要2个K线数据才能计算bull和bear
|
||||
HedgeOff: false, // 允许双向持仓
|
||||
RunTimeFrames: []string{"15m"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的状态
|
||||
s.More = &DDLState{
|
||||
lastbull: math.NaN(),
|
||||
lastbear: math.NaN(),
|
||||
lot: 0.0,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*DDLState)
|
||||
|
||||
// 获取当前和前一个K线的价格
|
||||
closePrice := e.Close.Get(0)
|
||||
prevClose := e.Close.Get(1)
|
||||
openPrice := e.Open.Get(0)
|
||||
|
||||
// 计算capital比例,避免重复计算
|
||||
capitalRate := capital / 100
|
||||
|
||||
// 如果数据不足,返回
|
||||
if math.IsNaN(closePrice) || math.IsNaN(prevClose) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算价格波动方向
|
||||
bull := closePrice > prevClose
|
||||
bear := closePrice < prevClose
|
||||
|
||||
// 更新最后一个涨停价和跌停价
|
||||
if bull {
|
||||
state.lastbull = closePrice
|
||||
}
|
||||
if bear {
|
||||
state.lastbear = closePrice
|
||||
}
|
||||
|
||||
// 如果还没有初始化lastbull和lastbear,返回
|
||||
if math.IsNaN(state.lastbull) || math.IsNaN(state.lastbear) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算价格变化比例
|
||||
ddl := ((closePrice / state.lastbull) - 1) * 100
|
||||
dds := ((closePrice / state.lastbear) - 1) * 100
|
||||
|
||||
// 生成交易信号
|
||||
up := ddl < signallong
|
||||
dn := dds > signalshort
|
||||
|
||||
// 计算开仓数量(如果没有持仓)
|
||||
if s.OrderNum == 0 {
|
||||
// 修复:使用策略方法获取权益计算仓位
|
||||
stakeAmount := s.Strat.GetStakeAmount(s)
|
||||
state.lot = stakeAmount / closePrice * capitalRate
|
||||
}
|
||||
|
||||
// truetime条件,与原始策略保持一致
|
||||
truetime := true
|
||||
|
||||
// 执行交易
|
||||
if up && needlong > 0.5 && truetime {
|
||||
// 修复:使用计算好的lot值作为开仓数量
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Short: false,
|
||||
Amount: state.lot,
|
||||
})
|
||||
}
|
||||
|
||||
if dn && needshort > 0.5 && truetime {
|
||||
// 修复:使用计算好的lot值作为开仓数量
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
Amount: state.lot,
|
||||
})
|
||||
}
|
||||
|
||||
// 平仓条件:开多仓后价格上涨就平多仓,开空仓后价格下跌就平空仓
|
||||
if len(s.LongOrders) > 0 && closePrice > openPrice {
|
||||
// 修复:使用CloseOrders进行平仓
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "CloseLong",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
if len(s.ShortOrders) > 0 && closePrice < openPrice {
|
||||
// 修复:使用CloseOrders进行平仓
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "CloseShort",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
64
llmjob/32.go
Normal file
64
llmjob/32.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J32(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
smaLength := pol.DefInt("smaLength", 30, core.PNorm(5, 30))
|
||||
macdFastLength := pol.DefInt("macdFastLength", 25, core.PNorm(5, 20))
|
||||
macdSlowLength := pol.DefInt("macdSlowLength", 38, core.PNorm(15, 40))
|
||||
macdSignalSmoothing := pol.DefInt("macdSignalSmoothing", 3, core.PNorm(3, 15))
|
||||
warmupNum := smaLength
|
||||
if macdSlowLength > warmupNum {
|
||||
warmupNum = macdSlowLength
|
||||
}
|
||||
if macdFastLength+macdSignalSmoothing > warmupNum {
|
||||
warmupNum = macdFastLength + macdSignalSmoothing
|
||||
}
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true,
|
||||
EachMaxLong: 1,
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
sma10 := ta.SMA(e.Close, smaLength)
|
||||
macdLine, signalLine := ta.MACD(e.Close, macdFastLength, macdSlowLength, macdSignalSmoothing)
|
||||
closePrice := e.Close.Get(0)
|
||||
sma10Value := sma10.Get(0)
|
||||
macdValue := macdLine.Get(0)
|
||||
signalValue := signalLine.Get(0)
|
||||
if math.IsNaN(closePrice) || math.IsNaN(sma10Value) || math.IsNaN(macdValue) || math.IsNaN(signalValue) {
|
||||
return
|
||||
}
|
||||
priceCrossSMA := e.Close.Cross(sma10)
|
||||
macdCrossSignal := macdLine.Cross(signalLine)
|
||||
longCondition := priceCrossSMA == 1 && macdCrossSignal == 1
|
||||
shortCondition := priceCrossSMA == -1 && macdCrossSignal == -1
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "CloseShort",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
if shortCondition && len(s.LongOrders) > 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
})
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "CloseLong",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
173
llmjob/3464.go
Normal file
173
llmjob/3464.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
type TrendlineData struct {
|
||||
upper float64
|
||||
lower float64
|
||||
slopePh float64
|
||||
slopePl float64
|
||||
upos int
|
||||
dnos int
|
||||
prevUpos int
|
||||
prevDnos int
|
||||
}
|
||||
|
||||
func J3464(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
length := pol.DefInt("length", 13, core.PNorm(5, 30))
|
||||
mult := pol.Def("mult", 1.05, core.PNorm(0.5, 2.0))
|
||||
calcMethod := pol.Def("calcMethod", 0, core.PUniform(0, 2)) // 0: Atr, 1: Stdev, 2: Linreg
|
||||
_ = pol.Def("backpaint", 1.0, core.PUniform(0, 1)) == 1.0 // 使用下划线忽略未使用的变量
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: length * 2,
|
||||
HedgeOff: true, // 单向持仓
|
||||
RunTimeFrames: []string{"4h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &TrendlineData{
|
||||
upper: 0,
|
||||
lower: 0,
|
||||
slopePh: 0,
|
||||
slopePl: 0,
|
||||
upos: 0,
|
||||
dnos: 0,
|
||||
prevUpos: 0,
|
||||
prevDnos: 0,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
data, _ := s.More.(*TrendlineData)
|
||||
|
||||
// 获取当前价格
|
||||
closePrice := e.Close.Get(0)
|
||||
if math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检测支点 - 实现与PineScript的pivothigh和pivotlow相似的功能
|
||||
// 需要检查当前K线是否是过去length周期内的最高点,并且左右两侧都有length个K线
|
||||
ph := false
|
||||
pl := false
|
||||
|
||||
// 确保有足够的历史数据
|
||||
if e.Close.Len() > length*2 {
|
||||
// 检查当前K线是否是支点高
|
||||
currentHigh := e.High.Get(length)
|
||||
isPivotHigh := true
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
if e.High.Get(i) >= currentHigh || e.High.Get(length*2-i) >= currentHigh {
|
||||
isPivotHigh = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前K线是否是支点低
|
||||
currentLow := e.Low.Get(length)
|
||||
isPivotLow := true
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
if e.Low.Get(i) <= currentLow || e.Low.Get(length*2-i) <= currentLow {
|
||||
isPivotLow = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ph = isPivotHigh && !math.IsNaN(currentHigh)
|
||||
pl = isPivotLow && !math.IsNaN(currentLow)
|
||||
}
|
||||
|
||||
// 计算斜率
|
||||
var slope float64
|
||||
method := int(calcMethod)
|
||||
switch method {
|
||||
case 0: // Atr
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, length)
|
||||
slope = atr.Get(0) / float64(length) * mult
|
||||
case 1: // Stdev
|
||||
stdDev := ta.StdDev(e.Close, length)
|
||||
slope = stdDev.Get(0) / float64(length) * mult
|
||||
case 2: // Linreg
|
||||
// 简化版的线性回归斜率计算
|
||||
n := float64(length)
|
||||
sumX := n * (n - 1) / 2
|
||||
sumY := ta.SMA(e.Close, length).Get(0) * n
|
||||
sumXY := 0.0
|
||||
sumX2 := 0.0
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
x := float64(i)
|
||||
y := e.Close.Get(i)
|
||||
if !math.IsNaN(y) {
|
||||
sumXY += x * y
|
||||
sumX2 += x * x
|
||||
}
|
||||
}
|
||||
|
||||
// 计算斜率
|
||||
denominator := n*sumX2 - sumX*sumX
|
||||
if denominator != 0 {
|
||||
slope = (n*sumXY - sumX*sumY) / denominator * mult
|
||||
} else {
|
||||
slope = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 更新趋势线斜率和值
|
||||
if ph {
|
||||
data.slopePh = slope
|
||||
data.upper = e.High.Get(length)
|
||||
} else {
|
||||
data.upper = data.upper - data.slopePh
|
||||
}
|
||||
|
||||
if pl {
|
||||
data.slopePl = slope
|
||||
data.lower = e.Low.Get(length)
|
||||
} else {
|
||||
data.lower = data.lower + data.slopePl
|
||||
}
|
||||
|
||||
// 保存前一个状态
|
||||
data.prevUpos = data.upos
|
||||
data.prevDnos = data.dnos
|
||||
|
||||
// 检测突破状态
|
||||
if ph {
|
||||
data.upos = 0
|
||||
} else if closePrice > data.upper-data.slopePh*float64(length) {
|
||||
data.upos = 1
|
||||
}
|
||||
|
||||
if pl {
|
||||
data.dnos = 0
|
||||
} else if closePrice < data.lower+data.slopePl*float64(length) {
|
||||
data.dnos = 1
|
||||
}
|
||||
|
||||
// 检测突破并生成交易信号
|
||||
upBreakout := data.upos > data.prevUpos
|
||||
dnBreakout := data.dnos > data.prevDnos
|
||||
|
||||
if upBreakout && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Up Breakout",
|
||||
})
|
||||
}
|
||||
|
||||
if dnBreakout && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Down Breakout",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
66
llmjob/3614.go
Normal file
66
llmjob/3614.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3614(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
length := pol.DefInt("length", 66, core.PNorm(20, 100))
|
||||
hmaRate := pol.Def("hmaRate", 8.72, core.PNorm(3, 15))
|
||||
channelFactor := pol.Def("channelFactor", 0.98, core.PNorm(0.95, 0.995))
|
||||
|
||||
// 计算HMA实际周期
|
||||
hmaPeriod := int(float64(length) * hmaRate)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: hmaPeriod, // 预热数量取最大的指标周期
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"1h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算线性回归线
|
||||
linreg := ta.LinReg(e.Close, length)
|
||||
linregValue := linreg.Get(0)
|
||||
|
||||
// 计算通道上限线
|
||||
band2 := linregValue * channelFactor
|
||||
|
||||
// 计算Hull移动平均线
|
||||
hma := ta.HMA(e.Close, hmaPeriod)
|
||||
hmaValue := hma.Get(0)
|
||||
|
||||
// 获取当前收盘价
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) || math.IsNaN(linregValue) || math.IsNaN(band2) || math.IsNaN(hmaValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成交易信号
|
||||
longCondition := closePrice < band2 && hmaValue < closePrice
|
||||
shortCondition := closePrice > linregValue
|
||||
|
||||
// 执行交易操作
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
// 满足做多条件且没有多头持仓时开仓
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "BUY",
|
||||
})
|
||||
} else if shortCondition && len(s.LongOrders) > 0 {
|
||||
// 满足平仓条件且持有多头仓位时平仓
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "SELL",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
218
llmjob/3644.go
Normal file
218
llmjob/3644.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
// SupertrendState 存储每个品种的策略状态
|
||||
type SupertrendState struct {
|
||||
trend int // 趋势状态,1表示上升趋势,-1表示下降趋势
|
||||
prevTrend int // 前一期的趋势状态
|
||||
up float64 // 上轨
|
||||
dn float64 // 下轨
|
||||
tp float64 // 止盈价格
|
||||
}
|
||||
|
||||
func J3644(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
atrPeriod := pol.DefInt("atrPeriod", 5, core.PNorm(2, 20))
|
||||
atrMultiplier := pol.Def("atrMultiplier", 4.7, core.PNorm(2.0, 8.0))
|
||||
profitRatio := pol.Def("profitRatio", 0.382, core.PNorm(0.2, 1.0))
|
||||
useSmaATR := pol.DefInt("useSmaATR", 1, core.PUniform(0, 1)) // 1=true, 0=false
|
||||
srcType := pol.DefInt("srcType", 0, core.PUniform(0, 7)) // 0=hlc3, 1=high, 2=low, 3=open, 4=hl2, 5=close, 6=hlcc4, 7=ohlc4
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: atrPeriod * 3, // 预热期设置为ATR周期的3倍,确保有足够数据
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多开1个多单
|
||||
EachMaxShort: 1, // 最多开1个空单
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &SupertrendState{
|
||||
trend: 1, // 初始趋势状态为上升趋势
|
||||
prevTrend: 1, // 前一期趋势状态也为上升趋势
|
||||
tp: 0, // 初始止盈价格
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*SupertrendState)
|
||||
|
||||
// 获取数据源
|
||||
var src *ta.Series
|
||||
switch srcType {
|
||||
case 0:
|
||||
src = ta.HLC3(e.High, e.Low, e.Close)
|
||||
case 1:
|
||||
src = e.High
|
||||
case 2:
|
||||
src = e.Low
|
||||
case 3:
|
||||
src = e.Open
|
||||
case 4:
|
||||
src = ta.HL2(e.High, e.Low)
|
||||
case 5:
|
||||
src = e.Close
|
||||
case 6:
|
||||
// HLCC4: (high + low + close + close) / 4
|
||||
src = e.High.Add(e.Low).Add(e.Close).Add(e.Close).Div(4)
|
||||
case 7:
|
||||
// OHLC4: (open + high + low + close) / 4
|
||||
src = e.Open.Add(e.High).Add(e.Low).Add(e.Close).Div(4)
|
||||
default:
|
||||
src = ta.HLC3(e.High, e.Low, e.Close)
|
||||
}
|
||||
|
||||
// 计算ATR
|
||||
var atr *ta.Series
|
||||
if useSmaATR == 1 {
|
||||
// 使用标准ATR计算
|
||||
atr = ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
} else {
|
||||
// 使用SMA(TR)计算ATR
|
||||
trueRange := ta.TR(e.High, e.Low, e.Close)
|
||||
atr = ta.SMA(trueRange, atrPeriod)
|
||||
}
|
||||
|
||||
// 计算超级趋势通道
|
||||
closePrice := e.Close.Get(0)
|
||||
prevClose := e.Close.Get(1)
|
||||
atrVal := atr.Get(0)
|
||||
srcVal := src.Get(0)
|
||||
|
||||
// 计算上下轨
|
||||
up := srcVal - (atrMultiplier * atrVal)
|
||||
dn := srcVal + (atrMultiplier * atrVal)
|
||||
|
||||
// 获取前一期上下轨
|
||||
prevUp := state.up
|
||||
prevDn := state.dn
|
||||
|
||||
// 如果是第一期或前一期值无效,使用当前值
|
||||
if math.IsNaN(prevUp) {
|
||||
prevUp = up
|
||||
}
|
||||
if math.IsNaN(prevDn) {
|
||||
prevDn = dn
|
||||
}
|
||||
|
||||
// 趋势跟踪逻辑
|
||||
if !math.IsNaN(prevClose) && prevClose > prevUp {
|
||||
up = math.Max(up, prevUp)
|
||||
}
|
||||
if !math.IsNaN(prevClose) && prevClose < prevDn {
|
||||
dn = math.Min(dn, prevDn)
|
||||
}
|
||||
|
||||
// 更新趋势状态
|
||||
state.prevTrend = state.trend
|
||||
if state.trend == -1 && closePrice > prevDn {
|
||||
state.trend = 1
|
||||
} else if state.trend == 1 && closePrice < prevUp {
|
||||
state.trend = -1
|
||||
}
|
||||
|
||||
// 保存当前上下轨值
|
||||
state.up = up
|
||||
state.dn = dn
|
||||
|
||||
// 生成交易信号
|
||||
buySignal := state.trend == 1 && state.prevTrend == -1
|
||||
sellSignal := state.trend == -1 && state.prevTrend == 1
|
||||
|
||||
// 获取当前订单
|
||||
longOrders := s.LongOrders
|
||||
shortOrders := s.ShortOrders
|
||||
|
||||
// 更新多头订单的止损
|
||||
if len(longOrders) > 0 {
|
||||
for _, od := range longOrders {
|
||||
if od.Status >= ormo.InOutStatusFullEnter {
|
||||
// 保持之前的止盈价
|
||||
tp := state.tp
|
||||
if math.IsNaN(tp) || tp <= 0 {
|
||||
tp = od.InitPrice + (od.InitPrice-up)*profitRatio
|
||||
}
|
||||
// 更新止损价为上轨
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: up,
|
||||
Tag: "super_trend_sl",
|
||||
})
|
||||
// 更新止盈价
|
||||
od.SetTakeProfit(&ormo.ExitTrigger{
|
||||
Price: tp,
|
||||
Tag: "super_trend_tp",
|
||||
})
|
||||
state.tp = tp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新空头订单的止损
|
||||
if len(shortOrders) > 0 {
|
||||
for _, od := range shortOrders {
|
||||
if od.Status >= ormo.InOutStatusFullEnter {
|
||||
// 保持之前的止盈价
|
||||
tp := state.tp
|
||||
if math.IsNaN(tp) || tp <= 0 {
|
||||
tp = od.InitPrice - (dn-od.InitPrice)*profitRatio
|
||||
}
|
||||
// 更新止损价为下轨
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: dn,
|
||||
Tag: "super_trend_sl",
|
||||
})
|
||||
// 更新止盈价
|
||||
od.SetTakeProfit(&ormo.ExitTrigger{
|
||||
Price: tp,
|
||||
Tag: "super_trend_tp",
|
||||
})
|
||||
state.tp = tp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 买入信号
|
||||
if buySignal && len(longOrders) == 0 {
|
||||
// 计算止盈价
|
||||
tp := closePrice + (closePrice-up)*profitRatio
|
||||
state.tp = tp
|
||||
// 开多单,使用限价单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "super_trend_long",
|
||||
Short: false,
|
||||
OrderType: core.OrderTypeLimit,
|
||||
Limit: tp,
|
||||
StopLoss: up,
|
||||
StopLossTag: "super_trend_sl",
|
||||
TakeProfit: tp,
|
||||
TakeProfitTag: "super_trend_tp",
|
||||
})
|
||||
}
|
||||
|
||||
// 卖出信号
|
||||
if sellSignal && len(shortOrders) == 0 {
|
||||
// 计算止盈价
|
||||
tp := closePrice - (dn-closePrice)*profitRatio
|
||||
state.tp = tp
|
||||
// 开空单,使用限价单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "super_trend_short",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeLimit,
|
||||
Limit: tp,
|
||||
StopLoss: dn,
|
||||
StopLossTag: "super_trend_sl",
|
||||
TakeProfit: tp,
|
||||
TakeProfitTag: "super_trend_tp",
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
86
llmjob/3683.go
Normal file
86
llmjob/3683.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
type MacdState struct {
|
||||
lastCrossUp bool
|
||||
lastCrossDown bool
|
||||
}
|
||||
|
||||
func J3683(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
fastPeriod := pol.DefInt("fastPeriod", 16, core.PNorm(5, 20))
|
||||
emaRatio := pol.Def("emaRatio", 1.7, core.PNorm(1.5, 4))
|
||||
signalPeriod := pol.DefInt("signalPeriod", 8, core.PNorm(5, 15))
|
||||
profitRate := pol.Def("profitRate", 0.012, core.PNorm(0.005, 0.03))
|
||||
lossRate := pol.Def("lossRate", 0.009, core.PNorm(0.005, 0.03))
|
||||
|
||||
// 计算慢速EMA周期
|
||||
slowPeriod := int(float64(fastPeriod) * emaRatio)
|
||||
|
||||
// 确保预热K线数量足够计算MACD指标
|
||||
warmupNum := fastPeriod + slowPeriod + signalPeriod
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的状态变量
|
||||
s.More = &MacdState{
|
||||
lastCrossUp: false,
|
||||
lastCrossDown: false,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*MacdState)
|
||||
|
||||
// 计算MACD指标
|
||||
macdLine, signalLine := ta.MACD(e.Close, fastPeriod, slowPeriod, signalPeriod)
|
||||
|
||||
// 检测金叉和死叉
|
||||
crossUp := macdLine.Cross(signalLine)
|
||||
crossDown := signalLine.Cross(macdLine)
|
||||
|
||||
// 更新交叉状态
|
||||
if crossUp == 1 { // 金叉
|
||||
state.lastCrossUp = true
|
||||
state.lastCrossDown = false
|
||||
} else if crossDown == 1 { // 死叉
|
||||
state.lastCrossUp = false
|
||||
state.lastCrossDown = true
|
||||
}
|
||||
|
||||
// 获取当前价格
|
||||
currentPrice := e.Close.Get(0)
|
||||
|
||||
// 连续金叉且当前无多头订单,开多仓
|
||||
if state.lastCrossUp && crossUp == 1 && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "买入开多",
|
||||
TakeProfitVal: currentPrice * (1 + profitRate),
|
||||
StopLossVal: currentPrice * (1 - lossRate),
|
||||
TakeProfitTag: "多单止盈",
|
||||
StopLossTag: "多单止损",
|
||||
})
|
||||
}
|
||||
|
||||
// 连续死叉且当前无空头订单,开空仓
|
||||
if state.lastCrossDown && crossDown == 1 && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "卖出开空",
|
||||
Short: true,
|
||||
TakeProfitVal: currentPrice * (1 - profitRate),
|
||||
StopLossVal: currentPrice * (1 + lossRate),
|
||||
TakeProfitTag: "空单止盈",
|
||||
StopLossTag: "空单止损",
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
72
llmjob/3707.go
Normal file
72
llmjob/3707.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3707(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义策略参数
|
||||
rangeLength := pol.DefInt("rangeLength", 5, core.PNorm(3, 20))
|
||||
useHeikinAshi := pol.Def("useHeikinAshi", 0.0, core.PUniform(0, 1)) // 1.0表示true,0.0表示false
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: rangeLength + 1, // 确保有足够的历史数据计算指标
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"3d"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 获取K线数据
|
||||
var open, close *ta.Series
|
||||
if useHeikinAshi > 0.5 {
|
||||
// 使用Heikin Ashi蜡烛图
|
||||
haOpen, _, _, haClose := ta.HeikinAshi(e)
|
||||
open = haOpen
|
||||
close = haClose
|
||||
} else {
|
||||
// 使用标准K线
|
||||
open = e.Open
|
||||
close = e.Close
|
||||
}
|
||||
|
||||
// 计算成交量范围
|
||||
highest := ta.Highest(e.Volume, rangeLength)
|
||||
_ = ta.Lowest(e.Volume.Mul(-1), rangeLength) // 最低负成交量
|
||||
|
||||
// 获取当前值和前一个值
|
||||
highestCurr := highest.Get(0)
|
||||
highestPrev := highest.Get(1)
|
||||
closeCurr := close.Get(0)
|
||||
openCurr := open.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(highestCurr) || math.IsNaN(highestPrev) || math.IsNaN(closeCurr) || math.IsNaN(openCurr) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成交易信号
|
||||
volumeBreakout := highestCurr > highestPrev
|
||||
isBullish := closeCurr > openCurr
|
||||
isBearish := closeCurr < openCurr
|
||||
|
||||
// 执行交易
|
||||
if volumeBreakout && isBullish {
|
||||
// 成交量突破前高且为阳线,做多
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long Entry",
|
||||
})
|
||||
} else if volumeBreakout && isBearish {
|
||||
// 成交量突破前高且为阴线,做空
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short Entry",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
163
llmjob/3746.go
Normal file
163
llmjob/3746.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
type KijunData struct {
|
||||
initialCapital float64
|
||||
netProfit float64
|
||||
}
|
||||
|
||||
func J3746(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
ksPeriod := pol.DefInt("ksPeriod", 30, core.PNorm(9, 50))
|
||||
atrPeriod := pol.DefInt("atrPeriod", 14, core.PNorm(7, 30))
|
||||
atrMultiplier := pol.Def("atrMultiplier", 1.5, core.PNorm(0.8, 4))
|
||||
targetPoints := pol.Def("targetPoints", 100, core.PNorm(50, 200))
|
||||
riskPercent := pol.Def("riskPercent", 4, core.PNorm(1, 10))
|
||||
equityProtection := pol.Def("equityProtection", 30, core.PNorm(10, 50))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: ksPeriod, // 预热K线数量,确保基均线计算有足够数据
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"3d"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的特定数据
|
||||
s.More = &KijunData{
|
||||
initialCapital: 50000.0, // 初始资金
|
||||
netProfit: 0.0, // 初始净利润
|
||||
}
|
||||
},
|
||||
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
m, _ := s.More.(*KijunData)
|
||||
|
||||
// 计算技术指标
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
atrValue := atr.Get(0)
|
||||
|
||||
// 计算基均线 (Kijun Sen)
|
||||
highest := ta.Highest(e.High, ksPeriod)
|
||||
lowest := ta.Lowest(e.Low, ksPeriod)
|
||||
kijunSen := highest.Add(lowest).Div(2)
|
||||
kijunSenValue := kijunSen.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(atrValue) || math.IsNaN(kijunSenValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成交易信号
|
||||
openPrice := e.Open.Get(0)
|
||||
closePrice := e.Close.Get(0)
|
||||
baseLong := openPrice < kijunSenValue && closePrice > kijunSenValue
|
||||
baseShort := openPrice > kijunSenValue && closePrice < kijunSenValue
|
||||
|
||||
// 计算止损距离
|
||||
stopLossVal := atrValue * atrMultiplier
|
||||
|
||||
// 更新净利润
|
||||
// 计算总浮动盈亏
|
||||
totalFloating := 0.0
|
||||
for _, od := range s.LongOrders {
|
||||
totalFloating += od.Profit
|
||||
}
|
||||
for _, od := range s.ShortOrders {
|
||||
totalFloating += od.Profit
|
||||
}
|
||||
m.netProfit = totalFloating
|
||||
|
||||
// 权益保护检查
|
||||
equityStopout := false
|
||||
if s.OrderNum > 0 {
|
||||
// 计算当前余额(初始资金 + 净利润)
|
||||
totalBalance := m.initialCapital + m.netProfit
|
||||
|
||||
// 检查是否触发权益保护
|
||||
if totalFloating < 0 && math.Abs(totalFloating/totalBalance) > equityProtection/100 {
|
||||
equityStopout = true
|
||||
}
|
||||
}
|
||||
|
||||
// 权益保护触发时平仓所有头寸
|
||||
if equityStopout {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "equity_protection",
|
||||
Dirt: core.OdDirtBoth,
|
||||
Force: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有持仓
|
||||
hasLongPosition := len(s.LongOrders) > 0
|
||||
hasShortPosition := len(s.ShortOrders) > 0
|
||||
|
||||
// 计算头寸大小
|
||||
balance := m.initialCapital + m.netProfit
|
||||
riskAmount := balance * (riskPercent / 100) // 风险金额
|
||||
size := riskAmount / stopLossVal // 合约数量
|
||||
if size < 0.001 { // 最小合约数量检查
|
||||
size = 0.001
|
||||
}
|
||||
|
||||
// 多头交易
|
||||
if baseLong && !hasLongPosition && !hasShortPosition {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "l_en",
|
||||
Amount: size,
|
||||
StopLossVal: stopLossVal,
|
||||
TakeProfitVal: targetPoints,
|
||||
StopLossTag: "stop_loss",
|
||||
TakeProfitTag: "take_profit",
|
||||
})
|
||||
}
|
||||
|
||||
// 空头交易
|
||||
if baseShort && !hasShortPosition && !hasLongPosition {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "s_en",
|
||||
Short: true,
|
||||
Amount: size,
|
||||
StopLossVal: stopLossVal,
|
||||
TakeProfitVal: targetPoints,
|
||||
StopLossTag: "stop_loss",
|
||||
TakeProfitTag: "take_profit",
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
||||
// 计算基均线
|
||||
highest := ta.Highest(s.Env.High, ksPeriod)
|
||||
lowest := ta.Lowest(s.Env.Low, ksPeriod)
|
||||
kijunSen := highest.Add(lowest).Div(2)
|
||||
kijunSenValue := kijunSen.Get(0)
|
||||
|
||||
closePrice := s.Env.Close.Get(0)
|
||||
|
||||
// 多头平仓条件:收盘价低于基均线
|
||||
if !od.Short && closePrice < kijunSenValue {
|
||||
return &strat.ExitReq{
|
||||
Tag: "long_exit",
|
||||
}
|
||||
}
|
||||
|
||||
// 空头平仓条件:收盘价高于基均线
|
||||
if od.Short && closePrice > kijunSenValue {
|
||||
return &strat.ExitReq{
|
||||
Tag: "short_exit",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
270
llmjob/3762.go
Normal file
270
llmjob/3762.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/banbox/banbot/biz"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"github.com/banbox/banexg/utils"
|
||||
ta "github.com/banbox/banta"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
/*
|
||||
超跌反弹策略。30个品种为宜;已增加暴跌抄底机制。
|
||||
开单越多,后续抄底价指数下移
|
||||
做空部分没有利润,不稳定,小亏,禁用
|
||||
*/
|
||||
|
||||
type State3762 struct {
|
||||
NoLongUntil int64
|
||||
BWidth float64
|
||||
MidPrice float64
|
||||
SlRange float64
|
||||
volRate float64
|
||||
}
|
||||
|
||||
func J3762(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义策略参数
|
||||
p := pol.DefInt("p", 20, core.PNorm(5, 50))
|
||||
d := pol.Def("d", 30.0, core.PNorm(5, 50))
|
||||
mult := pol.Def("mult", 1.5, core.PNorm(1.0, 3.0))
|
||||
slRate := pol.Def("slRate", 25, core.PNorm(0, 250)) //default: 25
|
||||
// 某品种入场时,下移其他品种的倍率(相对BWidth)
|
||||
// 0.3开单太多亏损,0.5开单不足亏损
|
||||
downOtherRate := 0.33
|
||||
|
||||
dayMS := int64(utils.TFToSecs("1d") * 1000)
|
||||
longFillNum := float64(0)
|
||||
fillLock := sync.Mutex{}
|
||||
var allJobs []*strat.StratJob
|
||||
|
||||
// 基于成交量计算的区间倍率,成交量太大时,适当调低做多和止损价格
|
||||
var rangeVolRate = float64(1)
|
||||
allowPairs := 1 // 允许挂单的订单数,默认为品种数,随开单减少,防止交易所故障导致下单过多
|
||||
openPairs := 0 // 实际有订单的品种数(挂单+持仓)
|
||||
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
|
||||
|
||||
calcBuyPrice := func(m *State3762) float64 {
|
||||
return m.MidPrice - m.BWidth*(1+math.Pow(longFillNum, 1.5)*downOtherRate)*rangeVolRate
|
||||
}
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: baseAtrLen * 2, // 预热K线数量,确保有足够数据计算指标
|
||||
RunTimeFrames: []string{"15m"},
|
||||
BatchInOut: true,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &State3762{}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
m, _ := s.More.(*State3762)
|
||||
// 计算基础布林带指标
|
||||
sma := ta.SMA(e.Close, p)
|
||||
ma7 := ta.SMA(e.Close, 7).Get(0)
|
||||
ma30 := ta.SMA(e.Close, 30).Get(0)
|
||||
multCur := mult
|
||||
if ma7 < ma30 {
|
||||
// 熊市下移下限
|
||||
multCur *= 1.35
|
||||
}
|
||||
stddev := ta.StdDev(e.Close, p).Get(0) * multCur
|
||||
upperBB := sma.Get(0) + stddev
|
||||
lowerBB := sma.Get(0) - stddev
|
||||
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
|
||||
maVol := ta.EMA(e.Volume, lenAtr)
|
||||
volBase := ta.Lowest(ta.Highest(maVol, lenAtr), baseAtrLen).Get(0)
|
||||
m.volRate = maVol.Get(0) / volBase
|
||||
volH3Rate := ta.Highest(maVol, 3).Get(0) / volBase
|
||||
|
||||
// 计算百分比偏离轨道
|
||||
atrRt := d
|
||||
if ma7 < ma30 {
|
||||
// 熊市下移下限
|
||||
atrRt *= 1.35
|
||||
}
|
||||
upex2 := sma.Get(0) + atrBase*atrRt
|
||||
dnex2 := sma.Get(0) - atrBase*atrRt
|
||||
|
||||
// 计算最终轨道
|
||||
upexPrice := max(upperBB, (upex2+upperBB)/2)
|
||||
dnexPrice := min(lowerBB, (dnex2+lowerBB)/2)
|
||||
m.BWidth = (upexPrice - dnexPrice) / 2
|
||||
m.MidPrice = (upexPrice + dnexPrice) / 2
|
||||
//lowPrice, highPrice := dnexPrice, upexPrice
|
||||
lowPrice := calcBuyPrice(m)
|
||||
|
||||
// 获取当前价格
|
||||
closePrice := e.Close.Get(0)
|
||||
|
||||
// 平仓条件:阳线
|
||||
if len(s.LongOrders) > 0 && s.LongOrders[0].Status >= ormo.InOutStatusPartEnter {
|
||||
doExit := closePrice > e.Open.Get(0)
|
||||
if volH3Rate > 5 {
|
||||
// 放巨量入场的订单,等待阳线后一个K线出场
|
||||
doExit = e.Close.Get(1) > e.Open.Get(1)
|
||||
}
|
||||
if doExit {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "ExitLong",
|
||||
Dirt: core.OdDirtLong,
|
||||
Log: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
if math.IsNaN(lowPrice) || lowPrice <= 0 {
|
||||
return
|
||||
}
|
||||
slRange := min(atrBase*slRate, closePrice*0.25) * min(3, rangeVolRate)
|
||||
m.SlRange = slRange
|
||||
|
||||
// 执行交易逻辑
|
||||
//if !s.IsWarmUp && e.TimeStop >= 1760130900000 {
|
||||
// line := fmt.Sprintf("%s upp: %.6f, low: %.6f upex2: %.6f, dnex2: %.6f dnex: %.6f slRange: %.6f",
|
||||
// e.Symbol, upperBB, lowerBB, upex2, dnex2, lowPrice, slRange)
|
||||
// s.Strat.WriteOutput(line, true)
|
||||
//}
|
||||
if len(s.LongOrders) == 0 {
|
||||
// 使用限价单在下轨价格做多,不依赖当前价格是否突破轨道
|
||||
if e.TimeStop >= m.NoLongUntil && openPairs < allowPairs {
|
||||
fillLock.Lock()
|
||||
openPairs += 1
|
||||
fillLock.Unlock()
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Limit: lowPrice, // 以下轨价格作为限价单
|
||||
StopLossVal: slRange,
|
||||
Log: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, od := range s.LongOrders {
|
||||
if od.Status == ormo.InOutStatusInit {
|
||||
err := od.SetEnterLimit(lowPrice)
|
||||
if err != nil {
|
||||
log.Error("SetEnterLimit fail", zap.Error(err))
|
||||
} else {
|
||||
err = od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: lowPrice - slRange,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("SetStopLoss fail", zap.Error(err))
|
||||
}
|
||||
}
|
||||
} else if od.Status == ormo.InOutStatusFullEnter {
|
||||
barNum := e.BarCount(od.Enter.UpdateAt)
|
||||
if barNum > 2 && od.ProfitRate < 0 {
|
||||
// 持仓2个k线,出现亏损,平仓
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "ExitBad",
|
||||
Dirt: core.OdDirtLong,
|
||||
Log: true,
|
||||
})
|
||||
m.NoLongUntil = e.TimeStop + dayMS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
OnBatchJobs: func(jobs []*strat.StratJob) {
|
||||
fillLock.Lock()
|
||||
longFillNum = 0
|
||||
openPairs = 0
|
||||
volRates := make([]float64, 0, len(jobs))
|
||||
for _, s := range jobs {
|
||||
if len(s.LongOrders) > 0 {
|
||||
openPairs += 1
|
||||
for _, od := range s.LongOrders {
|
||||
if od.Status >= ormo.InOutStatusPartEnter {
|
||||
longFillNum += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
m, _ := s.More.(*State3762)
|
||||
volRates = append(volRates, m.volRate)
|
||||
}
|
||||
fillLock.Unlock()
|
||||
allJobs = jobs
|
||||
sort.Float64s(volRates)
|
||||
rangeVolRate = max(1, math.Pow(volRates[len(volRates)/2]/2, 0.3))
|
||||
allowPairs = int(math.Round(math.Pow(0.8, longFillNum) * float64(len(allJobs))))
|
||||
//jobs[0].Strat.WriteOutput(fmt.Sprintf("%.3f", rangeVolRate), true)
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
if chgType == core.OdChgExitFill {
|
||||
if od.Enter.Amount > 0 {
|
||||
m, _ := s.More.(*State3762)
|
||||
m.NoLongUntil = od.ExitAt + dayMS
|
||||
fillLock.Lock()
|
||||
longFillNum -= 1
|
||||
openPairs -= 1
|
||||
fillLock.Unlock()
|
||||
}
|
||||
} else if chgType == core.OdChgEnterFill {
|
||||
fillLock.Lock()
|
||||
longFillNum += 1
|
||||
openPairs = 0 // 这里应当重新计算,避免并发和回调导致错误
|
||||
for _, job := range allJobs {
|
||||
if len(job.LongOrders) > 0 {
|
||||
openPairs += 1
|
||||
}
|
||||
}
|
||||
fillLock.Unlock()
|
||||
// 降低允许开仓的品种数,避免交易所故障导致下单太多
|
||||
allowPairs = int(math.Round(math.Pow(0.85, longFillNum) * float64(len(allJobs))))
|
||||
needClose := openPairs - allowPairs // 需要关闭的订单数
|
||||
if needClose > 0 {
|
||||
// 排序确保多次回测一致
|
||||
sort.Slice(allJobs, func(i, j int) bool {
|
||||
return allJobs[i].Symbol.Symbol < allJobs[j].Symbol.Symbol
|
||||
})
|
||||
}
|
||||
odMgr := biz.GetOdMgr(s.Account)
|
||||
// 移动其他品种的入场价格,避免同时开仓太多
|
||||
for _, job := range allJobs {
|
||||
if job == s || len(job.LongOrders) == 0 {
|
||||
continue
|
||||
}
|
||||
m, _ := job.More.(*State3762)
|
||||
lowPrice := calcBuyPrice(m)
|
||||
for _, odr := range job.LongOrders {
|
||||
if odr.Status <= ormo.InOutStatusPartEnter {
|
||||
if lowPrice <= 0 || needClose > 0 {
|
||||
needClose -= 1
|
||||
job.CloseOrders(&strat.ExitReq{
|
||||
Tag: "cancel",
|
||||
OrderID: odr.ID,
|
||||
Force: true,
|
||||
})
|
||||
odMgr.ProcessOrders(job)
|
||||
} else {
|
||||
err := odr.SetEnterLimit(lowPrice)
|
||||
if err == nil {
|
||||
odr.EnterTag = fmt.Sprintf("long%d", int(longFillNum))
|
||||
odr.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: max(lowPrice-m.SlRange, lowPrice*0.1),
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
odr.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: max(lowPrice-m.SlRange, odr.Enter.Price*0.1),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
310
llmjob/3782.go
Normal file
310
llmjob/3782.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
需要多品种综合市场信息,品种数不可少于30
|
||||
此策略有综合市场信息并限制开单数量,回测和实盘开单预期不一致,但开单数量基本符合
|
||||
openMaxs中的前两个减少会降低收益
|
||||
|
||||
待改进:
|
||||
zone1信号在22年有几次略大回撤
|
||||
*/
|
||||
|
||||
// ZoneTradeState 用于存储每个交易区间的状态信息
|
||||
type ZoneTradeState struct {
|
||||
LastTimes [3]int64 // 3个买入信号的最后交易时间
|
||||
LastSellTime int64 // 最后卖出时间
|
||||
atrBase float64
|
||||
score float64
|
||||
upFlag float64
|
||||
volRate float64
|
||||
}
|
||||
|
||||
func J3782(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
maPeriod := pol.DefInt("maPeriod", 100, core.PNorm(20, 200))
|
||||
baseRetra := pol.Def("baseRetra", 0.08, core.PNorm(0.05, 0.2))
|
||||
retraRate := pol.Def("retraRate", 1.8, core.PNorm(1.5, 2.5))
|
||||
profitRate := pol.Def("profitRate", 0.03, core.PNorm(0.015, 0.06))
|
||||
tradeDelay := pol.Def("tradeDelay", 40, core.PNorm(10, 100))
|
||||
slRate := pol.Def("slRate", 63, core.PNorm(5, 35))
|
||||
openMaxs := [3]int{2, 8, 13} // 同一个bar最大同时允许开首单的数量
|
||||
holdMaxs := [3]int{25, 35, 50} // 最大同时持仓数
|
||||
buyWeis := [3]float64{1, 1.5, 3} // 不同信号开仓倍率
|
||||
maxJobTrades := [3]int{1, 3, 4} // 单个品种最大开仓数量
|
||||
|
||||
// 计算各回撤比例
|
||||
retracement2 := baseRetra
|
||||
retracement3 := baseRetra * retraRate
|
||||
retracement4 := baseRetra * retraRate * 1.33
|
||||
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
|
||||
rates := make([]float64, 0, 10)
|
||||
|
||||
allOpens := 0
|
||||
openNums := [3]int{0, 0, 0} // 同一个bar开单数量
|
||||
holdNums := [3]int{0, 0, 0} // 总计持仓数量
|
||||
openUnits := 0.0
|
||||
var pairLock sync.Mutex
|
||||
addThres, firstRate := 1.0, 1.0
|
||||
avgAtrBase := 2.5
|
||||
maxAllowUnit := 3.0 // 这里默认为品种数
|
||||
rangeVolRate := 1.0 // 全局成交量倍率
|
||||
|
||||
checkMaxPair := func(level int, openNum int) (int, float64) {
|
||||
if openNum >= maxJobTrades[level] {
|
||||
return -1, 0
|
||||
}
|
||||
costRate := buyWeis[level]
|
||||
pairLock.Lock()
|
||||
if openUnits < maxAllowUnit && openNums[level] < openMaxs[level] && holdNums[level] < holdMaxs[level] {
|
||||
openNums[level] += 1
|
||||
holdNums[level] += 1
|
||||
allOpens += 1
|
||||
openUnits += costRate
|
||||
} else {
|
||||
level = -1
|
||||
}
|
||||
pairLock.Unlock()
|
||||
return level, costRate
|
||||
}
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: maPeriod,
|
||||
BatchInfo: true,
|
||||
BatchInOut: true,
|
||||
RunTimeFrames: []string{"15m"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化状态信息
|
||||
s.More = &ZoneTradeState{
|
||||
LastTimes: [3]int64{},
|
||||
}
|
||||
},
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", baseAtrLen * 2},
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*ZoneTradeState)
|
||||
|
||||
// 计算MA100
|
||||
ma100 := ta.SMA(e.Close, maPeriod)
|
||||
ma100Val := ma100.Get(0)
|
||||
|
||||
// 计算交易价格水平
|
||||
curRate := max(addThres, 1+state.score/10)
|
||||
itemRate := max(0.7, min(math.Sqrt(state.atrBase/avgAtrBase), 1.4))
|
||||
buyPrice2 := ma100Val * (1 - retracement2*curRate*itemRate*firstRate)
|
||||
buyPrice3 := ma100Val * (1 - retracement3*curRate*itemRate)
|
||||
buyPrice4 := ma100Val * (1 - retracement4*curRate*itemRate)
|
||||
sellPrice := ma100Val * (1 + profitRate)
|
||||
gapRange := (buyPrice2 - buyPrice4) / 2
|
||||
|
||||
maVol := ta.EMA(e.Volume, lenAtr)
|
||||
volBase := ta.Lowest(ta.Highest(maVol, lenAtr), baseAtrLen).Get(0)
|
||||
state.volRate = maVol.Get(0) / volBase
|
||||
|
||||
// 获取当前价格
|
||||
lowPrice := e.Low.Get(0)
|
||||
highPrice := e.High.Get(0)
|
||||
|
||||
// 检查交易间隔条件
|
||||
currentBarTime := e.TimeStop
|
||||
|
||||
lastEnt := max(state.LastTimes[0], state.LastTimes[1], state.LastTimes[2])
|
||||
canTrade := e.BarCount(lastEnt) >= 10
|
||||
canTrade2 := canTrade && e.BarCount(state.LastTimes[0]) >= tradeDelay
|
||||
canTrade3 := canTrade && e.BarCount(state.LastTimes[1]) >= tradeDelay
|
||||
canTrade4 := canTrade && e.BarCount(state.LastTimes[2]) >= tradeDelay
|
||||
canSell := e.BarCount(state.LastSellTime) >= tradeDelay
|
||||
|
||||
// 获取当前总开仓数量,与原始策略的strategy.opentrades保持一致
|
||||
openTrades := len(s.LongOrders) + len(s.ShortOrders)
|
||||
|
||||
buyIdx, costRate := -1, 0.0
|
||||
if lowPrice <= buyPrice4 && canTrade4 {
|
||||
buyIdx, costRate = checkMaxPair(2, openTrades)
|
||||
} else if lowPrice <= buyPrice3 && canTrade3 {
|
||||
buyIdx, costRate = checkMaxPair(1, openTrades)
|
||||
} else if lowPrice <= buyPrice2 && canTrade2 {
|
||||
buyIdx, costRate = checkMaxPair(0, openTrades)
|
||||
}
|
||||
if buyIdx >= 0 && costRate > 0 {
|
||||
callNum := allOpens
|
||||
//if buyIdx == 0 {
|
||||
// // 对信号1,累计开单较多时,下移入场价
|
||||
// pairLock.Lock()
|
||||
// callNum = max(callNum, int(math.Round(float64(holdNums[0])*0.5))-3)
|
||||
// pairLock.Unlock()
|
||||
//}
|
||||
// 同一个bar开单多次,从第三次开始,使用ln(num)下移入场价
|
||||
currRate := 1 + max(0, math.Log10(float64(callNum-2)))
|
||||
// 用倒抛物线映射成交量倍率,作为入场价距离调整系数
|
||||
volRate := 1 + max(0, 0.4-math.Pow(1.4*(rangeVolRate-1.5), 2))
|
||||
rangeRate := math.Pow(volRate*currRate, 0.3) - 1
|
||||
limitPrice := float64(0)
|
||||
if (rangeVolRate > 1.2 || callNum > 2) && rangeRate > 0.01 {
|
||||
limitPrice = e.Close.Get(0) - rangeRate*gapRange
|
||||
}
|
||||
tag := fmt.Sprintf("zone%d", buyIdx+1)
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: tag,
|
||||
CostRate: costRate,
|
||||
Limit: limitPrice,
|
||||
Log: true,
|
||||
StopBars: 3,
|
||||
})
|
||||
state.LastTimes[buyIdx] = currentBarTime
|
||||
}
|
||||
|
||||
// 检查卖出条件
|
||||
if s.OrderNum > 0 {
|
||||
curPrice := e.Close.Get(0)
|
||||
slRange := slRate * itemRate * curPrice / 100
|
||||
avgPrice := s.PositionAvgPrice(core.OdDirtLong, "")
|
||||
if curPrice+slRange <= avgPrice {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "StopLoss",
|
||||
Log: true,
|
||||
})
|
||||
state.LastSellTime = currentBarTime
|
||||
} else {
|
||||
exitPrice := avgPrice*(1+profitRate)*0.4 + sellPrice*0.6
|
||||
if openTrades > 0 && highPrice >= exitPrice && canSell {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "SellAll",
|
||||
Log: true,
|
||||
})
|
||||
state.LastSellTime = currentBarTime
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
// 多指标综合计算大周期行情下跌分数
|
||||
score := float64(0)
|
||||
if ta.RSI(e.Close, 14).Get(0) < 30 {
|
||||
score += 1
|
||||
}
|
||||
roc := ta.ROC(e.Close, 20).Get(0)
|
||||
if roc < -20 {
|
||||
score += 2
|
||||
} else if roc < -10 {
|
||||
score += 1
|
||||
}
|
||||
plusDi, minusDi := ta.PluMinDI(e.High, e.Low, e.Close, 14)
|
||||
adx := ta.ADX(e.High, e.Low, e.Close, 14).Get(0)
|
||||
if minusDi.Get(0) > plusDi.Get(0) {
|
||||
if adx > 40 {
|
||||
score += 2
|
||||
} else if adx > 25 {
|
||||
score += 1
|
||||
}
|
||||
}
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
rates = append(rates, atrBase/(e.Close.Get(0)*0.01))
|
||||
//openRate := 0.006 * closePrice / atrBase
|
||||
//if openRate < 1 {
|
||||
// openRate = math.Sqrt(openRate)
|
||||
//}
|
||||
//openRate = max(0.7, min(1.5, openRate))
|
||||
ma7 := ta.SMA(e.Close, 7).Get(0)
|
||||
ma20 := ta.SMA(e.Close, 20)
|
||||
ma20V := ma20.Get(0)
|
||||
ma99 := ta.SMA(e.Close, 99).Get(0)
|
||||
if ma99 > ma20V {
|
||||
score += 1
|
||||
}
|
||||
state, _ := s.More.(*ZoneTradeState)
|
||||
state.score = score
|
||||
state.upFlag = 0
|
||||
state.atrBase = atrBase / (e.Close.Get(0) * 0.01)
|
||||
if ma20V > ma99 && ma20V > ma20.Get(2) {
|
||||
state.upFlag = 1
|
||||
} else if ma7 < min(ma20V, ma99) {
|
||||
state.upFlag = -1
|
||||
}
|
||||
},
|
||||
OnBatchJobs: func(jobs []*strat.StratJob) {
|
||||
pairLock.Lock()
|
||||
openUnits = 0
|
||||
allOpens = 0
|
||||
openNums = [3]int{0, 0, 0}
|
||||
holdNums = [3]int{0, 0, 0}
|
||||
volRates := make([]float64, 0, len(jobs))
|
||||
for _, j := range jobs {
|
||||
for _, od := range j.LongOrders {
|
||||
if od.EnterTag == "zone1" {
|
||||
holdNums[0] += 1
|
||||
} else if od.EnterTag == "zone2" {
|
||||
holdNums[1] += 1
|
||||
} else if od.EnterTag == "zone3" {
|
||||
holdNums[2] += 1
|
||||
}
|
||||
}
|
||||
m, _ := j.More.(*ZoneTradeState)
|
||||
volRates = append(volRates, m.volRate)
|
||||
}
|
||||
pairLock.Unlock()
|
||||
sort.Float64s(volRates)
|
||||
rangeVolRate = max(1, math.Pow(volRates[len(volRates)/2]/2, 0.3))
|
||||
},
|
||||
OnBatchInfos: func(tf string, jobs map[string]*strat.JobEnv) {
|
||||
if len(jobs) < 3 {
|
||||
return
|
||||
}
|
||||
// 多品种综合计算整体市场下行激烈程度
|
||||
downNum, downHeavyNum := float64(0), float64(0)
|
||||
atrRates := make([]float64, 0, len(jobs))
|
||||
for _, j := range jobs {
|
||||
state, _ := j.Job.More.(*ZoneTradeState)
|
||||
if state.score >= 5 {
|
||||
downHeavyNum += 1
|
||||
}
|
||||
if state.score >= 3 {
|
||||
downNum += 1
|
||||
}
|
||||
atrRates = append(atrRates, state.atrBase)
|
||||
}
|
||||
jobNum := float64(len(jobs))
|
||||
downRate := downNum / jobNum
|
||||
downHeavyRate := downHeavyNum / jobNum
|
||||
sort.Float64s(atrRates)
|
||||
avgAtrBase = atrRates[len(jobs)/2]
|
||||
if downHeavyRate >= 0.8 {
|
||||
// 极端下跌
|
||||
addThres, firstRate = 1.6, 1.2
|
||||
maxAllowUnit = jobNum * 0.5
|
||||
} else if downRate >= 0.85 && downHeavyRate >= 0.6 {
|
||||
// 严重下跌
|
||||
addThres, firstRate = 1.45, 1.15
|
||||
maxAllowUnit = jobNum * 0.55
|
||||
} else if downRate >= 0.7 && downRate <= 0.85 {
|
||||
// 市场中度下跌
|
||||
addThres, firstRate = 1.3, 1.1
|
||||
maxAllowUnit = jobNum * 0.6
|
||||
} else if downRate >= 0.5 && downRate <= 0.7 {
|
||||
// 轻度下跌
|
||||
addThres, firstRate = 1.15, 1.05
|
||||
maxAllowUnit = jobNum * 0.7
|
||||
} else {
|
||||
// 轻度或未下跌
|
||||
maxAllowUnit = jobNum
|
||||
addThres, firstRate = 1, 1
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
144
llmjob/3827.go
Normal file
144
llmjob/3827.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
// FibRSIGoldenCross 用于存储每个品种的斐波那契回撤水平
|
||||
type FibRSIGoldenCross struct {
|
||||
FibHigh float64
|
||||
FibLow float64
|
||||
Fib38 float64
|
||||
Fib50 float64
|
||||
Fib61 float64
|
||||
}
|
||||
|
||||
func J3827(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
short_ma_length := pol.DefInt("short_ma_length", 40, core.PNorm(20, 100))
|
||||
ma_ratio := pol.Def("ma_ratio", 3.8, core.PNorm(2, 6))
|
||||
rsi_length := pol.DefInt("rsi_length", 14, core.PNorm(7, 21))
|
||||
rsi_overbought := pol.Def("rsi_overbought", 70, core.PNorm(60, 80))
|
||||
rsi_oversold := pol.Def("rsi_oversold", 30, core.PNorm(20, 40))
|
||||
fib_period := pol.DefInt("fib_period", 50, core.PNorm(20, 100))
|
||||
|
||||
// 计算长期均线周期
|
||||
long_ma_length := int(float64(short_ma_length) * ma_ratio)
|
||||
|
||||
// 计算预热数量,取所有指标中最大的周期
|
||||
warmup_num := int(math.Max(float64(long_ma_length), float64(fib_period)))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmup_num,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多一个多头订单
|
||||
EachMaxShort: 1, // 最多一个空头订单
|
||||
TimeFrames: "8h",
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化斐波那契回撤水平
|
||||
s.More = &FibRSIGoldenCross{
|
||||
FibHigh: math.NaN(),
|
||||
FibLow: math.NaN(),
|
||||
Fib38: math.NaN(),
|
||||
Fib50: math.NaN(),
|
||||
Fib61: math.NaN(),
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
fib, _ := s.More.(*FibRSIGoldenCross)
|
||||
|
||||
// 计算技术指标
|
||||
rsi := ta.RSI(e.Close, rsi_length)
|
||||
short_ma := ta.SMA(e.Close, short_ma_length)
|
||||
long_ma := ta.SMA(e.Close, long_ma_length)
|
||||
|
||||
// 获取当前价格
|
||||
close_price := e.Close.Get(0)
|
||||
if math.IsNaN(close_price) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新斐波那契回撤水平
|
||||
highest := ta.Highest(e.Close, fib_period).Get(0)
|
||||
lowest := ta.Lowest(e.Close, fib_period).Get(0)
|
||||
|
||||
// 如果最高价或最低价发生变化,更新斐波那契回撤水平
|
||||
if !math.IsNaN(highest) && (math.IsNaN(fib.FibHigh) || highest != fib.FibHigh) {
|
||||
fib.FibHigh = highest
|
||||
}
|
||||
if !math.IsNaN(lowest) && (math.IsNaN(fib.FibLow) || lowest != fib.FibLow) {
|
||||
fib.FibLow = lowest
|
||||
}
|
||||
|
||||
// 计算斐波那契回撤水平
|
||||
if !math.IsNaN(fib.FibHigh) && !math.IsNaN(fib.FibLow) {
|
||||
fib_range := fib.FibHigh - fib.FibLow
|
||||
fib.Fib38 = fib.FibHigh - fib_range*0.382
|
||||
fib.Fib50 = fib.FibHigh - fib_range*0.5
|
||||
fib.Fib61 = fib.FibHigh - fib_range*0.618
|
||||
}
|
||||
|
||||
// 获取当前指标值
|
||||
rsi_value := rsi.Get(0)
|
||||
short_ma_value := short_ma.Get(0)
|
||||
long_ma_value := long_ma.Get(0)
|
||||
|
||||
// 检查指标是否有效
|
||||
if math.IsNaN(rsi_value) || math.IsNaN(short_ma_value) || math.IsNaN(long_ma_value) ||
|
||||
math.IsNaN(fib.Fib50) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检测交叉信号 - 只计算一次交叉信号
|
||||
cross_signal := short_ma.Cross(long_ma)
|
||||
golden_cross := cross_signal == 1 // 1表示上穿
|
||||
death_cross := cross_signal == -1 // -1表示下穿
|
||||
|
||||
// 检查当前持仓
|
||||
long_position := s.Position(1, "") > 0 // 1表示多头
|
||||
short_position := s.Position(-1, "") > 0 // -1表示空头
|
||||
|
||||
// 多头入场条件:黄金交叉且价格在50%斐波那契水平之上且RSI未超买
|
||||
long_condition := golden_cross && close_price > fib.Fib50 && rsi_value < rsi_overbought
|
||||
|
||||
// 空头入场条件:死亡交叉且价格在50%斐波那契水平之下且RSI未超卖
|
||||
short_condition := death_cross && close_price < fib.Fib50 && rsi_value > rsi_oversold
|
||||
|
||||
// 执行交易
|
||||
if long_condition && !long_position {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
})
|
||||
}
|
||||
|
||||
if short_condition && !short_position {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 多头平仓条件:RSI超买
|
||||
if long_position && rsi_value > rsi_overbought {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Buy",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
// 空头平仓条件:RSI超卖
|
||||
if short_position && rsi_value < rsi_oversold {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Sell",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
160
llmjob/3939.go
Normal file
160
llmjob/3939.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func J3939(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
lwstPeriod := pol.DefInt("lwst_period", 10, core.PNorm(5, 25))
|
||||
lwstMultiplier := pol.Def("lwst_multiplier", 3.0, core.PNorm(1.5, 5.5))
|
||||
lwstLength := pol.DefInt("lwst_length", 20, core.PNorm(10, 40))
|
||||
trendLength := pol.DefInt("trend_length", 14, core.PNorm(7, 25))
|
||||
trendThreshold := pol.Def("trend_threshold", 0.5, core.PNorm(0.1, 2.0))
|
||||
wtChannelLength := pol.DefInt("wt_channel_length", 9, core.PNorm(5, 20))
|
||||
wtAverageLength := pol.DefInt("wt_average_length", 12, core.PNorm(6, 24))
|
||||
wtMaLength := pol.DefInt("wt_ma_length", 3, core.PNorm(2, 8))
|
||||
wtOverbought := pol.Def("wt_overbought", 53, core.PNorm(40, 70))
|
||||
wtOversold := pol.Def("wt_oversold", -53, core.PNorm(-70, -40)) // 修复:独立的超卖参数
|
||||
slPercent := pol.Def("sl_percent", 2.0, core.PNorm(0.5, 5.0))
|
||||
tpPercent := pol.Def("tp_percent", 4.0, core.PNorm(1.0, 10.0))
|
||||
|
||||
warmupNum := max(lwstPeriod*2, trendLength*2, wtChannelLength+wtAverageLength)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
Name: "Multi-Indicator-Consensus",
|
||||
WarmupNum: warmupNum + 50,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1,
|
||||
EachMaxShort: 1,
|
||||
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
currentPrice := e.Close.Get(0)
|
||||
|
||||
// =================== Indicator 1: Liquidity Weighted Supertrend ===================
|
||||
// Volume-weighted component for dynamic sensitivity
|
||||
volSma := ta.SMA(e.Volume, lwstLength)
|
||||
volWeight := e.Volume.Div(volSma)
|
||||
|
||||
// ATR-based bands with volume weighting
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lwstPeriod)
|
||||
hl2 := ta.HL2(e.High, e.Low)
|
||||
_ = hl2.Add(atr.Mul(lwstMultiplier).Mul(volWeight)) // upperBand
|
||||
_ = hl2.Sub(atr.Mul(lwstMultiplier).Mul(volWeight)) // lowerBand
|
||||
|
||||
// Trend determination based on price action and previous trend
|
||||
// 修复:使用Series保存趋势状态
|
||||
lwstTrendSeries := e.Close.To("_lwst_trend", 0)
|
||||
var lwstTrend float64
|
||||
if e.Close.Len() > 1 {
|
||||
prevTrend := lwstTrendSeries.Get(1)
|
||||
if currentPrice > prevTrend {
|
||||
lwstTrend = 1
|
||||
} else if currentPrice < prevTrend {
|
||||
lwstTrend = -1
|
||||
} else {
|
||||
lwstTrend = prevTrend
|
||||
}
|
||||
} else {
|
||||
lwstTrend = 1.0
|
||||
}
|
||||
lwstTrendSeries.Set(lwstTrend)
|
||||
|
||||
// =================== Indicator 2: Trend Signals ===================
|
||||
// Dual EMA system for trend detection
|
||||
fastEma := ta.EMA(e.Close, trendLength)
|
||||
slowEma := ta.EMA(e.Close, trendLength*2)
|
||||
trendDiff := fastEma.Sub(slowEma).Div(slowEma).Mul(100)
|
||||
trendDiffVal := trendDiff.Get(0)
|
||||
|
||||
var trendSignal float64
|
||||
if trendDiffVal > trendThreshold {
|
||||
trendSignal = 1
|
||||
} else if trendDiffVal < -trendThreshold {
|
||||
trendSignal = -1
|
||||
} else {
|
||||
trendSignal = 0
|
||||
}
|
||||
|
||||
// =================== Indicator 3: Enhanced Wavetrend ===================
|
||||
// Calculate Wavetrend components
|
||||
ap := ta.HLC3(e.High, e.Low, e.Close)
|
||||
esa := ta.EMA(ap, wtChannelLength)
|
||||
d := ta.EMA(ap.Sub(esa).Abs(), wtChannelLength)
|
||||
ci := ap.Sub(esa).Div(d.Mul(0.015))
|
||||
tci := ta.EMA(ci, wtAverageLength)
|
||||
|
||||
// Generate main and signal lines
|
||||
wt1 := tci
|
||||
wt2 := ta.SMA(wt1, wtMaLength)
|
||||
|
||||
// Generate Wavetrend Signal based on overbought/oversold conditions
|
||||
wt1Val := wt1.Get(0)
|
||||
wt2Val := wt2.Get(0)
|
||||
|
||||
// 修复:使用Series保存wt_signal状态
|
||||
wtSignalSeries := e.Close.To("_wt_signal", 0)
|
||||
var wtSignal float64
|
||||
if wt1Val > wtOverbought && wt2Val > wtOverbought {
|
||||
wtSignal = -1 // 超买信号
|
||||
} else if wt1Val < wtOversold && wt2Val < wtOversold { // 修复:使用独立的wtOversold参数
|
||||
wtSignal = 1 // 超卖信号
|
||||
} else {
|
||||
wtSignal = wtSignalSeries.Get(1)
|
||||
}
|
||||
wtSignalSeries.Set(wtSignal)
|
||||
|
||||
// =================== Consensus Signal Generation ===================
|
||||
// 修复:正确计算净信号数(范围-3到3)
|
||||
consensusCount := 0
|
||||
if lwstTrend == 1 {
|
||||
consensusCount++
|
||||
} else if lwstTrend == -1 {
|
||||
consensusCount--
|
||||
}
|
||||
if trendSignal == 1 {
|
||||
consensusCount++
|
||||
} else if trendSignal == -1 {
|
||||
consensusCount--
|
||||
}
|
||||
if wtSignal == 1 {
|
||||
consensusCount++
|
||||
} else if wtSignal == -1 {
|
||||
consensusCount--
|
||||
}
|
||||
|
||||
// Generate trading signals when majority (2+ indicators) agree
|
||||
buySignal := consensusCount >= 2
|
||||
sellSignal := consensusCount <= -2
|
||||
|
||||
// =================== Trade Execution ===================
|
||||
if buySignal && len(s.LongOrders) == 0 && len(s.ShortOrders) == 0 {
|
||||
// 计算止损止盈价格
|
||||
slAmount := currentPrice * slPercent / 100
|
||||
tpAmount := currentPrice * tpPercent / 100
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
StopLossVal: slAmount,
|
||||
TakeProfitVal: tpAmount,
|
||||
})
|
||||
} else if sellSignal && len(s.ShortOrders) == 0 && len(s.LongOrders) == 0 {
|
||||
// 计算做空止损止盈价格
|
||||
slAmount := currentPrice * slPercent / 100
|
||||
tpAmount := currentPrice * tpPercent / 100
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Short: false,
|
||||
StopLossVal: slAmount,
|
||||
TakeProfitVal: tpAmount,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
109
llmjob/3955.go
Normal file
109
llmjob/3955.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3955(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
macdFastPeriod := pol.DefInt("macdFastPeriod", 14, core.PNorm(5, 20))
|
||||
macdSlowRate := pol.Def("macdSlowRate", 2, core.PNorm(1.5, 4))
|
||||
macdSignalPeriod := pol.DefInt("macdSignalPeriod", 9, core.PNorm(5, 15))
|
||||
vwmaFastPeriod := pol.DefInt("vwmaFastPeriod", 20, core.PNorm(10, 40))
|
||||
leverage := pol.DefInt("leverage", 1, core.PNorm(1, 20))
|
||||
|
||||
// 计算MACD慢线周期
|
||||
macdSlowPeriod := int(float64(macdFastPeriod) * macdSlowRate)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: macdSlowPeriod + macdSignalPeriod, // 确保有足够的数据计算指标
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多一个多头订单
|
||||
EachMaxShort: 1, // 最多一个空头订单
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算MACD指标
|
||||
macdLine, signalLine := ta.MACD(e.Close, macdFastPeriod, macdSlowPeriod, macdSignalPeriod)
|
||||
histogram := macdLine.Sub(signalLine) // 计算MACD柱状图
|
||||
|
||||
// 计算VWMA指标
|
||||
vwma20 := ta.VWMA(e.Close, e.Volume, vwmaFastPeriod) // 使用VWMA替代WMA,考虑成交量因素
|
||||
vwma50 := ta.VWMA(e.Close, e.Volume, 50) // 使用VWMA替代WMA,考虑成交量因素
|
||||
|
||||
// 获取当前指标值
|
||||
macdLineVal := macdLine.Get(0)
|
||||
signalLineVal := signalLine.Get(0)
|
||||
histogramVal := histogram.Get(0)
|
||||
vwma20Val := vwma20.Get(0)
|
||||
vwma50Val := vwma50.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(macdLineVal) || math.IsNaN(signalLineVal) || math.IsNaN(histogramVal) ||
|
||||
math.IsNaN(vwma20Val) || math.IsNaN(vwma50Val) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成MACD信号
|
||||
macdLongEntrySignal := histogramVal > 0
|
||||
macdShortEntrySignal := histogramVal < 0
|
||||
|
||||
// 生成VWMA信号
|
||||
vwmaLongEntrySignal := vwma20Val > vwma50Val
|
||||
vwmaShortEntrySignal := vwma20Val < vwma50Val
|
||||
|
||||
// 生成组合交易信号
|
||||
longEntry := macdLongEntrySignal && vwmaLongEntrySignal
|
||||
shortEntry := macdShortEntrySignal && vwmaShortEntrySignal
|
||||
|
||||
// 检测MACD交叉信号作为出场条件(优化为单次调用)
|
||||
cross := macdLine.Cross(signalLine)
|
||||
longExit := cross == -1 // MACD线下穿信号线
|
||||
shortExit := cross == 1 // MACD线上穿信号线
|
||||
|
||||
// 执行交易逻辑
|
||||
if longEntry && len(s.LongOrders) == 0 {
|
||||
// 计算杠杆化后的合约数量
|
||||
costRate := float64(leverage)
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Short: false,
|
||||
CostRate: costRate,
|
||||
Leverage: float64(leverage),
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
}
|
||||
|
||||
if longExit && len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "LongExit",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
if shortEntry && len(s.ShortOrders) == 0 {
|
||||
// 计算杠杆化后的合约数量
|
||||
costRate := float64(leverage)
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
CostRate: costRate,
|
||||
Leverage: float64(leverage),
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
}
|
||||
|
||||
if shortExit && len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "ShortExit",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
94
llmjob/3956.go
Normal file
94
llmjob/3956.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3956(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
fastLength := pol.DefInt("fastLength", 9, core.PNorm(5, 20))
|
||||
slowMaRate := pol.Def("slowMaRate", 2.17, core.PNorm(1.5, 4))
|
||||
signalLength := pol.DefInt("signalLength", 20, core.PNorm(9, 30))
|
||||
entryLevel := pol.Def("entryLevel", 0, core.PNorm(-5, 5))
|
||||
longExitLevel := pol.Def("longExitLevel", 11, core.PNorm(5, 20))
|
||||
shortExitLevel := pol.Def("shortExitLevel", -9, core.PNorm(-20, -5))
|
||||
|
||||
// 计算慢速EMA周期
|
||||
slowLength := int(float64(fastLength) * slowMaRate)
|
||||
|
||||
// 确定预热数量,取所有参数中的最大值
|
||||
warmupNum := fastLength
|
||||
if slowLength > warmupNum {
|
||||
warmupNum = slowLength
|
||||
}
|
||||
if signalLength > warmupNum {
|
||||
warmupNum = signalLength
|
||||
}
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算MACD指标
|
||||
fastEma := ta.EMA(e.Close, fastLength)
|
||||
slowEma := ta.EMA(e.Close, slowLength)
|
||||
macd := fastEma.Sub(slowEma)
|
||||
signal := ta.EMA(macd, signalLength)
|
||||
hist := macd.Sub(signal)
|
||||
|
||||
// 获取当前值
|
||||
histVal := hist.Get(0)
|
||||
if math.IsNaN(histVal) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查交叉信号
|
||||
entryCross := hist.Cross(entryLevel)
|
||||
longEntry := entryCross == 1 // 上穿入场水平线
|
||||
shortEntry := entryCross == -1 // 下穿入场水平线
|
||||
longExit := hist.Cross(longExitLevel) == 1 // 上穿多头出场水平线
|
||||
shortExit := hist.Cross(shortExitLevel) == -1 // 下穿空头出场水平线
|
||||
|
||||
// 获取当前持仓状态
|
||||
longPos := s.Position(core.OdDirtLong, "")
|
||||
shortPos := s.Position(core.OdDirtShort, "")
|
||||
|
||||
// 多头入场
|
||||
if longEntry && longPos == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
})
|
||||
}
|
||||
|
||||
// 空头入场
|
||||
if shortEntry && shortPos == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 多头出场
|
||||
if longPos > 0 && longExit {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Long Exit",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
// 空头出场
|
||||
if shortPos < 0 && shortExit {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Short Exit",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
113
llmjob/3992.go
Normal file
113
llmjob/3992.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J3992(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
fastLength := pol.DefInt("fastLength", 10, core.PUniform(5, 20))
|
||||
macdSlowRate := pol.Def("macdSlowRate", 2.17, core.PNorm(1.5, 4))
|
||||
rsiThreshold := pol.DefInt("rsiThreshold", 65, core.PUniform(60, 80))
|
||||
bbMult := pol.Def("bbMult", 2.0, core.PNorm(1.5, 3.0))
|
||||
rsiLength := pol.DefInt("rsiLength", 14, core.PUniform(7, 21))
|
||||
|
||||
// 计算其他参数
|
||||
MACDLength := 9 // 固定值
|
||||
slowLength := int(float64(fastLength) * macdSlowRate)
|
||||
rsiOverbought := float64(rsiThreshold)
|
||||
rsiOversold := 100 - rsiOverbought
|
||||
stopLossPercent := 0.02 // 2% stop loss
|
||||
takeProfitPercent := 0.05 // 5% take profit
|
||||
|
||||
// 计算预热数量,取最大值
|
||||
warmupNum := slowLength
|
||||
if MACDLength > warmupNum {
|
||||
warmupNum = MACDLength
|
||||
}
|
||||
if rsiLength > warmupNum {
|
||||
warmupNum = rsiLength
|
||||
}
|
||||
if bbLength := 20; bbLength > warmupNum {
|
||||
warmupNum = bbLength
|
||||
}
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 检查数据长度是否足够
|
||||
if e.Close.Len() < warmupNum {
|
||||
return
|
||||
}
|
||||
|
||||
// MACD calculations
|
||||
fastMA := ta.EMA(e.Close, fastLength)
|
||||
slowMA := ta.EMA(e.Close, slowLength)
|
||||
macdLine := fastMA.Sub(slowMA)
|
||||
signalLine := ta.EMA(macdLine, MACDLength)
|
||||
|
||||
// RSI calculation
|
||||
rsi := ta.RSI(e.Close, rsiLength)
|
||||
|
||||
// Bollinger Bands calculation
|
||||
bbLength := 20
|
||||
basis := ta.SMA(e.Close, bbLength)
|
||||
stdDev := ta.StdDev(e.Close, bbLength)
|
||||
dev := stdDev.Mul(bbMult)
|
||||
upper := basis.Add(dev)
|
||||
lower := basis.Sub(dev)
|
||||
|
||||
// 获取当前值
|
||||
closePrice := e.Close.Get(0)
|
||||
macdVal := macdLine.Get(0)
|
||||
signalVal := signalLine.Get(0)
|
||||
rsiVal := rsi.Get(0)
|
||||
upperVal := upper.Get(0)
|
||||
lowerVal := lower.Get(0)
|
||||
|
||||
// 检查NaN值
|
||||
if math.IsNaN(closePrice) || math.IsNaN(macdVal) || math.IsNaN(signalVal) ||
|
||||
math.IsNaN(rsiVal) || math.IsNaN(upperVal) || math.IsNaN(lowerVal) {
|
||||
return
|
||||
}
|
||||
|
||||
// Entry conditions
|
||||
macdCrossOver := macdLine.Cross(signalLine) == 1
|
||||
rsiCrossUnder := rsi.Cross(rsiOversold) == -1
|
||||
longCondition := (macdCrossOver || rsiCrossUnder) && closePrice > lowerVal
|
||||
|
||||
macdCrossUnder := macdLine.Cross(signalLine) == -1
|
||||
rsiCrossOver := rsi.Cross(rsiOverbought) == 1
|
||||
shortCondition := (macdCrossUnder || rsiCrossOver) && closePrice < upperVal
|
||||
|
||||
// Long position logic
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLossVal: closePrice * (1 - stopLossPercent),
|
||||
TakeProfitVal: closePrice * (1 + takeProfitPercent),
|
||||
})
|
||||
}
|
||||
|
||||
// Short position logic
|
||||
if shortCondition && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLossVal: closePrice * (1 + stopLossPercent),
|
||||
TakeProfitVal: closePrice * (1 - takeProfitPercent),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
177
llmjob/4009.go
Normal file
177
llmjob/4009.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
// Sta4009 存储每个品种的策略状态
|
||||
type Sta4009 struct {
|
||||
atrTrailingStop float64 // ATR跟踪止损值
|
||||
position int // 当前持仓方向(1=多头,-1=空头,0=无持仓)
|
||||
entryPrice float64 // 入场价格
|
||||
buyTarget float64 // 多头目标价格
|
||||
sellTarget float64 // 空头目标价格
|
||||
}
|
||||
|
||||
func J4009(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
keyValue := pol.Def("keyValue", 1, core.PNorm(0.1, 5))
|
||||
atrPeriod := pol.DefInt("atrPeriod", 14, core.PNorm(5, 30))
|
||||
useHeikinAshi := pol.DefInt("useHeikinAshi", 0, core.PNorm(0, 1)) // 0=false, 1=true
|
||||
exitPercentage := pol.Def("exitPercentage", 0.002, core.PNorm(0.001, 0.01))
|
||||
rsiPeriod := pol.DefInt("rsiPeriod", 14, core.PNorm(7, 30))
|
||||
donchianLength := pol.DefInt("donchianLength", 20, core.PNorm(10, 50))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: donchianLength + 20, // 确保有足够数据计算所有指标
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
TimeFrames: "1d",
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
s.More = &Sta4009{
|
||||
atrTrailingStop: math.NaN(),
|
||||
position: 0,
|
||||
entryPrice: math.NaN(),
|
||||
buyTarget: math.NaN(),
|
||||
sellTarget: math.NaN(),
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*Sta4009)
|
||||
|
||||
// 计算基础指标
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
lossDistance := keyValue * atr.Get(0)
|
||||
rsiValue := ta.RSI(e.Close, rsiPeriod).Get(0)
|
||||
|
||||
// 计算价格源(普通收盘价或惠金-阿希收盘价)
|
||||
var priceSource *ta.Series
|
||||
if useHeikinAshi > 0 {
|
||||
haOpen, haHigh, haLow, haClose := ta.HeikinAshi(e)
|
||||
haCloseSeries := haOpen.Add(haHigh).Add(haLow).Add(haClose).Div(4)
|
||||
priceSource = haCloseSeries
|
||||
} else {
|
||||
priceSource = e.Close
|
||||
}
|
||||
currentPrice := priceSource.Get(0)
|
||||
prevPrice := priceSource.Get(1)
|
||||
|
||||
// 计算ATR跟踪止损
|
||||
if !math.IsNaN(lossDistance) && !math.IsNaN(currentPrice) && !math.IsNaN(prevPrice) {
|
||||
prevStop := state.atrTrailingStop
|
||||
if math.IsNaN(prevStop) {
|
||||
prevStop = 0
|
||||
}
|
||||
|
||||
if currentPrice > prevStop && prevPrice > prevStop {
|
||||
// 价格在止损线上方且前一价格也在止损线上方,止损线上移
|
||||
state.atrTrailingStop = math.Max(prevStop, currentPrice-lossDistance)
|
||||
} else if currentPrice < prevStop && prevPrice < prevStop {
|
||||
// 价格在止损线下方且前一价格也在止损线下方,止损线下移
|
||||
state.atrTrailingStop = math.Min(prevStop, currentPrice+lossDistance)
|
||||
} else if currentPrice > prevStop {
|
||||
// 价格上穿止损线,设置新的止损线
|
||||
state.atrTrailingStop = currentPrice - lossDistance
|
||||
} else {
|
||||
// 价格下穿止损线,设置新的止损线
|
||||
state.atrTrailingStop = currentPrice + lossDistance
|
||||
}
|
||||
}
|
||||
|
||||
// 计算持仓方向
|
||||
if !math.IsNaN(prevPrice) && !math.IsNaN(state.atrTrailingStop) {
|
||||
if prevPrice < state.atrTrailingStop && currentPrice > state.atrTrailingStop {
|
||||
state.position = 1 // 多头信号
|
||||
} else if prevPrice > state.atrTrailingStop && currentPrice < state.atrTrailingStop {
|
||||
state.position = -1 // 空头信号
|
||||
}
|
||||
// 否则保持原有方向
|
||||
}
|
||||
|
||||
// 计算唐奇安通道
|
||||
donchianUpper := ta.Highest(e.High, donchianLength)
|
||||
donchianLower := ta.Lowest(e.Low, donchianLength)
|
||||
donchianBasis := donchianUpper.Add(donchianLower).Div(2).Get(0)
|
||||
|
||||
// 计算交叉信号
|
||||
above := currentPrice > state.atrTrailingStop && prevPrice <= state.atrTrailingStop
|
||||
below := currentPrice < state.atrTrailingStop && prevPrice >= state.atrTrailingStop
|
||||
|
||||
// 生成交易信号
|
||||
buySignal := currentPrice > state.atrTrailingStop && above && rsiValue > 50 && currentPrice > donchianBasis
|
||||
sellSignal := currentPrice < state.atrTrailingStop && below && rsiValue < 50 && currentPrice < donchianBasis
|
||||
|
||||
// 执行交易
|
||||
if buySignal && s.OrderNum == 0 {
|
||||
state.entryPrice = currentPrice
|
||||
state.buyTarget = state.entryPrice * (1 + exitPercentage)
|
||||
state.sellTarget = state.entryPrice * (1 - exitPercentage)
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
} else if sellSignal && s.OrderNum == 0 {
|
||||
state.entryPrice = currentPrice
|
||||
state.buyTarget = state.entryPrice * (1 + exitPercentage)
|
||||
state.sellTarget = state.entryPrice * (1 - exitPercentage)
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
}
|
||||
|
||||
// 退出条件检查
|
||||
if len(s.LongOrders) > 0 {
|
||||
od := s.LongOrders[0]
|
||||
if od.Status >= ormo.InOutStatusFullEnter {
|
||||
if currentPrice >= state.buyTarget {
|
||||
// 达到止盈目标
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Take Profit",
|
||||
Dirt: core.OdDirtLong,
|
||||
Limit: state.buyTarget,
|
||||
})
|
||||
} else if currentPrice <= state.sellTarget {
|
||||
// 触发止损
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Stop Loss",
|
||||
Dirt: core.OdDirtLong,
|
||||
Limit: state.sellTarget,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.ShortOrders) > 0 {
|
||||
od := s.ShortOrders[0]
|
||||
if od.Status >= ormo.InOutStatusFullEnter {
|
||||
if currentPrice <= state.sellTarget {
|
||||
// 达到止盈目标
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Take Profit",
|
||||
Dirt: core.OdDirtShort,
|
||||
Limit: state.sellTarget,
|
||||
})
|
||||
} else if currentPrice >= state.buyTarget {
|
||||
// 触发止损
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Stop Loss",
|
||||
Dirt: core.OdDirtShort,
|
||||
Limit: state.buyTarget,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
128
llmjob/4034.go
Normal file
128
llmjob/4034.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
// 定义一个结构体来存储策略需要的变量
|
||||
type StrategyData struct {
|
||||
atrValue float64
|
||||
}
|
||||
|
||||
func J4034(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
fastLength := pol.DefInt("fastLength", 12, core.PNorm(5, 20))
|
||||
emaRate := pol.Def("emaRate", 2.33, core.PNorm(1.5, 5.0))
|
||||
rsiLength := pol.DefInt("rsiLength", 14, core.PNorm(7, 30))
|
||||
rsiOverbought := pol.DefInt("rsiOverbought", 70, core.PNorm(60, 85))
|
||||
rsiOversold := pol.DefInt("rsiOversold", 30, core.PNorm(15, 40))
|
||||
volLength := pol.DefInt("volLength", 20, core.PNorm(10, 40))
|
||||
atrLength := pol.DefInt("atrLength", 14, core.PNorm(7, 30))
|
||||
atrMultiplierSL := pol.Def("atrMultiplierSL", 1.8, core.PNorm(1.0, 3.0))
|
||||
atrMultiplierTP := pol.Def("atrMultiplierTP", 2.5, core.PNorm(1.5, 5.0))
|
||||
|
||||
// 计算慢速EMA周期
|
||||
slowLength := int(float64(fastLength) * emaRate)
|
||||
|
||||
// 确定预热数量,取所有指标中最大的周期
|
||||
warmupNum := fastLength
|
||||
if slowLength > warmupNum {
|
||||
warmupNum = slowLength
|
||||
}
|
||||
if rsiLength > warmupNum {
|
||||
warmupNum = rsiLength
|
||||
}
|
||||
if volLength > warmupNum {
|
||||
warmupNum = volLength
|
||||
}
|
||||
if atrLength > warmupNum {
|
||||
warmupNum = atrLength
|
||||
}
|
||||
// 额外增加1个K线用于突破确认
|
||||
warmupNum += 1
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
EachMaxLong: 1, // 最多同时持有一个多头订单
|
||||
EachMaxShort: 1, // 最多同时持有一个空头订单
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略数据
|
||||
s.More = &StrategyData{atrValue: 0}
|
||||
},
|
||||
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
fastEMA := ta.EMA(e.Close, fastLength)
|
||||
slowEMA := ta.EMA(e.Close, slowLength)
|
||||
rsiValue := ta.RSI(e.Close, rsiLength)
|
||||
volMA := ta.SMA(e.Volume, volLength)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrLength)
|
||||
|
||||
// 获取当前值
|
||||
closePrice := e.Close.Get(0)
|
||||
highPrev := e.High.Get(1)
|
||||
lowPrev := e.Low.Get(1)
|
||||
volume := e.Volume.Get(0)
|
||||
volMAValue := volMA.Get(0)
|
||||
rsi := rsiValue.Get(0)
|
||||
atrValue := atr.Get(0)
|
||||
maCross := fastEMA.Cross(slowEMA)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) || math.IsNaN(highPrev) || math.IsNaN(lowPrev) ||
|
||||
math.IsNaN(volume) || math.IsNaN(volMAValue) || math.IsNaN(rsi) || math.IsNaN(atrValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// 存储ATR值以便在跟踪止损中使用
|
||||
data, _ := s.More.(*StrategyData)
|
||||
data.atrValue = atrValue
|
||||
|
||||
// K线突破条件
|
||||
longCandleBreak := closePrice > highPrev
|
||||
shortCandleBreak := closePrice < lowPrev
|
||||
|
||||
// 入场条件
|
||||
longCondition := maCross == 1 && rsi < float64(rsiOverbought) && volume > volMAValue && longCandleBreak
|
||||
shortCondition := maCross == -1 && rsi > float64(rsiOversold) && volume > volMAValue && shortCandleBreak
|
||||
|
||||
// 执行交易
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
// 计算具体止损止盈价格
|
||||
longStop := closePrice - atrMultiplierSL*atrValue
|
||||
longTP := closePrice + atrMultiplierTP*atrValue
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Short: false,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLoss: longStop,
|
||||
TakeProfit: longTP,
|
||||
})
|
||||
}
|
||||
|
||||
if shortCondition && len(s.ShortOrders) == 0 {
|
||||
// 计算具体止损止盈价格
|
||||
shortStop := closePrice + atrMultiplierSL*atrValue
|
||||
shortTP := closePrice - atrMultiplierTP*atrValue
|
||||
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLoss: shortStop,
|
||||
TakeProfit: shortTP,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
477
llmjob/4067.go
Normal file
477
llmjob/4067.go
Normal file
@@ -0,0 +1,477 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// State4067 存储每个品种的策略状态
|
||||
type State4067 struct {
|
||||
step int
|
||||
inTrade bool
|
||||
tradeDirection int // 1=多头, -1=空头, 0=无
|
||||
entryPrice float64
|
||||
tp1Hit bool
|
||||
buytp1Hit bool
|
||||
buySLPrice float64
|
||||
buyTP1Price float64
|
||||
buyTP2Price float64
|
||||
selltp1Hit bool
|
||||
sellSLPrice float64
|
||||
sellTP1Price float64
|
||||
sellTP2Price float64
|
||||
higherEMA float64 // 更高时间周期EMA值
|
||||
}
|
||||
|
||||
// MTFCross 多时间周期EMA交叉策略
|
||||
func J4067(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
fastPeriod := pol.DefInt("fastPeriod", 9, core.PNorm(5, 20))
|
||||
slowRate := pol.Def("slowRate", 5.56, core.PNorm(2, 10))
|
||||
higherEMARate := pol.Def("higherEMARate", 2, core.PNorm(1.5, 5))
|
||||
slRate := pol.Def("slRate", 1.6, core.PNorm(0.5, 5))
|
||||
tp1Rate := pol.Def("tp1Rate", 1.4, core.PNorm(0.5, 3))
|
||||
beoffRate := pol.Def("beoffRate", 0.3, core.PNorm(0.1, 1))
|
||||
atrPeriod := pol.DefInt("atrPeriod", 14, core.PNorm(7, 30))
|
||||
|
||||
// 交易时间控制参数
|
||||
startHour := pol.DefInt("startHour", 1, core.PNorm(0, 23))
|
||||
endHour := pol.DefInt("endHour", 25, core.PNorm(0, 47))
|
||||
tradeOnMonday := pol.DefInt("tradeOnMonday", 1, core.PNorm(0, 1)) != 0
|
||||
tradeOnTuesday := pol.DefInt("tradeOnTuesday", 1, core.PNorm(0, 1)) != 0
|
||||
tradeOnWednesday := pol.DefInt("tradeOnWednesday", 1, core.PNorm(0, 1)) != 0
|
||||
tradeOnThursday := pol.DefInt("tradeOnThursday", 1, core.PNorm(0, 1)) != 0
|
||||
tradeOnFriday := pol.DefInt("tradeOnFriday", 1, core.PNorm(0, 1)) != 0
|
||||
|
||||
// 计算衍生参数
|
||||
slowPeriod := int(float64(fastPeriod) * slowRate)
|
||||
higherPeriod := int(float64(slowPeriod) * higherEMARate)
|
||||
|
||||
// 调整endHour为24小时格式
|
||||
adjustedEndHour := endHour % 24
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowPeriod, // 预热K线数量
|
||||
HedgeOff: true, // 单向持仓
|
||||
EachMaxLong: 1, // 最多一个多头订单
|
||||
EachMaxShort: 1, // 最多一个空头订单
|
||||
RunTimeFrames: []string{"4h"},
|
||||
// 初始化策略状态
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &State4067{
|
||||
step: 0,
|
||||
inTrade: false,
|
||||
tradeDirection: 0,
|
||||
entryPrice: math.NaN(),
|
||||
tp1Hit: false,
|
||||
buytp1Hit: false,
|
||||
selltp1Hit: false,
|
||||
buySLPrice: math.NaN(),
|
||||
buyTP1Price: math.NaN(),
|
||||
buyTP2Price: math.NaN(),
|
||||
sellSLPrice: math.NaN(),
|
||||
sellTP1Price: math.NaN(),
|
||||
sellTP2Price: math.NaN(),
|
||||
higherEMA: math.NaN(),
|
||||
}
|
||||
},
|
||||
|
||||
// 定义需要订阅的其他时间周期数据
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{Pair: "_cur_", TimeFrame: "15m", WarmupNum: higherPeriod},
|
||||
}
|
||||
},
|
||||
|
||||
// 处理更高时间周期数据
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
state, _ := s.More.(*State4067)
|
||||
// 计算更高时间周期的EMA
|
||||
higherEMA := ta.EMA(e.Close, higherPeriod)
|
||||
if higherEMA.Len() > 0 {
|
||||
state.higherEMA = higherEMA.Get(0)
|
||||
}
|
||||
},
|
||||
|
||||
// 主策略逻辑
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*State4067)
|
||||
|
||||
// 计算技术指标
|
||||
fastEMA := ta.EMA(e.Close, fastPeriod)
|
||||
slowEMA := ta.EMA(e.Close, slowPeriod)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
|
||||
// 获取当前值
|
||||
closePrice := e.Close.Get(0)
|
||||
fastEMAV := fastEMA.Get(0)
|
||||
slowEMAV := slowEMA.Get(0)
|
||||
atrV := atr.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) || math.IsNaN(fastEMAV) || math.IsNaN(slowEMAV) ||
|
||||
math.IsNaN(atrV) || math.IsNaN(state.higherEMA) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算止损止盈距离
|
||||
slDist := atrV * slRate
|
||||
tp1Dist := atrV * tp1Rate
|
||||
tp2Dist := atrV * tp1Rate * 2 // 第二止盈是第一止盈的2倍
|
||||
beoffDist := atrV * beoffRate
|
||||
|
||||
// 检查交易时间
|
||||
t := time.UnixMilli(e.TimeStart)
|
||||
isTradingTime := checkTradingTime(t, startHour, adjustedEndHour,
|
||||
tradeOnMonday, tradeOnTuesday, tradeOnWednesday, tradeOnThursday, tradeOnFriday)
|
||||
|
||||
// 更新信号状态步骤
|
||||
updateStep(state, fastEMA, slowEMA)
|
||||
|
||||
// 生成交易信号(仅在交易时间内)
|
||||
if isTradingTime {
|
||||
if state.step == 1 && !state.inTrade && fastEMAV > slowEMAV && fastEMAV > state.higherEMA {
|
||||
// 买入信号
|
||||
state.inTrade = true
|
||||
state.tradeDirection = 1
|
||||
state.entryPrice = closePrice
|
||||
state.buytp1Hit = false
|
||||
state.tp1Hit = false
|
||||
state.buySLPrice = state.entryPrice - slDist
|
||||
state.buyTP1Price = state.entryPrice + tp1Dist
|
||||
state.buyTP2Price = state.entryPrice + tp2Dist
|
||||
state.step = 3
|
||||
|
||||
// 开多单
|
||||
//s.OpenOrder(&strat.EnterReq{
|
||||
// Tag: "Buy",
|
||||
// Short: false,
|
||||
// StopLoss: state.buySLPrice,
|
||||
// TakeProfit: state.buyTP1Price,
|
||||
// StopLossVal: slDist,
|
||||
//})
|
||||
} else if state.step == 2 && !state.inTrade && fastEMAV < slowEMAV && fastEMAV < state.higherEMA {
|
||||
// 卖出信号
|
||||
state.inTrade = true
|
||||
state.tradeDirection = -1
|
||||
state.entryPrice = closePrice
|
||||
state.selltp1Hit = false
|
||||
state.tp1Hit = false
|
||||
state.sellSLPrice = state.entryPrice + slDist
|
||||
state.sellTP1Price = state.entryPrice - tp1Dist
|
||||
state.sellTP2Price = state.entryPrice - tp2Dist
|
||||
state.step = 4
|
||||
|
||||
// 开空单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
StopLoss: state.sellSLPrice,
|
||||
TakeProfit: state.sellTP1Price,
|
||||
StopLossVal: slDist,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 管理多头仓位
|
||||
if state.inTrade && state.tradeDirection == 1 && len(s.LongOrders) > 0 {
|
||||
od := s.LongOrders[0]
|
||||
highPrice := e.High.Get(0)
|
||||
lowPrice := e.Low.Get(0)
|
||||
|
||||
// 检查是否达到第一止盈目标
|
||||
if highPrice >= state.buyTP1Price && !state.tp1Hit {
|
||||
state.tp1Hit = true
|
||||
state.buytp1Hit = true
|
||||
// 移动止损到保本位置
|
||||
state.buySLPrice = state.entryPrice + beoffDist
|
||||
// 平仓25%
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP1 Hit",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: 0.25,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
// 更新剩余仓位的止损止盈
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: state.buySLPrice,
|
||||
Rate: 1.0,
|
||||
Tag: "Breakeven",
|
||||
})
|
||||
od.SetTakeProfit(&ormo.ExitTrigger{
|
||||
Price: state.buyTP2Price,
|
||||
Rate: 1.0,
|
||||
Tag: "TP2",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查是否触发止损
|
||||
if lowPrice <= state.buySLPrice && !state.tp1Hit {
|
||||
// 止损出场
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "SL Hit",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: 1.0,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
resetState(state)
|
||||
}
|
||||
|
||||
// 检查是否达到第二止盈目标
|
||||
if state.tp1Hit && highPrice >= state.buyTP2Price {
|
||||
// 第二止盈出场
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP2 Hit",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: 1.0,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
resetState(state)
|
||||
}
|
||||
|
||||
// 检查保本止损是否触发
|
||||
if state.tp1Hit && lowPrice <= state.buySLPrice {
|
||||
// 保本止损出场
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Breakeven Hit",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: 1.0,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
resetState(state)
|
||||
}
|
||||
}
|
||||
|
||||
// 管理空头仓位
|
||||
if state.inTrade && state.tradeDirection == -1 && len(s.ShortOrders) > 0 {
|
||||
od := s.ShortOrders[0]
|
||||
highPrice := e.High.Get(0)
|
||||
lowPrice := e.Low.Get(0)
|
||||
|
||||
// 检查是否达到第一止盈目标
|
||||
if lowPrice <= state.sellTP1Price && !state.tp1Hit {
|
||||
state.tp1Hit = true
|
||||
state.selltp1Hit = true
|
||||
// 移动止损到保本位置
|
||||
state.sellSLPrice = state.entryPrice - beoffDist
|
||||
// 平仓25%
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP1 Hit",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: 0.25,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
// 更新剩余仓位的止损止盈
|
||||
od.SetStopLoss(&ormo.ExitTrigger{
|
||||
Price: state.sellSLPrice,
|
||||
Rate: 1.0,
|
||||
Tag: "Breakeven",
|
||||
})
|
||||
od.SetTakeProfit(&ormo.ExitTrigger{
|
||||
Price: state.sellTP2Price,
|
||||
Rate: 1.0,
|
||||
Tag: "TP2",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查是否触发止损
|
||||
if highPrice >= state.sellSLPrice && !state.tp1Hit {
|
||||
// 止损出场
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "SL Hit",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: 1.0,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
resetState(state)
|
||||
}
|
||||
|
||||
// 检查是否达到第二止盈目标
|
||||
if state.tp1Hit && lowPrice <= state.sellTP2Price {
|
||||
// 第二止盈出场
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "TP2 Hit",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: 1.0,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
resetState(state)
|
||||
}
|
||||
|
||||
// 检查保本止损是否触发
|
||||
if state.tp1Hit && highPrice >= state.sellSLPrice {
|
||||
// 保本止损出场
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Breakeven Hit",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: 1.0,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
})
|
||||
resetState(state)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新交易状态
|
||||
if state.inTrade && state.tradeDirection == 1 && state.step == 2 {
|
||||
// 多头收到卖出信号
|
||||
state.inTrade = false
|
||||
state.tradeDirection = 0
|
||||
state.step = 2
|
||||
}
|
||||
|
||||
if state.inTrade && state.tradeDirection == -1 && state.step == 1 {
|
||||
// 空头收到买入信号
|
||||
state.inTrade = false
|
||||
state.tradeDirection = 0
|
||||
state.step = 1
|
||||
}
|
||||
},
|
||||
|
||||
// 自定义订单退出逻辑
|
||||
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
||||
state, _ := s.More.(*State4067)
|
||||
|
||||
// 多头订单检查
|
||||
if !od.Short && state.inTrade && state.tradeDirection == 1 {
|
||||
lowPrice := s.Env.Low.Get(0)
|
||||
|
||||
// 检查是否触发止损
|
||||
if lowPrice <= state.buySLPrice {
|
||||
return &strat.ExitReq{
|
||||
Tag: "SL Hit",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否达到第二止盈目标
|
||||
if state.tp1Hit {
|
||||
highPrice := s.Env.High.Get(0)
|
||||
if highPrice >= state.buyTP2Price {
|
||||
return &strat.ExitReq{
|
||||
Tag: "TP2 Hit",
|
||||
Dirt: core.OdDirtLong,
|
||||
ExitRate: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空头订单检查
|
||||
if od.Short && state.inTrade && state.tradeDirection == -1 {
|
||||
highPrice := s.Env.High.Get(0)
|
||||
|
||||
// 检查是否触发止损
|
||||
if highPrice >= state.sellSLPrice {
|
||||
return &strat.ExitReq{
|
||||
Tag: "SL Hit",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否达到第二止盈目标
|
||||
if state.tp1Hit {
|
||||
lowPrice := s.Env.Low.Get(0)
|
||||
if lowPrice <= state.sellTP2Price {
|
||||
return &strat.ExitReq{
|
||||
Tag: "TP2 Hit",
|
||||
Dirt: core.OdDirtShort,
|
||||
ExitRate: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// checkTradingTime 检查当前时间是否在交易时间内
|
||||
func checkTradingTime(t time.Time, startHour, adjustedEndHour int,
|
||||
tradeOnMonday, tradeOnTuesday, tradeOnWednesday, tradeOnThursday, tradeOnFriday bool) bool {
|
||||
// 获取当前小时和星期几
|
||||
currentHour := t.Hour()
|
||||
currentDay := t.Weekday()
|
||||
|
||||
// 检查是否在交易小时内
|
||||
isTradingHour := false
|
||||
if startHour < adjustedEndHour {
|
||||
isTradingHour = currentHour >= startHour && currentHour < adjustedEndHour
|
||||
} else {
|
||||
isTradingHour = currentHour >= startHour || currentHour < adjustedEndHour
|
||||
}
|
||||
|
||||
// 检查是否在交易日内
|
||||
isTradingDay := false
|
||||
switch currentDay {
|
||||
case time.Monday:
|
||||
isTradingDay = tradeOnMonday
|
||||
case time.Tuesday:
|
||||
isTradingDay = tradeOnTuesday
|
||||
case time.Wednesday:
|
||||
isTradingDay = tradeOnWednesday
|
||||
case time.Thursday:
|
||||
isTradingDay = tradeOnThursday
|
||||
case time.Friday:
|
||||
isTradingDay = tradeOnFriday
|
||||
}
|
||||
|
||||
return isTradingHour && isTradingDay
|
||||
}
|
||||
|
||||
// updateStep 更新信号状态步骤
|
||||
func updateStep(state *State4067, fastEMA, slowEMA *ta.Series) {
|
||||
// 检查EMA交叉
|
||||
crossUp := fastEMA.Cross(slowEMA)
|
||||
crossDown := slowEMA.Cross(fastEMA)
|
||||
|
||||
// 更新状态步骤
|
||||
if (state.step == 0 || state.step == 4) && crossUp == 1 && fastEMA.Get(0) > state.higherEMA {
|
||||
state.step = 1
|
||||
}
|
||||
|
||||
if state.step == 3 && crossUp == 1 && fastEMA.Get(0) > state.higherEMA {
|
||||
state.step = 3
|
||||
}
|
||||
|
||||
if state.step == 2 && crossUp == 1 && fastEMA.Get(0) > state.higherEMA {
|
||||
state.step = 1
|
||||
}
|
||||
|
||||
if (state.step == 0 || state.step == 3) && crossDown == 1 && fastEMA.Get(0) < state.higherEMA {
|
||||
state.step = 2
|
||||
}
|
||||
|
||||
if state.step == 4 && crossDown == 1 && fastEMA.Get(0) < state.higherEMA {
|
||||
state.step = 4
|
||||
}
|
||||
|
||||
if state.step == 1 && crossDown == 1 && fastEMA.Get(0) < state.higherEMA {
|
||||
state.step = 2
|
||||
}
|
||||
}
|
||||
|
||||
// resetState 重置策略状态
|
||||
func resetState(state *State4067) {
|
||||
state.inTrade = false
|
||||
state.tradeDirection = 0
|
||||
state.entryPrice = math.NaN()
|
||||
state.tp1Hit = false
|
||||
state.buytp1Hit = false
|
||||
state.selltp1Hit = false
|
||||
state.buySLPrice = math.NaN()
|
||||
state.buyTP1Price = math.NaN()
|
||||
state.buyTP2Price = math.NaN()
|
||||
state.sellSLPrice = math.NaN()
|
||||
state.sellTP1Price = math.NaN()
|
||||
state.sellTP2Price = math.NaN()
|
||||
state.step = 0
|
||||
}
|
||||
134
llmjob/419.go
Normal file
134
llmjob/419.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
type FLIData struct {
|
||||
trendLine float64
|
||||
iTrend float64
|
||||
}
|
||||
|
||||
func J419(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
bbPeriod := pol.DefInt("bbPeriod", 38, core.PNorm(10, 50))
|
||||
bbDeviations := pol.Def("bbDeviations", 0.65, core.PNorm(0.5, 3.0))
|
||||
useATRFilter := pol.Def("useATRFilter", 0.0, core.PUniform(0, 1)) // 0=false, 1=true
|
||||
atrPeriod := pol.DefInt("atrPeriod", 5, core.PNorm(3, 20))
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: bbPeriod + 5, // 确保有足够数据计算指标
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
TimeFrames: "4h",
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的数据
|
||||
s.More = &FLIData{
|
||||
trendLine: 0.0,
|
||||
iTrend: 0.0,
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
data, _ := s.More.(*FLIData)
|
||||
|
||||
// 计算布林带
|
||||
bbMiddle := ta.SMA(e.Close, bbPeriod)
|
||||
stdDev := ta.StdDev(e.Close, bbPeriod)
|
||||
bbUpper := bbMiddle.Add(stdDev.Mul(bbDeviations))
|
||||
bbLower := bbMiddle.Sub(stdDev.Mul(bbDeviations))
|
||||
|
||||
// 获取当前价格
|
||||
closePrice := e.Close.Get(0)
|
||||
highPrice := e.High.Get(0)
|
||||
lowPrice := e.Low.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(closePrice) || math.IsNaN(highPrice) || math.IsNaN(lowPrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 确定布林带信号
|
||||
bbSignal := 0
|
||||
if closePrice > bbUpper.Get(0) {
|
||||
bbSignal = 1 // 看涨信号
|
||||
} else if closePrice < bbLower.Get(0) {
|
||||
bbSignal = -1 // 看跌信号
|
||||
}
|
||||
|
||||
// 计算趋势线
|
||||
prevTrendLine := data.trendLine
|
||||
var newTrendLine float64
|
||||
|
||||
if useATRFilter > 0.5 { // 启用ATR过滤器
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
atrValue := atr.Get(0)
|
||||
|
||||
switch bbSignal {
|
||||
case 1: // 看涨信号
|
||||
newTrendLine = lowPrice - atrValue
|
||||
if newTrendLine < prevTrendLine {
|
||||
newTrendLine = prevTrendLine
|
||||
}
|
||||
case -1: // 看跌信号
|
||||
newTrendLine = highPrice + atrValue
|
||||
if newTrendLine > prevTrendLine {
|
||||
newTrendLine = prevTrendLine
|
||||
}
|
||||
default: // 无信号
|
||||
newTrendLine = prevTrendLine
|
||||
}
|
||||
} else { // 不启用ATR过滤器
|
||||
switch bbSignal {
|
||||
case 1: // 看涨信号
|
||||
newTrendLine = lowPrice
|
||||
if newTrendLine < prevTrendLine {
|
||||
newTrendLine = prevTrendLine
|
||||
}
|
||||
case -1: // 看跌信号
|
||||
newTrendLine = highPrice
|
||||
if newTrendLine > prevTrendLine {
|
||||
newTrendLine = prevTrendLine
|
||||
}
|
||||
default: // 无信号
|
||||
newTrendLine = prevTrendLine
|
||||
}
|
||||
}
|
||||
|
||||
// 更新趋势线
|
||||
data.trendLine = newTrendLine
|
||||
|
||||
// 确定趋势方向
|
||||
prevITrend := data.iTrend
|
||||
var newITrend float64
|
||||
|
||||
if newTrendLine > prevTrendLine {
|
||||
newITrend = 1 // 上升趋势
|
||||
} else if newTrendLine < prevTrendLine {
|
||||
newITrend = -1 // 下降趋势
|
||||
} else {
|
||||
newITrend = prevITrend // 保持原趋势
|
||||
}
|
||||
|
||||
// 生成交易信号
|
||||
if prevITrend == -1 && newITrend == 1 {
|
||||
// 趋势从下跌转为上涨,产生买入信号
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "buy",
|
||||
})
|
||||
} else if prevITrend == 1 && newITrend == -1 {
|
||||
// 趋势从上涨转为下跌,产生卖出信号
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "sell",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 更新趋势方向
|
||||
data.iTrend = newITrend
|
||||
},
|
||||
}
|
||||
}
|
||||
110
llmjob/4217.go
Normal file
110
llmjob/4217.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func J4217(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
fastLength := pol.DefInt("fastLength", 9, core.PNorm(5, 20))
|
||||
maRate := pol.Def("maRate", 2.33, core.PNorm(1.5, 5))
|
||||
rsiLength := pol.DefInt("rsiLength", 14, core.PNorm(7, 21))
|
||||
rsiThreshold := pol.DefInt("rsiThreshold", 70, core.PNorm(60, 80))
|
||||
atrStopLoss := pol.Def("atrStopLoss", 2.0, core.PNorm(1.0, 4.0))
|
||||
atrPeriod := pol.DefInt("atrPeriod", 14, core.PNorm(7, 21))
|
||||
// 添加MACD参数配置
|
||||
macdFast := pol.DefInt("macdFast", 12, core.PNorm(8, 20))
|
||||
macdSlow := pol.DefInt("macdSlow", 26, core.PNorm(20, 40))
|
||||
macdSignal := pol.DefInt("macdSignal", 9, core.PNorm(5, 15))
|
||||
|
||||
// 计算慢速EMA周期
|
||||
slowLength := int(float64(fastLength) * maRate)
|
||||
|
||||
// 计算RSI超买超卖水平
|
||||
rsiOverboughtLevel := float64(rsiThreshold)
|
||||
rsiOversoldLevel := 100 - float64(rsiThreshold)
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: slowLength + atrPeriod, // 确保有足够的数据计算指标
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多开1个多单
|
||||
EachMaxShort: 1, // 最多开1个空单
|
||||
TimeFrames: "8h",
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
fastMA := ta.EMA(e.Close, fastLength)
|
||||
slowMA := ta.EMA(e.Close, slowLength)
|
||||
rsi := ta.RSI(e.Close, rsiLength)
|
||||
// 使用配置的MACD参数
|
||||
macdLine, signalLine := ta.MACD(e.Close, macdFast, macdSlow, macdSignal)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
||||
|
||||
// 获取最新值
|
||||
fastMAVal := fastMA.Get(0)
|
||||
slowMAVal := slowMA.Get(0)
|
||||
rsiVal := rsi.Get(0)
|
||||
atrVal := atr.Get(0)
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(fastMAVal) || math.IsNaN(slowMAVal) || math.IsNaN(rsiVal) || math.IsNaN(atrVal) {
|
||||
return
|
||||
}
|
||||
|
||||
// 判断趋势
|
||||
bullishTrend := fastMAVal > slowMAVal
|
||||
bearishTrend := fastMAVal < slowMAVal
|
||||
|
||||
// 生成交易信号
|
||||
longCondition := bullishTrend && rsiVal < rsiOverboughtLevel && macdLine.Cross(signalLine) == 1
|
||||
shortCondition := bearishTrend && rsiVal > rsiOversoldLevel && macdLine.Cross(signalLine) == -1
|
||||
|
||||
// 计算止损距离
|
||||
stopLossDistance := atrVal * atrStopLoss
|
||||
|
||||
// 执行交易操作
|
||||
if longCondition && len(s.LongOrders) == 0 {
|
||||
// 开多单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Short: false,
|
||||
StopLossVal: stopLossDistance,
|
||||
StopLossTag: "ATR Stop Loss",
|
||||
})
|
||||
}
|
||||
|
||||
if shortCondition && len(s.ShortOrders) == 0 {
|
||||
// 开空单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
StopLossVal: stopLossDistance,
|
||||
StopLossTag: "ATR Stop Loss",
|
||||
})
|
||||
}
|
||||
|
||||
// 平仓条件
|
||||
if len(s.LongOrders) > 0 && (bearishTrend || rsiVal > rsiOverboughtLevel) {
|
||||
// 平多单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Close Long",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
if len(s.ShortOrders) > 0 && (bullishTrend || rsiVal < rsiOversoldLevel) {
|
||||
// 平空单
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Close Short",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
113
llmjob/4225.go
Normal file
113
llmjob/4225.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
// StrategyMore 存储每个品种的策略状态
|
||||
type StrategyMore struct {
|
||||
LastBarNum float64 // 上次交易的K线编号
|
||||
}
|
||||
|
||||
// MultiEMACross 多重均线交叉与时间间隔整合策略
|
||||
func J4225(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调参数
|
||||
emaBasePeriod := pol.DefInt("emaBasePeriod", 4, core.PNorm(3, 15))
|
||||
emaMidRatio := pol.Def("emaMidRatio", 2.1, core.PNorm(1.5, 4))
|
||||
emaSlowRatio := pol.Def("emaSlowRatio", 10, core.PNorm(5, 20))
|
||||
timeInterval := pol.DefInt("timeInterval", 35, core.PNorm(10, 100))
|
||||
atrMultiplier := pol.Def("atrMultiplier", 1.9, core.PNorm(0.5, 5))
|
||||
|
||||
// 计算EMA周期
|
||||
emaMidPeriod := int(float64(emaBasePeriod) * emaMidRatio)
|
||||
emaSlowPeriod := int(float64(emaBasePeriod) * emaSlowRatio)
|
||||
|
||||
// 确定预热K线数量,取最慢EMA周期的2倍
|
||||
warmupNum := emaSlowPeriod * 2
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化每个品种的策略状态
|
||||
s.More = &StrategyMore{
|
||||
LastBarNum: -1, // 初始化为-1,表示尚未交易
|
||||
}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
m, _ := s.More.(*StrategyMore)
|
||||
|
||||
// 计算EMA指标
|
||||
emaFast := ta.EMA(e.Close, emaBasePeriod)
|
||||
emaMid := ta.EMA(e.Close, emaMidPeriod)
|
||||
emaSlow := ta.EMA(e.Close, emaSlowPeriod)
|
||||
|
||||
// 计算ATR用于动态止盈止损
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, 14)
|
||||
atrVal := atr.Get(0)
|
||||
|
||||
// 检查EMA交叉条件 - 修复重复计算问题
|
||||
fastCrossSlow := emaFast.Cross(emaSlow)
|
||||
midCrossSlow := emaMid.Cross(emaSlow)
|
||||
fastCrossSlowUp := fastCrossSlow == 1 // 快线上穿慢线
|
||||
midCrossSlowUp := midCrossSlow == 1 // 中线上穿慢线
|
||||
fastCrossSlowDown := fastCrossSlow == -1 // 快线下穿慢线
|
||||
midCrossSlowDown := midCrossSlow == -1 // 中线下穿慢线
|
||||
|
||||
// 生成交易信号
|
||||
buyCondition := fastCrossSlowUp && midCrossSlowUp
|
||||
sellCondition := fastCrossSlowDown && midCrossSlowDown
|
||||
|
||||
// 检查时间间隔是否满足要求
|
||||
currentBarNum := e.BarCount(0)
|
||||
barsSinceLastTrade := currentBarNum - m.LastBarNum
|
||||
timeElapsedOk := m.LastBarNum == -1 || barsSinceLastTrade >= float64(timeInterval)
|
||||
|
||||
// 如果有交易信号且时间间隔满足要求
|
||||
if (buyCondition || sellCondition) && timeElapsedOk {
|
||||
// 先平掉所有现有仓位
|
||||
if len(s.LongOrders) > 0 || len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "close_all",
|
||||
Dirt: core.OdDirtBoth,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 计算止盈止损价格
|
||||
stopLossDist := atrVal * atrMultiplier
|
||||
takeProfitDist := stopLossDist * 1.67 // 保持5:3的盈亏比
|
||||
|
||||
// 根据信号方向开仓
|
||||
if buyCondition {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
Short: false,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLossVal: stopLossDist,
|
||||
TakeProfitVal: takeProfitDist,
|
||||
StopLossTag: "stop_loss",
|
||||
TakeProfitTag: "take_profit",
|
||||
})
|
||||
m.LastBarNum = currentBarNum
|
||||
} else if sellCondition {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
OrderType: core.OrderTypeMarket,
|
||||
StopLossVal: stopLossDist,
|
||||
TakeProfitVal: takeProfitDist,
|
||||
StopLossTag: "stop_loss",
|
||||
TakeProfitTag: "take_profit",
|
||||
})
|
||||
m.LastBarNum = currentBarNum
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
156
llmjob/4267.go
Normal file
156
llmjob/4267.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J4267(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
shortMaLength := pol.DefInt("shortMaLength", 10, core.PNorm(5, 20))
|
||||
maRate := pol.Def("maRate", 1.8, core.PNorm(1.5, 4))
|
||||
rsiOverbought := pol.DefInt("rsiOverbought", 75, core.PNorm(60, 85))
|
||||
rsiOversold := pol.DefInt("rsiOversold", 25, core.PNorm(15, 40))
|
||||
bbMultiplier := pol.Def("bbMultiplier", 2.0, core.PNorm(1.5, 3.0))
|
||||
|
||||
// 计算长期均线周期
|
||||
longMaLength := int(float64(shortMaLength) * maRate)
|
||||
|
||||
// 确定预热数量,取所有指标中最大的周期
|
||||
warmupNum := longMaLength
|
||||
if warmupNum < 20 {
|
||||
warmupNum = 20
|
||||
}
|
||||
if warmupNum < 26 {
|
||||
warmupNum = 26
|
||||
}
|
||||
|
||||
return &strat.TradeStrat{
|
||||
Name: "Multi-Technical-Indicator-Synergistic-Trading-System",
|
||||
WarmupNum: warmupNum,
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多一个做多订单
|
||||
EachMaxShort: 1, // 最多一个做空订单
|
||||
RunTimeFrames: []string{"4h"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算移动平均线
|
||||
shortMa := ta.SMA(e.Close, shortMaLength)
|
||||
longMa := ta.SMA(e.Close, longMaLength)
|
||||
|
||||
// 计算RSI
|
||||
rsi := ta.RSI(e.Close, 14)
|
||||
|
||||
// 计算MACD
|
||||
macdLine, signalLine := ta.MACD(e.Close, 12, 26, 9)
|
||||
macdHist := macdLine.Sub(signalLine)
|
||||
|
||||
// 计算布林带
|
||||
bbUpper, _, bbLower := ta.BBANDS(e.Close, 20, bbMultiplier, bbMultiplier)
|
||||
|
||||
// 获取当前价格
|
||||
closePrice := e.Close.Get(0)
|
||||
if math.IsNaN(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检测主要交易信号:MA交叉
|
||||
maCross := shortMa.Cross(longMa)
|
||||
buySignal := maCross == 1 // 短期均线上穿长期均线
|
||||
sellSignal := maCross == -1 // 短期均线下穿长期均线
|
||||
|
||||
// 检测辅助交易信号
|
||||
rsiVal := rsi.Get(0)
|
||||
macdHistVal := macdHist.Get(0)
|
||||
bbLowerVal := bbLower.Get(0)
|
||||
bbUpperVal := bbUpper.Get(0)
|
||||
|
||||
additionalBuySignal := !math.IsNaN(rsiVal) && !math.IsNaN(macdHistVal) && !math.IsNaN(bbLowerVal) &&
|
||||
rsiVal < float64(rsiOversold) && macdHistVal > 0 && closePrice < bbLowerVal
|
||||
|
||||
additionalSellSignal := !math.IsNaN(rsiVal) && !math.IsNaN(macdHistVal) && !math.IsNaN(bbUpperVal) &&
|
||||
rsiVal > float64(rsiOverbought) && macdHistVal < 0 && closePrice > bbUpperVal
|
||||
|
||||
// 执行交易策略
|
||||
if buySignal {
|
||||
// 平掉所有做空订单
|
||||
if len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "MA_Sell_Close",
|
||||
Dirt: core.OdDirtShort,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 开做多订单
|
||||
if len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "MA_Buy",
|
||||
})
|
||||
}
|
||||
} else if sellSignal {
|
||||
// 平掉所有做多订单
|
||||
if len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "MA_Buy_Close",
|
||||
Dirt: core.OdDirtLong,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 平掉所有做空订单
|
||||
if len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "MA_Sell_Close",
|
||||
Dirt: core.OdDirtShort,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 开做空订单
|
||||
if len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "MA_Sell",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助信号处理(仅在主要信号未触发时执行)
|
||||
if additionalBuySignal && !buySignal && len(s.LongOrders) == 0 {
|
||||
// 平掉所有做空订单
|
||||
if len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Aux_Sell_Close",
|
||||
Dirt: core.OdDirtShort,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 开做多订单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Aux_Buy",
|
||||
})
|
||||
} else if additionalSellSignal && !sellSignal && len(s.ShortOrders) == 0 {
|
||||
// 平掉所有做多订单
|
||||
if len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "Aux_Buy_Close",
|
||||
Dirt: core.OdDirtLong,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 开做空订单
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Aux_Sell",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
115
llmjob/4316.go
Normal file
115
llmjob/4316.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func J4316(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
ema1Length := pol.DefInt("ema1_length", 19, core.PNorm(5, 50))
|
||||
ema2Length := pol.DefInt("ema2_length", 50, core.PNorm(20, 100))
|
||||
rsiOverbought := pol.DefInt("rsi_overbought", 70, core.PNorm(60, 80))
|
||||
rsiOversold := pol.DefInt("rsi_oversold", 30, core.PNorm(20, 40))
|
||||
macdFast := pol.DefInt("macd_fast", 12, core.PNorm(8, 16))
|
||||
macdSlow := pol.DefInt("macd_slow", 26, core.PNorm(20, 32))
|
||||
macdSignal := pol.DefInt("macd_signal", 9, core.PNorm(6, 12))
|
||||
atrLength := pol.DefInt("atr_length", 14, core.PNorm(7, 21))
|
||||
atrMult := pol.Def("atr_mult", 1.85, core.PNorm(1.5, 5))
|
||||
useVolume := pol.Def("use_volume", 1.0, core.PNorm(0.0, 1.0)) > 0.5 // 修复:使用Def代替DefBool
|
||||
volumeMult := pol.Def("volume_mult", 2.0, core.PNorm(1.0, 4.0))
|
||||
capitalRisk := pol.Def("capital_risk", 2.0, core.PNorm(0.5, 5.0)) / 100
|
||||
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: ema2Length + 20, // 确保所有指标都有足够数据
|
||||
HedgeOff: true, // 单向持仓模式
|
||||
EachMaxLong: 1, // 最多一个多头订单
|
||||
EachMaxShort: 1, // 最多一个空头订单
|
||||
DrawDownExit: true, // 启用回撤止盈
|
||||
RunTimeFrames: []string{"15m"},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
|
||||
// 计算技术指标
|
||||
ema1 := ta.EMA(e.Close, ema1Length)
|
||||
ema2 := ta.EMA(e.Close, ema2Length)
|
||||
rsi := ta.RSI(e.Close, 14)
|
||||
|
||||
// 计算MACD
|
||||
macdLine := ta.EMA(e.Close, macdFast).Sub(ta.EMA(e.Close, macdSlow))
|
||||
macdSignalLine := ta.EMA(macdLine, macdSignal)
|
||||
macdHist := macdLine.Sub(macdSignalLine)
|
||||
|
||||
// 计算ATR
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, atrLength)
|
||||
|
||||
// 计算成交量过滤条件
|
||||
volumeMA := ta.SMA(e.Volume, 20)
|
||||
volumeFilter := e.Volume.Get(0) > volumeMA.Get(0)*volumeMult
|
||||
|
||||
// 检查数据有效性
|
||||
if math.IsNaN(ema1.Get(0)) || math.IsNaN(ema2.Get(0)) ||
|
||||
math.IsNaN(rsi.Get(0)) || math.IsNaN(macdHist.Get(0)) ||
|
||||
math.IsNaN(atr.Get(0)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 信号过滤 - 优化重复计算
|
||||
crossResult := ema1.Cross(ema2)
|
||||
emaCrossUp := crossResult == 1 // 快速EMA上穿慢速EMA
|
||||
emaCrossDown := crossResult == -1 // 快速EMA下穿慢速EMA
|
||||
rsiFilterLong := rsi.Get(0) > float64(rsiOversold) // RSI高于超卖水平
|
||||
rsiFilterShort := rsi.Get(0) < float64(rsiOverbought) // RSI低于超买水平
|
||||
macdFilterLong := macdHist.Get(0) > 0 // MACD柱状图为正
|
||||
macdFilterShort := macdHist.Get(0) < 0 // MACD柱状图为负
|
||||
volumeOk := !useVolume || volumeFilter // 成交量条件满足
|
||||
|
||||
// 生成交易信号
|
||||
longEntry := emaCrossUp && rsiFilterLong && macdFilterLong && volumeOk
|
||||
shortEntry := emaCrossDown && rsiFilterShort && macdFilterShort && volumeOk
|
||||
|
||||
// 风险管理计算
|
||||
atrValue := atr.Get(0) // 修复:删除未使用的closePrice变量
|
||||
|
||||
// 计算交易规模 - 修复基于ATR的交易规模计算
|
||||
riskAmount := s.Strat.GetStakeAmount(s) * capitalRisk
|
||||
tradeSize := riskAmount / math.Max(atrValue*atrMult, 1.0)
|
||||
|
||||
// 计算追踪止损价格
|
||||
|
||||
// 执行交易
|
||||
if longEntry && len(s.LongOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Long",
|
||||
Amount: tradeSize,
|
||||
// 修复:移除不存在的TrailOffset字段
|
||||
})
|
||||
}
|
||||
|
||||
if shortEntry && len(s.ShortOrders) == 0 {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Short",
|
||||
Short: true,
|
||||
Amount: tradeSize,
|
||||
// 修复:移除不存在的TrailOffset字段
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 实现回撤止盈逻辑
|
||||
GetDrawDownExitRate: func(s *strat.StratJob, od *ormo.InOutOrder, maxChg float64) float64 {
|
||||
// 根据最大盈利回撤比例决定止盈
|
||||
if maxChg > 0.1 { // 盈利超过10%
|
||||
return 0.5 // 回撤50%止盈
|
||||
} else if maxChg > 0.05 { // 盈利超过5%
|
||||
return 0.7 // 回撤70%止盈
|
||||
}
|
||||
return 0 // 不设置回撤止盈
|
||||
},
|
||||
}
|
||||
}
|
||||
268
llmjob/4513.go
Normal file
268
llmjob/4513.go
Normal file
@@ -0,0 +1,268 @@
|
||||
// 修复后的banbot完整策略代码
|
||||
package llmjob
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
// State4513 存储每个品种的策略状态
|
||||
type State4513 struct {
|
||||
dir int // 当前趋势方向,1=上升趋势,-1=下降趋势
|
||||
longStop float64 // 多头止损价格
|
||||
shortStop float64 // 空头止损价格
|
||||
longStopPrev float64 // 前一个多头止损价格
|
||||
shortStopPrev float64 // 前一个空头止损价格
|
||||
trailingStop float64 // 当前跟踪止损价格
|
||||
}
|
||||
|
||||
// Demo 创建并返回策略实例
|
||||
func J4513(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
// 定义可调超参数
|
||||
bbLength := pol.DefInt("BBLength", 100, core.PNorm(20, 200))
|
||||
stdDev := pol.Def("StdDev", 10, core.PNorm(1, 20))
|
||||
useMovingAverage := pol.Def("useMovingAverage", 1, core.PUniform(0, 1)) // 1=true, 0=false
|
||||
maType := pol.DefInt("maType", 3, core.PUniform(0, 5)) // 0=ema, 1=sma, 2=hma, 3=rma, 4=vwma, 5=wma
|
||||
lookbackPeriod := pol.DefInt("lookbackPeriod", 22, core.PNorm(5, 50))
|
||||
atrMaType := pol.DefInt("atrMaType", 2, core.PUniform(0, 5)) // 0=ema, 1=sma, 2=hma, 3=rma, 4=vwma, 5=wma
|
||||
atrLength := pol.DefInt("atrLength", 10, core.PNorm(5, 30))
|
||||
atrMult := pol.Def("atrMult", 4.2, core.PNorm(1, 10))
|
||||
wicks := pol.Def("wicks", 0, core.PUniform(0, 1)) // 1=true, 0=false
|
||||
considerYearlyHighLow := pol.Def("considerYearlyHighLow", 0, core.PUniform(0, 1)) // 1=true, 0=false
|
||||
considerNewLongTermHighLows := pol.Def("considerNewLongTermHighLows", 0, core.PUniform(0, 1)) // 1=true, 0=false
|
||||
tradeDirection := pol.DefInt("tradeDirection", 0, core.PUniform(0, 2)) // 0=all, 1=long, 2=short
|
||||
|
||||
// 固定参数
|
||||
shortHighLowPeriod := 100
|
||||
longHighLowPeriod := 200
|
||||
|
||||
return &strat.TradeStrat{
|
||||
Name: "Bollinger Percentage Bands",
|
||||
WarmupNum: int(math.Max(float64(bbLength), float64(longHighLowPeriod))) + 10,
|
||||
HedgeOff: true, // 单向持仓
|
||||
EachMaxLong: 1,
|
||||
EachMaxShort: 1,
|
||||
RunTimeFrames: []string{"8h"},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
// 初始化策略状态
|
||||
state := &State4513{
|
||||
dir: 1,
|
||||
longStop: 0,
|
||||
shortStop: 0,
|
||||
}
|
||||
s.More = state
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
state, _ := s.More.(*State4513)
|
||||
|
||||
// 1. 计算布林带指标
|
||||
// 修复错误1:布林带计算参数错误
|
||||
// 原始策略使用bb(close, BBLength, StdDev),其中StdDev是标准差倍数
|
||||
// 修改为使用正确的参数格式,提供上下轨的标准差倍数
|
||||
upperClose, _, lowerClose := ta.BBANDS(e.Close, bbLength, stdDev, stdDev)
|
||||
upperHigh, _, lowerHigh := ta.BBANDS(e.High, bbLength, stdDev, stdDev)
|
||||
upperLow, _, lowerLow := ta.BBANDS(e.Low, bbLength, stdDev, stdDev)
|
||||
|
||||
// 2. 计算布林带百分比
|
||||
percentBClose := e.Close.Sub(lowerClose).Div(upperClose.Sub(lowerClose)).Mul(100)
|
||||
percentBHigh := e.High.Sub(lowerHigh).Div(upperHigh.Sub(lowerHigh)).Mul(100)
|
||||
percentBLow := e.Low.Sub(lowerLow).Div(upperLow.Sub(lowerLow)).Mul(100)
|
||||
|
||||
// 3. 应用移动平均线平滑
|
||||
var newClose, newHigh, newLow *ta.Series
|
||||
if useMovingAverage > 0.5 {
|
||||
percentBMAClose := getMovingAverage(percentBClose, maType, lookbackPeriod)
|
||||
percentBMAHigh := getMovingAverage(percentBHigh, maType, lookbackPeriod)
|
||||
percentBMALow := getMovingAverage(percentBLow, maType, lookbackPeriod)
|
||||
newClose = percentBMAClose
|
||||
newHigh = percentBMAHigh
|
||||
newLow = percentBMALow
|
||||
} else {
|
||||
newClose = percentBClose
|
||||
newHigh = percentBHigh
|
||||
newLow = percentBLow
|
||||
}
|
||||
|
||||
// 4. 计算ATR和止损
|
||||
closePrev := newClose.Back(1)
|
||||
highPrev := newHigh.Back(1)
|
||||
lowPrev := newLow.Back(1)
|
||||
|
||||
trueRange := newHigh.Max(closePrev).Sub(newLow.Min(closePrev))
|
||||
averageTrueRange := getMovingAverage(trueRange, atrMaType, atrLength)
|
||||
atr := averageTrueRange.Mul(atrMult)
|
||||
|
||||
longStopVal := newClose.Sub(atr)
|
||||
shortStopVal := newClose.Add(atr)
|
||||
|
||||
// 修复错误2:止损更新逻辑不完整
|
||||
// 原始策略使用nz(longStop[1], longStop)处理前一个止损值不存在的情况
|
||||
// 修改为正确处理初始状态,并确保止损只能向有利方向移动
|
||||
if state.longStopPrev == 0 {
|
||||
state.longStopPrev = longStopVal.Get(0)
|
||||
}
|
||||
|
||||
if state.shortStopPrev == 0 {
|
||||
state.shortStopPrev = shortStopVal.Get(0)
|
||||
}
|
||||
|
||||
// 修复错误3:使用Series运算创建常量而不是ta.Const
|
||||
if wicks > 0.5 {
|
||||
if lowPrev.Get(0) > state.longStopPrev {
|
||||
// 使用Max确保止损只能向有利方向移动
|
||||
longStopVal = longStopVal.Max(newClose.Sub(newClose).Add(state.longStopPrev))
|
||||
}
|
||||
} else {
|
||||
if closePrev.Get(0) > state.longStopPrev {
|
||||
longStopVal = longStopVal.Max(newClose.Sub(newClose).Add(state.longStopPrev))
|
||||
}
|
||||
}
|
||||
|
||||
if wicks > 0.5 {
|
||||
if highPrev.Get(0) < state.shortStopPrev {
|
||||
shortStopVal = shortStopVal.Min(newClose.Sub(newClose).Add(state.shortStopPrev))
|
||||
}
|
||||
} else {
|
||||
if closePrev.Get(0) < state.shortStopPrev {
|
||||
shortStopVal = shortStopVal.Min(newClose.Sub(newClose).Add(state.shortStopPrev))
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 判断趋势方向
|
||||
// 修复错误4:趋势方向判断可能存在边界条件处理错误
|
||||
// 原始策略使用三元运算符,逻辑清晰且紧凑
|
||||
// 修改为更接近原始策略的实现
|
||||
if state.dir == -1 {
|
||||
if wicks > 0.5 {
|
||||
if newHigh.Get(0) > state.shortStopPrev {
|
||||
state.dir = 1
|
||||
}
|
||||
} else {
|
||||
if newClose.Get(0) > state.shortStopPrev {
|
||||
state.dir = 1
|
||||
}
|
||||
}
|
||||
} else if state.dir == 1 {
|
||||
if wicks > 0.5 {
|
||||
if newLow.Get(0) < state.longStopPrev {
|
||||
state.dir = -1
|
||||
}
|
||||
} else {
|
||||
if newClose.Get(0) < state.longStopPrev {
|
||||
state.dir = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.dir == 1 {
|
||||
state.trailingStop = longStopVal.Get(0)
|
||||
} else {
|
||||
state.trailingStop = shortStopVal.Get(0)
|
||||
}
|
||||
|
||||
// 6. 判断辅助条件
|
||||
yearlyHighCondition, yearlyLowCondition := getYearlyHighLowCondition(e, considerYearlyHighLow > 0.5)
|
||||
newHighS, newLowS := calculateNewHighLows(e, shortHighLowPeriod, longHighLowPeriod, considerNewLongTermHighLows > 0.5)
|
||||
|
||||
// 7. 生成交易信号
|
||||
buyCondition := state.dir == 1 && yearlyHighCondition && newHighS
|
||||
exitBuyCondition := state.dir == -1
|
||||
sellCondition := state.dir == -1 && yearlyLowCondition && newLowS
|
||||
exitSellCondition := state.dir == 1
|
||||
|
||||
// 8. 执行交易
|
||||
if buyCondition && (tradeDirection == 0 || tradeDirection == 1) {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Buy",
|
||||
Short: false,
|
||||
})
|
||||
}
|
||||
|
||||
if exitBuyCondition && len(s.LongOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "ExitBuy",
|
||||
Dirt: core.OdDirtLong,
|
||||
})
|
||||
}
|
||||
|
||||
if sellCondition && (tradeDirection == 0 || tradeDirection == 2) {
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "Sell",
|
||||
Short: true,
|
||||
})
|
||||
}
|
||||
|
||||
if exitSellCondition && len(s.ShortOrders) > 0 {
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: "ExitSell",
|
||||
Dirt: core.OdDirtShort,
|
||||
})
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
state.longStop = longStopVal.Get(0)
|
||||
state.shortStop = shortStopVal.Get(0)
|
||||
state.longStopPrev = state.longStop
|
||||
state.shortStopPrev = state.shortStop
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getMovingAverage 根据类型获取移动平均线
|
||||
func getMovingAverage(source *ta.Series, maType int, length int) *ta.Series {
|
||||
switch maType {
|
||||
case 0: // ema
|
||||
return ta.EMA(source, length)
|
||||
case 1: // sma
|
||||
return ta.SMA(source, length)
|
||||
case 2: // hma
|
||||
return ta.HMA(source, length)
|
||||
case 3: // rma
|
||||
return ta.RMA(source, length)
|
||||
case 4: // vwma - 在Banbot中可能没有直接对应,使用SMA代替
|
||||
return ta.SMA(source, length)
|
||||
case 5: // wma
|
||||
return ta.WMA(source, length)
|
||||
default:
|
||||
return ta.SMA(source, length)
|
||||
}
|
||||
}
|
||||
|
||||
// calculateNewHighLows 计算新的高低点条件
|
||||
func calculateNewHighLows(e *ta.BarEnv, shortPeriod, longPeriod int, consider bool) (bool, bool) {
|
||||
shortHigh := ta.Highest(e.High, shortPeriod)
|
||||
longHigh := ta.Highest(e.High, longPeriod)
|
||||
shortLow := ta.Lowest(e.Low, shortPeriod)
|
||||
longLow := ta.Lowest(e.Low, longPeriod)
|
||||
|
||||
newHigh := true
|
||||
newLow := true
|
||||
|
||||
if consider {
|
||||
newHigh = math.Abs(shortHigh.Get(0)-longHigh.Get(0)) < 1e-10
|
||||
newLow = math.Abs(shortLow.Get(0)-longLow.Get(0)) < 1e-10
|
||||
}
|
||||
|
||||
return newHigh, newLow
|
||||
}
|
||||
|
||||
// getYearlyHighLowCondition 获取年度高低点条件
|
||||
func getYearlyHighLowCondition(e *ta.BarEnv, consider bool) (bool, bool) {
|
||||
if !consider {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// 在Banbot中,我们无法直接获取年度数据,所以简化处理
|
||||
// 使用较长期的高低点来近似年度高低点
|
||||
yearlyHigh := ta.Highest(e.High, 365)
|
||||
yearlyLow := ta.Lowest(e.Low, 365)
|
||||
|
||||
yearlyHighCondition := e.Close.Get(0) > yearlyHigh.Get(0)
|
||||
yearlyLowCondition := e.Close.Get(0) < yearlyLow.Get(0)
|
||||
|
||||
return yearlyHighCondition, yearlyLowCondition
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user