478 lines
13 KiB
Go
478 lines
13 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"
|
|
"time"
|
|
)
|
|
|
|
// State4067 存储每个品种的策略状态
|
|
type State4067 struct {
|
|
step int
|
|
inTrade bool
|
|
tradeDirection int // 1=多头, -1=空头, 0=无
|
|
entryPrice float64
|
|
tp1Hit bool
|
|
buytp1Hit bool
|
|
buySLPrice float64
|
|
buyTP1Price float64
|
|
buyTP2Price float64
|
|
selltp1Hit bool
|
|
sellSLPrice float64
|
|
sellTP1Price float64
|
|
sellTP2Price float64
|
|
higherEMA float64 // 更高时间周期EMA值
|
|
}
|
|
|
|
// MTFCross 多时间周期EMA交叉策略
|
|
func J4067(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
|
// 定义可调参数
|
|
fastPeriod := pol.DefInt("fastPeriod", 9, core.PNorm(5, 20))
|
|
slowRate := pol.Def("slowRate", 5.56, core.PNorm(2, 10))
|
|
higherEMARate := pol.Def("higherEMARate", 2, core.PNorm(1.5, 5))
|
|
slRate := pol.Def("slRate", 1.6, core.PNorm(0.5, 5))
|
|
tp1Rate := pol.Def("tp1Rate", 1.4, core.PNorm(0.5, 3))
|
|
beoffRate := pol.Def("beoffRate", 0.3, core.PNorm(0.1, 1))
|
|
atrPeriod := pol.DefInt("atrPeriod", 14, core.PNorm(7, 30))
|
|
|
|
// 交易时间控制参数
|
|
startHour := pol.DefInt("startHour", 1, core.PNorm(0, 23))
|
|
endHour := pol.DefInt("endHour", 25, core.PNorm(0, 47))
|
|
tradeOnMonday := pol.DefInt("tradeOnMonday", 1, core.PNorm(0, 1)) != 0
|
|
tradeOnTuesday := pol.DefInt("tradeOnTuesday", 1, core.PNorm(0, 1)) != 0
|
|
tradeOnWednesday := pol.DefInt("tradeOnWednesday", 1, core.PNorm(0, 1)) != 0
|
|
tradeOnThursday := pol.DefInt("tradeOnThursday", 1, core.PNorm(0, 1)) != 0
|
|
tradeOnFriday := pol.DefInt("tradeOnFriday", 1, core.PNorm(0, 1)) != 0
|
|
|
|
// 计算衍生参数
|
|
slowPeriod := int(float64(fastPeriod) * slowRate)
|
|
higherPeriod := int(float64(slowPeriod) * higherEMARate)
|
|
|
|
// 调整endHour为24小时格式
|
|
adjustedEndHour := endHour % 24
|
|
|
|
return &strat.TradeStrat{
|
|
WarmupNum: slowPeriod, // 预热K线数量
|
|
HedgeOff: true, // 单向持仓
|
|
EachMaxLong: 1, // 最多一个多头订单
|
|
EachMaxShort: 1, // 最多一个空头订单
|
|
RunTimeFrames: []string{"4h"},
|
|
// 初始化策略状态
|
|
OnStartUp: func(s *strat.StratJob) {
|
|
s.More = &State4067{
|
|
step: 0,
|
|
inTrade: false,
|
|
tradeDirection: 0,
|
|
entryPrice: math.NaN(),
|
|
tp1Hit: false,
|
|
buytp1Hit: false,
|
|
selltp1Hit: false,
|
|
buySLPrice: math.NaN(),
|
|
buyTP1Price: math.NaN(),
|
|
buyTP2Price: math.NaN(),
|
|
sellSLPrice: math.NaN(),
|
|
sellTP1Price: math.NaN(),
|
|
sellTP2Price: math.NaN(),
|
|
higherEMA: math.NaN(),
|
|
}
|
|
},
|
|
|
|
// 定义需要订阅的其他时间周期数据
|
|
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
|
return []*strat.PairSub{
|
|
{Pair: "_cur_", TimeFrame: "15m", WarmupNum: higherPeriod},
|
|
}
|
|
},
|
|
|
|
// 处理更高时间周期数据
|
|
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
|
state, _ := s.More.(*State4067)
|
|
// 计算更高时间周期的EMA
|
|
higherEMA := ta.EMA(e.Close, higherPeriod)
|
|
if higherEMA.Len() > 0 {
|
|
state.higherEMA = higherEMA.Get(0)
|
|
}
|
|
},
|
|
|
|
// 主策略逻辑
|
|
OnBar: func(s *strat.StratJob) {
|
|
e := s.Env
|
|
state, _ := s.More.(*State4067)
|
|
|
|
// 计算技术指标
|
|
fastEMA := ta.EMA(e.Close, fastPeriod)
|
|
slowEMA := ta.EMA(e.Close, slowPeriod)
|
|
atr := ta.ATR(e.High, e.Low, e.Close, atrPeriod)
|
|
|
|
// 获取当前值
|
|
closePrice := e.Close.Get(0)
|
|
fastEMAV := fastEMA.Get(0)
|
|
slowEMAV := slowEMA.Get(0)
|
|
atrV := atr.Get(0)
|
|
|
|
// 检查数据有效性
|
|
if math.IsNaN(closePrice) || math.IsNaN(fastEMAV) || math.IsNaN(slowEMAV) ||
|
|
math.IsNaN(atrV) || math.IsNaN(state.higherEMA) {
|
|
return
|
|
}
|
|
|
|
// 计算止损止盈距离
|
|
slDist := atrV * slRate
|
|
tp1Dist := atrV * tp1Rate
|
|
tp2Dist := atrV * tp1Rate * 2 // 第二止盈是第一止盈的2倍
|
|
beoffDist := atrV * beoffRate
|
|
|
|
// 检查交易时间
|
|
t := time.UnixMilli(e.TimeStart)
|
|
isTradingTime := checkTradingTime(t, startHour, adjustedEndHour,
|
|
tradeOnMonday, tradeOnTuesday, tradeOnWednesday, tradeOnThursday, tradeOnFriday)
|
|
|
|
// 更新信号状态步骤
|
|
updateStep(state, fastEMA, slowEMA)
|
|
|
|
// 生成交易信号(仅在交易时间内)
|
|
if isTradingTime {
|
|
if state.step == 1 && !state.inTrade && fastEMAV > slowEMAV && fastEMAV > state.higherEMA {
|
|
// 买入信号
|
|
state.inTrade = true
|
|
state.tradeDirection = 1
|
|
state.entryPrice = closePrice
|
|
state.buytp1Hit = false
|
|
state.tp1Hit = false
|
|
state.buySLPrice = state.entryPrice - slDist
|
|
state.buyTP1Price = state.entryPrice + tp1Dist
|
|
state.buyTP2Price = state.entryPrice + tp2Dist
|
|
state.step = 3
|
|
|
|
// 开多单
|
|
//s.OpenOrder(&strat.EnterReq{
|
|
// Tag: "Buy",
|
|
// Short: false,
|
|
// StopLoss: state.buySLPrice,
|
|
// TakeProfit: state.buyTP1Price,
|
|
// StopLossVal: slDist,
|
|
//})
|
|
} else if state.step == 2 && !state.inTrade && fastEMAV < slowEMAV && fastEMAV < state.higherEMA {
|
|
// 卖出信号
|
|
state.inTrade = true
|
|
state.tradeDirection = -1
|
|
state.entryPrice = closePrice
|
|
state.selltp1Hit = false
|
|
state.tp1Hit = false
|
|
state.sellSLPrice = state.entryPrice + slDist
|
|
state.sellTP1Price = state.entryPrice - tp1Dist
|
|
state.sellTP2Price = state.entryPrice - tp2Dist
|
|
state.step = 4
|
|
|
|
// 开空单
|
|
s.OpenOrder(&strat.EnterReq{
|
|
Tag: "Sell",
|
|
Short: true,
|
|
StopLoss: state.sellSLPrice,
|
|
TakeProfit: state.sellTP1Price,
|
|
StopLossVal: slDist,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 管理多头仓位
|
|
if state.inTrade && state.tradeDirection == 1 && len(s.LongOrders) > 0 {
|
|
od := s.LongOrders[0]
|
|
highPrice := e.High.Get(0)
|
|
lowPrice := e.Low.Get(0)
|
|
|
|
// 检查是否达到第一止盈目标
|
|
if highPrice >= state.buyTP1Price && !state.tp1Hit {
|
|
state.tp1Hit = true
|
|
state.buytp1Hit = true
|
|
// 移动止损到保本位置
|
|
state.buySLPrice = state.entryPrice + beoffDist
|
|
// 平仓25%
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "TP1 Hit",
|
|
Dirt: core.OdDirtLong,
|
|
ExitRate: 0.25,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
// 更新剩余仓位的止损止盈
|
|
od.SetStopLoss(&ormo.ExitTrigger{
|
|
Price: state.buySLPrice,
|
|
Rate: 1.0,
|
|
Tag: "Breakeven",
|
|
})
|
|
od.SetTakeProfit(&ormo.ExitTrigger{
|
|
Price: state.buyTP2Price,
|
|
Rate: 1.0,
|
|
Tag: "TP2",
|
|
})
|
|
}
|
|
|
|
// 检查是否触发止损
|
|
if lowPrice <= state.buySLPrice && !state.tp1Hit {
|
|
// 止损出场
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "SL Hit",
|
|
Dirt: core.OdDirtLong,
|
|
ExitRate: 1.0,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
resetState(state)
|
|
}
|
|
|
|
// 检查是否达到第二止盈目标
|
|
if state.tp1Hit && highPrice >= state.buyTP2Price {
|
|
// 第二止盈出场
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "TP2 Hit",
|
|
Dirt: core.OdDirtLong,
|
|
ExitRate: 1.0,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
resetState(state)
|
|
}
|
|
|
|
// 检查保本止损是否触发
|
|
if state.tp1Hit && lowPrice <= state.buySLPrice {
|
|
// 保本止损出场
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "Breakeven Hit",
|
|
Dirt: core.OdDirtLong,
|
|
ExitRate: 1.0,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
resetState(state)
|
|
}
|
|
}
|
|
|
|
// 管理空头仓位
|
|
if state.inTrade && state.tradeDirection == -1 && len(s.ShortOrders) > 0 {
|
|
od := s.ShortOrders[0]
|
|
highPrice := e.High.Get(0)
|
|
lowPrice := e.Low.Get(0)
|
|
|
|
// 检查是否达到第一止盈目标
|
|
if lowPrice <= state.sellTP1Price && !state.tp1Hit {
|
|
state.tp1Hit = true
|
|
state.selltp1Hit = true
|
|
// 移动止损到保本位置
|
|
state.sellSLPrice = state.entryPrice - beoffDist
|
|
// 平仓25%
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "TP1 Hit",
|
|
Dirt: core.OdDirtShort,
|
|
ExitRate: 0.25,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
// 更新剩余仓位的止损止盈
|
|
od.SetStopLoss(&ormo.ExitTrigger{
|
|
Price: state.sellSLPrice,
|
|
Rate: 1.0,
|
|
Tag: "Breakeven",
|
|
})
|
|
od.SetTakeProfit(&ormo.ExitTrigger{
|
|
Price: state.sellTP2Price,
|
|
Rate: 1.0,
|
|
Tag: "TP2",
|
|
})
|
|
}
|
|
|
|
// 检查是否触发止损
|
|
if highPrice >= state.sellSLPrice && !state.tp1Hit {
|
|
// 止损出场
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "SL Hit",
|
|
Dirt: core.OdDirtShort,
|
|
ExitRate: 1.0,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
resetState(state)
|
|
}
|
|
|
|
// 检查是否达到第二止盈目标
|
|
if state.tp1Hit && lowPrice <= state.sellTP2Price {
|
|
// 第二止盈出场
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "TP2 Hit",
|
|
Dirt: core.OdDirtShort,
|
|
ExitRate: 1.0,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
resetState(state)
|
|
}
|
|
|
|
// 检查保本止损是否触发
|
|
if state.tp1Hit && highPrice >= state.sellSLPrice {
|
|
// 保本止损出场
|
|
s.CloseOrders(&strat.ExitReq{
|
|
Tag: "Breakeven Hit",
|
|
Dirt: core.OdDirtShort,
|
|
ExitRate: 1.0,
|
|
OrderType: core.OrderTypeMarket,
|
|
})
|
|
resetState(state)
|
|
}
|
|
}
|
|
|
|
// 更新交易状态
|
|
if state.inTrade && state.tradeDirection == 1 && state.step == 2 {
|
|
// 多头收到卖出信号
|
|
state.inTrade = false
|
|
state.tradeDirection = 0
|
|
state.step = 2
|
|
}
|
|
|
|
if state.inTrade && state.tradeDirection == -1 && state.step == 1 {
|
|
// 空头收到买入信号
|
|
state.inTrade = false
|
|
state.tradeDirection = 0
|
|
state.step = 1
|
|
}
|
|
},
|
|
|
|
// 自定义订单退出逻辑
|
|
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
|
|
state, _ := s.More.(*State4067)
|
|
|
|
// 多头订单检查
|
|
if !od.Short && state.inTrade && state.tradeDirection == 1 {
|
|
lowPrice := s.Env.Low.Get(0)
|
|
|
|
// 检查是否触发止损
|
|
if lowPrice <= state.buySLPrice {
|
|
return &strat.ExitReq{
|
|
Tag: "SL Hit",
|
|
Dirt: core.OdDirtLong,
|
|
ExitRate: 1.0,
|
|
}
|
|
}
|
|
|
|
// 检查是否达到第二止盈目标
|
|
if state.tp1Hit {
|
|
highPrice := s.Env.High.Get(0)
|
|
if highPrice >= state.buyTP2Price {
|
|
return &strat.ExitReq{
|
|
Tag: "TP2 Hit",
|
|
Dirt: core.OdDirtLong,
|
|
ExitRate: 1.0,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 空头订单检查
|
|
if od.Short && state.inTrade && state.tradeDirection == -1 {
|
|
highPrice := s.Env.High.Get(0)
|
|
|
|
// 检查是否触发止损
|
|
if highPrice >= state.sellSLPrice {
|
|
return &strat.ExitReq{
|
|
Tag: "SL Hit",
|
|
Dirt: core.OdDirtShort,
|
|
ExitRate: 1.0,
|
|
}
|
|
}
|
|
|
|
// 检查是否达到第二止盈目标
|
|
if state.tp1Hit {
|
|
lowPrice := s.Env.Low.Get(0)
|
|
if lowPrice <= state.sellTP2Price {
|
|
return &strat.ExitReq{
|
|
Tag: "TP2 Hit",
|
|
Dirt: core.OdDirtShort,
|
|
ExitRate: 1.0,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// checkTradingTime 检查当前时间是否在交易时间内
|
|
func checkTradingTime(t time.Time, startHour, adjustedEndHour int,
|
|
tradeOnMonday, tradeOnTuesday, tradeOnWednesday, tradeOnThursday, tradeOnFriday bool) bool {
|
|
// 获取当前小时和星期几
|
|
currentHour := t.Hour()
|
|
currentDay := t.Weekday()
|
|
|
|
// 检查是否在交易小时内
|
|
isTradingHour := false
|
|
if startHour < adjustedEndHour {
|
|
isTradingHour = currentHour >= startHour && currentHour < adjustedEndHour
|
|
} else {
|
|
isTradingHour = currentHour >= startHour || currentHour < adjustedEndHour
|
|
}
|
|
|
|
// 检查是否在交易日内
|
|
isTradingDay := false
|
|
switch currentDay {
|
|
case time.Monday:
|
|
isTradingDay = tradeOnMonday
|
|
case time.Tuesday:
|
|
isTradingDay = tradeOnTuesday
|
|
case time.Wednesday:
|
|
isTradingDay = tradeOnWednesday
|
|
case time.Thursday:
|
|
isTradingDay = tradeOnThursday
|
|
case time.Friday:
|
|
isTradingDay = tradeOnFriday
|
|
}
|
|
|
|
return isTradingHour && isTradingDay
|
|
}
|
|
|
|
// updateStep 更新信号状态步骤
|
|
func updateStep(state *State4067, fastEMA, slowEMA *ta.Series) {
|
|
// 检查EMA交叉
|
|
crossUp := fastEMA.Cross(slowEMA)
|
|
crossDown := slowEMA.Cross(fastEMA)
|
|
|
|
// 更新状态步骤
|
|
if (state.step == 0 || state.step == 4) && crossUp == 1 && fastEMA.Get(0) > state.higherEMA {
|
|
state.step = 1
|
|
}
|
|
|
|
if state.step == 3 && crossUp == 1 && fastEMA.Get(0) > state.higherEMA {
|
|
state.step = 3
|
|
}
|
|
|
|
if state.step == 2 && crossUp == 1 && fastEMA.Get(0) > state.higherEMA {
|
|
state.step = 1
|
|
}
|
|
|
|
if (state.step == 0 || state.step == 3) && crossDown == 1 && fastEMA.Get(0) < state.higherEMA {
|
|
state.step = 2
|
|
}
|
|
|
|
if state.step == 4 && crossDown == 1 && fastEMA.Get(0) < state.higherEMA {
|
|
state.step = 4
|
|
}
|
|
|
|
if state.step == 1 && crossDown == 1 && fastEMA.Get(0) < state.higherEMA {
|
|
state.step = 2
|
|
}
|
|
}
|
|
|
|
// resetState 重置策略状态
|
|
func resetState(state *State4067) {
|
|
state.inTrade = false
|
|
state.tradeDirection = 0
|
|
state.entryPrice = math.NaN()
|
|
state.tp1Hit = false
|
|
state.buytp1Hit = false
|
|
state.selltp1Hit = false
|
|
state.buySLPrice = math.NaN()
|
|
state.buyTP1Price = math.NaN()
|
|
state.buyTP2Price = math.NaN()
|
|
state.sellSLPrice = math.NaN()
|
|
state.sellTP1Price = math.NaN()
|
|
state.sellTP2Price = math.NaN()
|
|
state.step = 0
|
|
}
|