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

279 lines
9.7 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.

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
}
}
},
}
}