279 lines
9.7 KiB
Go
279 lines
9.7 KiB
Go
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
|
||
}
|
||
}
|
||
},
|
||
}
|
||
}
|