Files
banstagy/llmjob/1525.go
2026-08-24 20:53:21 +08:00

526 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 修复后的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)
}
},
}
}