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" ) type HullBusterState struct { // 交易状态 entryPrice float64 stopPrice float64 lastLevel float64 maeLevel float64 pyramidCount int pyramidWaveID int entryTrendDir int consecLosses int // 交叉跟踪 riseALCrossPrice float64 riseALNetChg float64 fallALCrossPrice float64 fallALNetChg float64 riseALBarsSince int fallALBarsSince int // EMA交叉跟踪 riseEMCrossPrice float64 riseEMNetChg float64 fallEMCrossPrice float64 fallEMNetChg float64 riseEMBarsSince int fallEMBarsSince int // ZScore跟踪 riseZSSince int fallZSSince int riseZSCount int fallZSCount int riseZSHigh float64 fallZSLow float64 aboveZSSince int belowZSSince int aboveZSCount int belowZSCount int // 事件状态 htfEventHigh float64 htfEventLow float64 lastHTFEvent int impulseWaveDir int impulseWaveID int // 状态标志 nascentEntry bool hemiTransit bool feeRise bool feeFall bool pullback bool } func J7699(pol *config.RunPolicyConfig) *strat.TradeStrat { // 可调参数定义 fastCrossLen := pol.DefInt("fastCrossLen", 9, core.PNorm(5, 20)) smaCrossRate := pol.Def("smaCrossRate", 2.6, core.PNorm(1.5, 5.0)) trendDEMALen := pol.DefInt("trendDEMALen", 200, core.PNorm(50, 300)) fastAlmaLen := pol.DefInt("fastAlmaLen", 10, core.PNorm(5, 30)) almaLinregRate := pol.Def("almaLinregRate", 3.0, core.PNorm(2.0, 7.0)) // 固定参数 slowCrossLen := int(float64(fastCrossLen) * smaCrossRate) slowLinRegLen := int(float64(fastAlmaLen) * almaLinregRate) fastLinRegLen := 30 fastEMALen := 8 stochastLen := 14 minProfit := 0.50 stopOffset := 0.50 maxAddOns := 0 pyramidSpan := 0.0 tickScaler := 10.0 chgDivisor := 1000.0 // 计算所需预热长度 warmupNum := fastCrossLen if slowCrossLen > warmupNum { warmupNum = slowCrossLen } if trendDEMALen > warmupNum { warmupNum = trendDEMALen } if fastAlmaLen > warmupNum { warmupNum = fastAlmaLen } if slowLinRegLen > warmupNum { warmupNum = slowLinRegLen } if fastLinRegLen > warmupNum { warmupNum = fastLinRegLen } if fastEMALen > warmupNum { warmupNum = fastEMALen } if stochastLen > warmupNum { warmupNum = stochastLen } warmupNum += 100 // 为ZScore等计算预留空间 return &strat.TradeStrat{ WarmupNum: warmupNum, HedgeOff: true, // 单向持仓 TimeFrames: "8h", OnStartUp: func(s *strat.StratJob) { s.More = &HullBusterState{} }, OnPairInfos: func(s *strat.StratJob) []*strat.PairSub { // 需要两个高时间框架:信号周期和趋势周期 return []*strat.PairSub{ {"_cur_", "1h", warmupNum}, // 信号周期 {"_cur_", "4h", warmupNum}, // 趋势周期 } }, OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair string, tf string) { // 处理高时间框架数据 if tf == "1h" { // 将1h数据存储到环境中 s.Env.Data.Store("_cur_1h", e) } else if tf == "4h" { // 将4h数据存储到环境中 s.Env.Data.Store("_cur_4h", e) } }, OnBar: func(s *strat.StratJob) { e := s.Env state, _ := s.More.(*HullBusterState) // 当前周期指标计算 fastCross := ta.SMA(e.Close, fastCrossLen) slowCross := ta.SMA(e.Close, slowCrossLen) fastEMA := ta.EMA(e.Close, fastEMALen) // 使用Stochastic指标而非KDJ fastK := Stochastic(e.High, e.Low, e.Close, stochastLen) slowD := ta.SMA(fastK, 3) // 交叉信号 - 只计算一次 crossValue := fastCross.Cross(slowCross) crossRise := crossValue == 1 crossFall := crossValue == -1 // 从高时间框架获取数据 htfSignalData, _ := s.Env.Data.Load("_cur_1h") htfTrendData, _ := s.Env.Data.Load("_cur_4h") if htfSignalData == nil || htfTrendData == nil { return } signalEnv := htfSignalData.(*ta.BarEnv) trendEnv := htfTrendData.(*ta.BarEnv) // HTF指标 slowLinReg := ta.LinReg(trendEnv.Close, slowLinRegLen) fastLinReg := ta.LinReg(signalEnv.Close, fastLinRegLen) fastAlma := ta.ALMA(signalEnv.Close, fastAlmaLen, 6.0, 0.85) trendDEMA := DEMA(trendEnv.Close, trendDEMALen) // ZScore计算 almaMean := ta.SMA(fastAlma, 100) almaMarks := fastAlma.Sub(almaMean) almaStdDev := ta.StdDev(almaMarks, 100) zScore := almaMarks.Div(almaStdDev) // HTF Stochastic htfFastK := Stochastic(trendEnv.High, trendEnv.Low, trendEnv.Close, stochastLen) htfDLine := ta.SMA(htfFastK, 3) htfSlowD := ta.SMA(htfDLine, 3) // 信号条件 - 使用Get(1)获取前一根K线值以对齐HTF数据 closePrice := e.Close.Get(0) trendDEMAVal := trendDEMA.Get(1) fastAlmaVal := fastAlma.Get(1) fastLinRegVal := fastLinReg.Get(1) slowLinRegVal := slowLinReg.Get(1) zScoreVal := zScore.Get(1) // 趋势信号 htfSignalRise := fastAlmaVal > fastLinRegVal htfSignalFall := fastAlmaVal < fastLinRegVal htfFastAboveTrend := fastAlmaVal > trendDEMAVal && fastLinRegVal > trendDEMAVal htfFastBelowTrend := fastAlmaVal < trendDEMAVal && fastLinRegVal < trendDEMAVal // 计算ALMA事件 updateALEvents(state, fastAlmaVal, fastLinRegVal, closePrice, tickScaler, chgDivisor) // 计算EMA事件 fastEMAVal := fastEMA.Get(0) updateEMEvents(state, fastEMAVal, fastAlmaVal, closePrice, tickScaler, chgDivisor) // 更新ZScore状态 updateZScoreState(state, zScoreVal, closePrice) // 更新脉冲波方向 waveDirNow := 0 if fastLinRegVal > slowLinRegVal { waveDirNow = 1 } else if fastLinRegVal < slowLinRegVal { waveDirNow = -1 } if waveDirNow != state.impulseWaveDir { state.impulseWaveDir = waveDirNow state.impulseWaveID++ } // 计算事件 riseALEvent := checkRiseEvent(state, 10.0, 0.1) fallALEvent := checkFallEvent(state, 10.0, 0.1) riseEMEvent := checkRiseEMEvent(state, 3.0, 0.8) fallEMEvent := checkFallEMEvent(state, 4.0, 0.8) // Stochastic事件 fullFanCone := fastEMAVal > fastAlmaVal && fastAlmaVal > fastLinRegVal && fastLinRegVal > slowLinRegVal downFanCone := fastEMAVal < fastAlmaVal && fastAlmaVal < fastLinRegVal && fastLinRegVal < slowLinRegVal partialTrendRise := fastEMAVal > fastAlmaVal && fastAlmaVal > slowLinRegVal && fastLinRegVal > slowLinRegVal partialTrendFall := fastEMAVal < fastAlmaVal && fastAlmaVal < slowLinRegVal && fastLinRegVal < slowLinRegVal durableTrendRise := partialTrendRise && slowLinRegVal > trendDEMAVal durableTrendFall := partialTrendFall && slowLinRegVal < trendDEMAVal riseStochas := (htfDLine.Get(1) > 80 && fullFanCone) || (htfSlowD.Get(1) > 90 && durableTrendRise) || (htfDLine.Get(1) > 90 && partialTrendRise && state.htfEventHigh > 0 && closePrice > state.htfEventHigh) fallStochas := (htfDLine.Get(1) < 20 && downFanCone) || (htfSlowD.Get(1) < 10 && durableTrendFall) || (htfDLine.Get(1) < 10 && partialTrendFall && state.htfEventLow > 0 && closePrice < state.htfEventLow) // 更新HTF事件高低点 if riseALEvent { state.htfEventHigh = closePrice state.lastHTFEvent = 1 } if fallALEvent { state.htfEventLow = closePrice state.lastHTFEvent = -1 } // Nascent Entry条件 htfNascentRise := fastAlmaVal > slowLinRegVal && fastLinRegVal > slowLinRegVal && slowLinRegVal < trendDEMAVal htfNascentFall := fastAlmaVal < slowLinRegVal && fastLinRegVal < slowLinRegVal && slowLinRegVal > trendDEMAVal // 检查是否有持仓 positionDir := 0 if len(s.LongOrders) > 0 { positionDir = 1 } else if len(s.ShortOrders) > 0 { positionDir = -1 } alreadyIn := positionDir != 0 // 计算盈利状态 var currentOrder *ormo.InOutOrder if len(s.LongOrders) > 0 { currentOrder = s.LongOrders[0] } else if len(s.ShortOrders) > 0 { currentOrder = s.ShortOrders[0] } profitable := false if currentOrder != nil { profitAmount := currentOrder.ProfitRate * currentOrder.Leverage * state.entryPrice profitable = profitAmount >= minProfit } // 更新ZScore退出条件 if alreadyIn { if zScoreVal > 5.0 { state.feeRise = true } if zScoreVal < -5.0 { state.feeFall = true } } // 入场逻辑(包含Nascent Entry) if !alreadyIn { if crossRise { longNascent := false if closePrice <= trendDEMAVal && htfNascentRise { longNascent = true state.nascentEntry = true } if closePrice > trendDEMAVal || longNascent { stopPrice := e.Low.Get(0) - stopOffset s.OpenOrder(&strat.EnterReq{ Tag: "WBHDrive-L", Short: false, StopLoss: stopPrice, StopLossTag: "stop_loss", }) state.entryPrice = closePrice state.stopPrice = stopPrice state.lastLevel = closePrice state.entryTrendDir = 1 if !longNascent { state.nascentEntry = false } } } else if crossFall { shortNascent := false if closePrice >= trendDEMAVal && htfNascentFall { shortNascent = true state.nascentEntry = true } if closePrice < trendDEMAVal || shortNascent { stopPrice := e.High.Get(0) + stopOffset s.OpenOrder(&strat.EnterReq{ Tag: "WBHDrive-S", Short: true, StopLoss: stopPrice, StopLossTag: "stop_loss", }) state.entryPrice = closePrice state.stopPrice = stopPrice state.lastLevel = closePrice state.entryTrendDir = -1 if !shortNascent { state.nascentEntry = false } } } } // 出场逻辑 if alreadyIn && profitable { shouldClose := false if positionDir == 1 { // 多头 // 交叉退出 if htfSignalFall { shouldClose = true } // 信号退出 if htfSignalRise && riseALEvent && state.feeRise { shouldClose = true } // 事件退出 if riseEMEvent && fastEMAVal > fastAlmaVal && htfFastAboveTrend { shouldClose = true } // Stochastic退出 if riseStochas { shouldClose = true } } else if positionDir == -1 { // 空头 // 交叉退出 if htfSignalRise { shouldClose = true } // 信号退出 if htfSignalFall && fallALEvent && state.feeFall { shouldClose = true } // 事件退出 if fallEMEvent && fastEMAVal < fastAlmaVal && htfFastBelowTrend { shouldClose = true } // Stochastic退出 if fallStochas { shouldClose = true } } if shouldClose { if positionDir == 1 { s.CloseOrders(&strat.ExitReq{ Tag: "exit_long", Dirt: core.OdDirtLong, }) } else { s.CloseOrders(&strat.ExitReq{ Tag: "exit_short", Dirt: core.OdDirtShort, }) } // 重置状态 if profitable { state.consecLosses = 0 } else { state.consecLosses++ } state.entryPrice = 0 state.stopPrice = 0 state.lastLevel = 0 state.pyramidCount = 0 state.feeRise = false state.feeFall = false } } // 完整的金字塔加仓逻辑 if alreadyIn && maxAddOns > 0 && state.consecLosses < maxAddOns { // 检测pullback条件 if positionDir == 1 && slowD.Get(0) < 20 { if fallALEvent || fallStochas { state.pullback = true } } else if positionDir == -1 && slowD.Get(0) > 80 { if riseALEvent || riseStochas { state.pullback = true } } shouldPyramid := false if positionDir == 1 { // 完整的多头金字塔逻辑 exceedingSpan := pyramidSpan > 0 && (state.lastLevel-closePrice) > pyramidSpan if (crossRise && closePrice > trendDEMAVal && htfFastAboveTrend && closePrice > state.lastLevel && state.pullback) || (closePrice < state.lastLevel && exceedingSpan && closePrice > state.entryPrice && (fallALEvent || fallEMEvent || fallStochas)) || (closePrice < state.lastLevel && exceedingSpan && closePrice < state.entryPrice && fallALEvent && fallStochas && state.impulseWaveID > state.pyramidWaveID) { shouldPyramid = true } } else if positionDir == -1 { // 完整的空头金字塔逻辑 exceedingSpan := pyramidSpan > 0 && (closePrice-state.lastLevel) > pyramidSpan if (crossFall && closePrice < trendDEMAVal && htfFastBelowTrend && closePrice < state.lastLevel && state.pullback) || (closePrice > state.lastLevel && exceedingSpan && closePrice < state.entryPrice && (riseALEvent || riseEMEvent || riseStochas)) || (closePrice > state.lastLevel && exceedingSpan && closePrice > state.entryPrice && riseALEvent && riseStochas && state.impulseWaveID > state.pyramidWaveID) { shouldPyramid = true } } if shouldPyramid { if positionDir == 1 { stopPrice := e.Low.Get(0) - stopOffset s.OpenOrder(&strat.EnterReq{ Tag: "WBHDrive-L-Pyramid", Short: false, StopLoss: stopPrice, StopLossTag: "stop_loss", }) } else { stopPrice := e.High.Get(0) + stopOffset s.OpenOrder(&strat.EnterReq{ Tag: "WBHDrive-S-Pyramid", Short: true, StopLoss: stopPrice, StopLossTag: "stop_loss", }) } state.pyramidCount++ state.pyramidWaveID = state.impulseWaveID state.lastLevel = closePrice state.pullback = false } } }, } } // 实现与PineScript stoch()函数一致的Stochastic指标 func Stochastic(high, low, close *ta.Series, length int) *ta.Series { llowest := ta.Lowest(low, length) hhighest := ta.Highest(high, length) k := close.Sub(llowest).Div(hhighest.Sub(llowest)).Mul(100) return k } // 辅助函数:更新ALMA事件状态(包含tick标准化) func updateALEvents(state *HullBusterState, fastAlma, fastLinReg, closePrice, tickScaler, chgDivisor float64) { fxMinTick := tickScaler if fastAlma > fastLinReg { if state.fallALBarsSince > 0 { // 从下跌转为上涨 state.fallALBarsSince = 0 state.fallALCrossPrice = 0 state.riseALBarsSince = 1 state.riseALCrossPrice = closePrice state.riseALNetChg = 0 } else { state.riseALBarsSince++ if state.riseALCrossPrice > 0 { divisor := chgDivisor if chgDivisor <= 1.0 { divisor = state.riseALCrossPrice } state.riseALNetChg = ((closePrice - state.riseALCrossPrice) / fxMinTick / divisor) * 100.0 } } } else if fastAlma < fastLinReg { if state.riseALBarsSince > 0 { // 从上涨转为下跌 state.riseALBarsSince = 0 state.riseALCrossPrice = 0 state.fallALBarsSince = 1 state.fallALCrossPrice = closePrice state.fallALNetChg = 0 } else { state.fallALBarsSince++ if state.fallALCrossPrice > 0 { divisor := chgDivisor if chgDivisor <= 1.0 { divisor = state.fallALCrossPrice } state.fallALNetChg = ((state.fallALCrossPrice - closePrice) / fxMinTick / divisor) * 100.0 } } } } // 辅助函数:更新EMA事件状态(包含tick标准化) func updateEMEvents(state *HullBusterState, fastEMA, fastAlma, closePrice, tickScaler, chgDivisor float64) { fxMinTick := tickScaler if fastEMA > fastAlma { if state.fallEMBarsSince > 0 { state.fallEMBarsSince = 0 state.fallEMCrossPrice = 0 state.riseEMBarsSince = 1 state.riseEMCrossPrice = closePrice state.riseEMNetChg = 0 } else { state.riseEMBarsSince++ if state.riseEMCrossPrice > 0 { divisor := chgDivisor if chgDivisor <= 1.0 { divisor = state.riseEMCrossPrice } state.riseEMNetChg = ((closePrice - state.riseEMCrossPrice) / fxMinTick / divisor) * 100.0 } } } else if fastEMA < fastAlma { if state.riseEMBarsSince > 0 { state.riseEMBarsSince = 0 state.riseEMCrossPrice = 0 state.fallEMBarsSince = 1 state.fallEMCrossPrice = closePrice state.fallEMNetChg = 0 } else { state.fallEMBarsSince++ if state.fallEMCrossPrice > 0 { divisor := chgDivisor if chgDivisor <= 1.0 { divisor = state.fallEMCrossPrice } state.fallEMNetChg = ((state.fallEMCrossPrice - closePrice) / fxMinTick / divisor) * 100.0 } } } } // 辅助函数:更新ZScore状态 func updateZScoreState(state *HullBusterState, zScore, closePrice float64) { state.aboveZSSince++ state.belowZSSince++ state.riseZSSince++ state.fallZSSince++ // 简化的ZScore跟踪 if zScore > 0 { if state.aboveZSSince > 0 { state.aboveZSSince = 0 state.aboveZSCount = 1 } else { state.aboveZSCount++ } } if zScore < 0 { if state.belowZSSince > 0 { state.belowZSSince = 0 state.belowZSCount = 1 } else { state.belowZSCount++ } } } // 辅助函数:检查上升事件 func checkRiseEvent(state *HullBusterState, minNetChg, minROC float64) bool { return state.riseALBarsSince > 0 && state.riseALNetChg > 0 && state.riseALNetChg >= minNetChg && (state.riseALNetChg/float64(state.riseALBarsSince)) >= minROC } // 辅助函数:检查下降事件 func checkFallEvent(state *HullBusterState, minNetChg, minROC float64) bool { return state.fallALBarsSince > 0 && state.fallALNetChg > 0 && state.fallALNetChg >= minNetChg && (state.fallALNetChg/float64(state.fallALBarsSince)) >= minROC } // 辅助函数:检查EMA上升事件 func checkRiseEMEvent(state *HullBusterState, minNetChg, minROC float64) bool { return state.riseEMBarsSince > 0 && state.riseEMNetChg > 0 && state.riseEMNetChg >= minNetChg && (state.riseEMNetChg/float64(state.riseEMBarsSince)) >= minROC } // 辅助函数:检查EMA下降事件 func checkFallEMEvent(state *HullBusterState, minNetChg, minROC float64) bool { return state.fallEMBarsSince > 0 && state.fallEMNetChg > 0 && state.fallEMNetChg >= minNetChg && (state.fallEMNetChg/float64(state.fallEMBarsSince)) >= minROC } // DEMA实现(双重指数移动平均) func DEMA(series *ta.Series, period int) *ta.Series { ema1 := ta.EMA(series, period) ema2 := ta.EMA(ema1, period) return ema1.Mul(2).Sub(ema2) }