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

625 lines
18 KiB
Go
Raw Permalink 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"
)
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)
}