526 lines
15 KiB
Go
526 lines
15 KiB
Go
// 修复后的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)
|
||
}
|
||
},
|
||
}
|
||
}
|