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