270 lines
8.1 KiB
Go
270 lines
8.1 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"
|
|
)
|
|
|
|
// ZeroLagEMA 计算零滞后指数移动平均线
|
|
func ZeroLagEMA(source *ta.Series, length int) *ta.Series {
|
|
res := source.To("_zlema", length)
|
|
if res.Cached() {
|
|
return res
|
|
}
|
|
if source.Len() < length {
|
|
return res.Append(math.NaN())
|
|
}
|
|
ema1 := ta.EMA(source, length)
|
|
ema2 := ta.EMA(ema1, length)
|
|
c1 := ema1.Get(0) - ema2.Get(0)
|
|
zlema := ema1.Get(0) + c1
|
|
return res.Append(zlema)
|
|
}
|
|
|
|
// ZlemaState 存储每个品种的策略状态
|
|
type ZlemaState struct {
|
|
goLong bool
|
|
sectionLongs int
|
|
sectionShorts int
|
|
sectionLongConditions int
|
|
sectionShortConditions int
|
|
totalLongs float64
|
|
totalShorts float64
|
|
averageLongs float64
|
|
averageShorts float64
|
|
lastHigh float64
|
|
lastLow float64
|
|
inLong bool
|
|
inShort bool
|
|
}
|
|
|
|
func J2984(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
|
// 定义可调超参数
|
|
fastPeriod := pol.DefInt("fastPeriod", 10, core.PNorm(3, 20))
|
|
maRate := pol.Def("maRate", 2.4, core.PNorm(1.5, 5))
|
|
takeProfitRate := pol.Def("takeProfitRate", 3.5, core.PNorm(1, 10))
|
|
trailingStopRate := pol.Def("trailingStopRate", 3.5, core.PNorm(1, 8))
|
|
trailingActivationRate := pol.Def("trailingActivationRate", 16, core.PNorm(5, 25))
|
|
stopLossPercent := pol.Def("stopLossPercent", 7.5, core.PNorm(1, 15))
|
|
|
|
// 金字塔加仓参数
|
|
pyramidingLess := pol.DefInt("pyramidingLess", 1, core.PNorm(0, 5))
|
|
pyramidingEqual := pol.DefInt("pyramidingEqual", 0, core.PNorm(0, 5))
|
|
pyramidingGreater := pol.DefInt("pyramidingGreater", 1000000, core.PNorm(100, 1000000))
|
|
|
|
// 交易方向控制参数
|
|
longsOnly := pol.Def("longsOnly", 0, core.PUniform(0, 1)) > 0.5
|
|
shortsOnly := pol.Def("shortsOnly", 0, core.PUniform(0, 1)) > 0.5
|
|
flipOpens := pol.Def("flipOpens", 0, core.PUniform(0, 1)) > 0.5
|
|
|
|
// 计算慢线周期
|
|
slowPeriod := int(float64(fastPeriod) * maRate)
|
|
|
|
return &strat.TradeStrat{
|
|
WarmupNum: slowPeriod * 2, // 确保有足够的数据计算指标
|
|
HedgeOff: true, // 单向持仓模式
|
|
EachMaxLong: 1, // 最多开1单多仓
|
|
EachMaxShort: 1, // 最多开1单空仓
|
|
DrawDownExit: false, // 不使用回撤止盈
|
|
RunTimeFrames: []string{"8h"},
|
|
StopLoss: 0.075,
|
|
OnStartUp: func(s *strat.StratJob) {
|
|
// 初始化策略状态
|
|
s.More = &ZlemaState{
|
|
goLong: true,
|
|
sectionLongs: 0,
|
|
sectionShorts: 0,
|
|
sectionLongConditions: 0,
|
|
sectionShortConditions: 0,
|
|
totalLongs: 0,
|
|
totalShorts: 0,
|
|
averageLongs: 0,
|
|
averageShorts: 0,
|
|
lastHigh: 0,
|
|
lastLow: 0,
|
|
inLong: false,
|
|
inShort: false,
|
|
}
|
|
},
|
|
OnBar: func(s *strat.StratJob) {
|
|
e := s.Env
|
|
state, _ := s.More.(*ZlemaState)
|
|
|
|
// 计算Zero-Lag EMA
|
|
zlemaFast := ZeroLagEMA(e.Close, fastPeriod)
|
|
zlemaSlow := ZeroLagEMA(e.Close, slowPeriod)
|
|
|
|
// 获取当前值
|
|
fastVal := zlemaFast.Get(0)
|
|
slowVal := zlemaSlow.Get(0)
|
|
fastVal1 := zlemaFast.Get(1)
|
|
slowVal1 := zlemaSlow.Get(1)
|
|
closePrice := e.Close.Get(0)
|
|
highPrice := e.High.Get(0)
|
|
lowPrice := e.Low.Get(0)
|
|
|
|
// 检查数据有效性
|
|
if math.IsNaN(fastVal) || math.IsNaN(slowVal) || math.IsNaN(closePrice) {
|
|
return
|
|
}
|
|
|
|
// 生成基础交易信号
|
|
longSignal := fastVal > slowVal && fastVal1 <= slowVal1 // 快线上穿慢线
|
|
shortSignal := fastVal < slowVal && fastVal1 >= slowVal1 // 快线下穿慢线
|
|
|
|
// 应用交易方向翻转功能
|
|
if flipOpens {
|
|
longSignal, shortSignal = shortSignal, longSignal
|
|
}
|
|
|
|
// 应用仅做多/仅做空选项
|
|
if longsOnly {
|
|
shortSignal = false
|
|
}
|
|
if shortsOnly {
|
|
longSignal = false
|
|
}
|
|
|
|
// 更新连续信号计数
|
|
if longSignal {
|
|
state.sectionLongs++
|
|
state.sectionShorts = 0
|
|
} else if shortSignal {
|
|
state.sectionShorts++
|
|
state.sectionLongs = 0
|
|
}
|
|
|
|
// 应用金字塔式加仓条件
|
|
longCondition := longSignal && (state.sectionLongs <= pyramidingLess || state.sectionLongs >= pyramidingGreater || state.sectionLongs == pyramidingEqual)
|
|
shortCondition := shortSignal && (state.sectionShorts <= pyramidingLess || state.sectionShorts >= pyramidingGreater || state.sectionShorts == pyramidingEqual)
|
|
|
|
// 更新开仓价格和数量
|
|
if longCondition {
|
|
state.totalLongs += closePrice
|
|
state.sectionLongConditions++
|
|
state.averageLongs = state.totalLongs / float64(state.sectionLongConditions)
|
|
state.inLong = true
|
|
state.inShort = false
|
|
} else if shortCondition {
|
|
state.totalShorts += closePrice
|
|
state.sectionShortConditions++
|
|
state.averageShorts = state.totalShorts / float64(state.sectionShortConditions)
|
|
state.inShort = true
|
|
state.inLong = false
|
|
}
|
|
|
|
// 更新最高价/最低价追踪
|
|
if state.inLong {
|
|
if state.lastHigh == 0 || highPrice > state.lastHigh {
|
|
state.lastHigh = highPrice
|
|
}
|
|
} else if state.inShort {
|
|
if state.lastLow == 0 || lowPrice < state.lastLow {
|
|
state.lastLow = lowPrice
|
|
}
|
|
}
|
|
|
|
// 执行交易
|
|
if longCondition && len(s.LongOrders) == 0 {
|
|
// 设置止损止盈 - 基于平均开仓价计算
|
|
stopLossVal := state.averageLongs * (1 - stopLossPercent/100)
|
|
takeProfitVal := state.averageLongs * (1 + takeProfitRate/100)
|
|
|
|
s.OpenOrder(&strat.EnterReq{
|
|
Tag: "long",
|
|
Short: false,
|
|
OrderType: core.OrderTypeMarket,
|
|
StopLossVal: stopLossVal,
|
|
TakeProfitVal: takeProfitVal,
|
|
StopLossTag: "stop_loss",
|
|
TakeProfitTag: "take_profit",
|
|
})
|
|
} else if shortCondition && len(s.ShortOrders) == 0 {
|
|
// 设置止损止盈 - 基于平均开仓价计算
|
|
stopLossVal := state.averageShorts * (1 + stopLossPercent/100)
|
|
takeProfitVal := state.averageShorts * (1 - takeProfitRate/100)
|
|
|
|
s.OpenOrder(&strat.EnterReq{
|
|
Tag: "short",
|
|
Short: true,
|
|
OrderType: core.OrderTypeMarket,
|
|
StopLossVal: stopLossVal,
|
|
TakeProfitVal: takeProfitVal,
|
|
StopLossTag: "stop_loss",
|
|
TakeProfitTag: "take_profit",
|
|
})
|
|
}
|
|
|
|
// 检查平仓条件
|
|
if state.inLong && shortSignal {
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "trend_reverse",
|
|
Dirt: core.OdDirtLong,
|
|
})
|
|
} else if state.inShort && longSignal {
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "trend_reverse",
|
|
Dirt: core.OdDirtShort,
|
|
})
|
|
}
|
|
},
|
|
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
|
e := s.Env
|
|
state, _ := s.More.(*ZlemaState)
|
|
closePrice := e.Close.Get(0)
|
|
|
|
if od.Short {
|
|
// 做空订单检查
|
|
if state.inShort {
|
|
// 检查追踪止损条件 - 包含激活条件判断
|
|
if state.lastLow > 0 &&
|
|
state.lastLow <= state.averageShorts*(1-trailingActivationRate/100) && // 激活条件
|
|
closePrice >= state.lastLow*(1+trailingStopRate/100) { // 追踪止损条件
|
|
return &strat.ExitReq{Tag: "trailing_stop"}
|
|
}
|
|
}
|
|
} else {
|
|
// 做多订单检查
|
|
if state.inLong {
|
|
// 检查追踪止损条件 - 包含激活条件判断
|
|
if state.lastHigh > 0 &&
|
|
state.lastHigh >= state.averageLongs*(1+trailingActivationRate/100) && // 激活条件
|
|
closePrice <= state.lastHigh*(1-trailingStopRate/100) { // 追踪止损条件
|
|
return &strat.ExitReq{Tag: "trailing_stop"}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
|
state, _ := s.More.(*ZlemaState)
|
|
|
|
// 订单状态变化时更新状态
|
|
if chgType == strat.OdChgExitFill {
|
|
if od.Short {
|
|
// 做空订单已平仓
|
|
state.inShort = false
|
|
state.averageShorts = 0
|
|
state.totalShorts = 0
|
|
state.sectionShorts = 0
|
|
state.sectionShortConditions = 0
|
|
state.lastLow = 0
|
|
} else {
|
|
// 做多订单已平仓
|
|
state.inLong = false
|
|
state.averageLongs = 0
|
|
state.totalLongs = 0
|
|
state.sectionLongs = 0
|
|
state.sectionLongConditions = 0
|
|
state.lastHigh = 0
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|