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

215 lines
5.9 KiB
Go
Raw 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 his
import (
"fmt"
"github.com/banbox/banbot/com"
"github.com/banbox/banbot/config"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/orm/ormo"
"github.com/banbox/banbot/strat"
"github.com/banbox/banexg"
"github.com/banbox/banexg/log"
"github.com/banbox/banexg/utils"
ta "github.com/banbox/banta"
"go.uber.org/zap"
"math"
)
/*
超跌反弹策略。30个品种为宜
做空部分没有利润,禁用
*/
type State3762 struct {
NoLongUntil int64
NoShortUntil int64
}
func J3762(pol *config.RunPolicyConfig) *strat.TradeStrat {
// 定义策略参数
p := pol.DefInt("p", 20, core.PNorm(5, 50))
d := pol.Def("d", 30.0, core.PNorm(5, 50))
mult := pol.Def("mult", 1.5, core.PNorm(1.0, 3.0))
slRate := pol.Def("slRate", 25, core.PNorm(0, 50))
needlong := pol.Def("needlong", 1.0, core.PUniform(0, 1)) > 0.5
needshort := pol.Def("needshort", 0.0, core.PUniform(0, 1)) > 0.5
dayMS := int64(utils.TFToSecs("1d") * 1000)
lenAtr := 20
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
return &strat.TradeStrat{
WarmupNum: baseAtrLen * 2, // 预热K线数量,确保有足够数据计算指标
RunTimeFrames: []string{"15m"},
OnStartUp: func(s *strat.StratJob) {
s.More = &State3762{}
},
OnBar: func(s *strat.StratJob) {
e := s.Env
m, _ := s.More.(*State3762)
// 计算基础布林带指标
sma := ta.SMA(e.Close, p)
ma7 := ta.SMA(e.Close, 7).Get(0)
ma30 := ta.SMA(e.Close, 30).Get(0)
multCur := mult
if ma7 < ma30 {
// 熊市下移下限
multCur *= 1.35
}
stddev := ta.StdDev(e.Close, p).Get(0) * multCur
upperBB := sma.Get(0) + stddev
lowerBB := sma.Get(0) - stddev
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
// 计算百分比偏离轨道
atrRt := d
if ma7 < ma30 {
// 熊市下移下限
atrRt *= 1.35
}
upex2 := sma.Get(0) + atrBase*atrRt
dnex2 := sma.Get(0) - atrBase*atrRt
// 计算最终轨道
upexPrice := max(upperBB, (upex2+upperBB)/2)
dnexPrice := min(lowerBB, (dnex2+lowerBB)/2)
// 获取当前价格
closePrice := e.Close.Get(0)
openPrice := e.Open.Get(0)
// 生成交易信号
exitSignal := closePrice > openPrice // 绿色K线信号
if math.IsNaN(upexPrice) || math.IsNaN(dnexPrice) {
return
}
slRange := min(atrBase*slRate, closePrice*0.25)
// 执行交易逻辑
if !s.IsWarmUp {
s.Strat.WriteOutput(fmt.Sprintf("%s upp: %.6f, low: %.6f upex2: %.6f, dnex2: %.6f upex: %.6f, dnex: %.6f slRange: %.6f",
e.Symbol, upperBB, lowerBB, upex2, dnex2, upexPrice, dnexPrice, slRange), true)
}
if needlong {
if len(s.LongOrders) == 0 {
// 使用限价单在下轨价格做多,不依赖当前价格是否突破轨道
if e.TimeStop >= m.NoLongUntil {
curPrice := com.GetPrice(e.Symbol, banexg.OdSideBuy)
if math.Abs(curPrice/closePrice-1) > 0.01 {
log.Info("bad price from GetPrice", zap.String("pair", e.Symbol),
zap.Float64("val", curPrice), zap.Float64("close", closePrice))
}
s.OpenOrder(&strat.EnterReq{
Tag: "Long",
Limit: dnexPrice, // 以下轨价格作为限价单
StopLossVal: slRange,
Log: true,
})
}
} else {
for _, od := range s.LongOrders {
if od.Status == ormo.InOutStatusInit {
err := od.SetEnterLimit(dnexPrice)
if err != nil {
log.Error("SetEnterLimit fail", zap.Error(err))
} else {
err = od.SetStopLoss(&ormo.ExitTrigger{
Price: dnexPrice - slRange,
})
if err != nil {
log.Error("SetStopLoss fail", zap.Error(err))
}
}
} else if od.Status == ormo.InOutStatusFullEnter {
barNum := e.BarCount(od.Enter.UpdateAt)
if barNum > 2 && od.ProfitRate < 0 {
// 持仓2个k线,出现亏损,平仓
s.CloseOrders(&strat.ExitReq{
Tag: "Exit bad Long",
Dirt: core.OdDirtLong,
Log: true,
})
m.NoLongUntil = e.TimeStop + dayMS
}
}
}
}
}
if needshort {
if len(s.ShortOrders) == 0 {
// 使用限价单在上轨价格做空,不依赖当前价格是否突破轨道
if e.TimeStop >= m.NoShortUntil {
s.OpenOrder(&strat.EnterReq{
Tag: "Short",
Short: true,
Limit: upexPrice, // 以上轨价格作为限价单
StopLossVal: slRange,
Log: true,
})
}
} else {
for _, od := range s.ShortOrders {
if od.Status == ormo.InOutStatusInit {
err := od.SetEnterLimit(upexPrice)
if err != nil {
log.Error("SetEnterLimit fail", zap.Error(err))
} else {
err = od.SetStopLoss(&ormo.ExitTrigger{
Price: upexPrice + slRange,
})
if err != nil {
log.Error("SetStopLoss fail", zap.Error(err))
}
}
} else if od.Status == ormo.InOutStatusFullEnter {
barNum := e.BarCount(od.Enter.UpdateAt)
if barNum > 2 && od.ProfitRate < 0 {
// 持仓2个k线,出现亏损,平仓
s.CloseOrders(&strat.ExitReq{
Tag: "Exit bad short",
Dirt: core.OdDirtShort,
Log: true,
})
m.NoShortUntil = e.TimeStop + dayMS
}
}
}
}
}
// 平仓条件:绿色K线
if exitSignal {
if len(s.LongOrders) > 0 {
s.CloseOrders(&strat.ExitReq{
Tag: "Exit Long",
Dirt: core.OdDirtLong,
Log: true,
})
}
} else {
if len(s.ShortOrders) > 0 {
s.CloseOrders(&strat.ExitReq{
Tag: "Exit Short",
Dirt: core.OdDirtShort,
Log: true,
})
}
}
},
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
if chgType == core.OdChgExitFill && od.ExitTag == core.ExitTagStopLoss {
m, _ := s.More.(*State3762)
if od.Short {
m.NoShortUntil = od.ExitAt + dayMS
} else {
m.NoLongUntil = od.ExitAt + dayMS
}
}
},
}
}