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

525
llmjob/1525.go Normal file
View 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)
}
},
}
}