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

278
llmjob/1665.go Normal file
View 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
}
}
},
}
}