This commit is contained in:
2026-08-24 20:53:21 +08:00
commit 6e78bba16a
287 changed files with 32443 additions and 0 deletions

270
llmjob/3762.go Normal file
View File

@@ -0,0 +1,270 @@
package llmjob
import (
"fmt"
"math"
"sort"
"sync"
"github.com/banbox/banbot/biz"
"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/log"
"github.com/banbox/banexg/utils"
ta "github.com/banbox/banta"
"go.uber.org/zap"
)
/*
超跌反弹策略。30个品种为宜;已增加暴跌抄底机制。
开单越多,后续抄底价指数下移
做空部分没有利润,不稳定,小亏,禁用
*/
type State3762 struct {
NoLongUntil int64
BWidth float64
MidPrice float64
SlRange float64
volRate float64
}
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, 250)) //default: 25
// 某品种入场时,下移其他品种的倍率(相对BWidth)
// 0.3开单太多亏损,0.5开单不足亏损
downOtherRate := 0.33
dayMS := int64(utils.TFToSecs("1d") * 1000)
longFillNum := float64(0)
fillLock := sync.Mutex{}
var allJobs []*strat.StratJob
// 基于成交量计算的区间倍率,成交量太大时,适当调低做多和止损价格
var rangeVolRate = float64(1)
allowPairs := 1 // 允许挂单的订单数,默认为品种数,随开单减少,防止交易所故障导致下单过多
openPairs := 0 // 实际有订单的品种数(挂单+持仓)
lenAtr := 20
baseAtrLen := int(math.Round(float64(lenAtr) * 4.3))
calcBuyPrice := func(m *State3762) float64 {
return m.MidPrice - m.BWidth*(1+math.Pow(longFillNum, 1.5)*downOtherRate)*rangeVolRate
}
return &strat.TradeStrat{
WarmupNum: baseAtrLen * 2, // 预热K线数量,确保有足够数据计算指标
RunTimeFrames: []string{"15m"},
BatchInOut: true,
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)
maVol := ta.EMA(e.Volume, lenAtr)
volBase := ta.Lowest(ta.Highest(maVol, lenAtr), baseAtrLen).Get(0)
m.volRate = maVol.Get(0) / volBase
volH3Rate := ta.Highest(maVol, 3).Get(0) / volBase
// 计算百分比偏离轨道
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)
m.BWidth = (upexPrice - dnexPrice) / 2
m.MidPrice = (upexPrice + dnexPrice) / 2
//lowPrice, highPrice := dnexPrice, upexPrice
lowPrice := calcBuyPrice(m)
// 获取当前价格
closePrice := e.Close.Get(0)
// 平仓条件:阳线
if len(s.LongOrders) > 0 && s.LongOrders[0].Status >= ormo.InOutStatusPartEnter {
doExit := closePrice > e.Open.Get(0)
if volH3Rate > 5 {
// 放巨量入场的订单,等待阳线后一个K线出场
doExit = e.Close.Get(1) > e.Open.Get(1)
}
if doExit {
s.CloseOrders(&strat.ExitReq{
Tag: "ExitLong",
Dirt: core.OdDirtLong,
Log: true,
})
}
}
if math.IsNaN(lowPrice) || lowPrice <= 0 {
return
}
slRange := min(atrBase*slRate, closePrice*0.25) * min(3, rangeVolRate)
m.SlRange = slRange
// 执行交易逻辑
//if !s.IsWarmUp && e.TimeStop >= 1760130900000 {
// line := fmt.Sprintf("%s upp: %.6f, low: %.6f upex2: %.6f, dnex2: %.6f dnex: %.6f slRange: %.6f",
// e.Symbol, upperBB, lowerBB, upex2, dnex2, lowPrice, slRange)
// s.Strat.WriteOutput(line, true)
//}
if len(s.LongOrders) == 0 {
// 使用限价单在下轨价格做多,不依赖当前价格是否突破轨道
if e.TimeStop >= m.NoLongUntil && openPairs < allowPairs {
fillLock.Lock()
openPairs += 1
fillLock.Unlock()
s.OpenOrder(&strat.EnterReq{
Tag: "Long",
Limit: lowPrice, // 以下轨价格作为限价单
StopLossVal: slRange,
Log: true,
})
}
} else {
for _, od := range s.LongOrders {
if od.Status == ormo.InOutStatusInit {
err := od.SetEnterLimit(lowPrice)
if err != nil {
log.Error("SetEnterLimit fail", zap.Error(err))
} else {
err = od.SetStopLoss(&ormo.ExitTrigger{
Price: lowPrice - 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: "ExitBad",
Dirt: core.OdDirtLong,
Log: true,
})
m.NoLongUntil = e.TimeStop + dayMS
}
}
}
}
},
OnBatchJobs: func(jobs []*strat.StratJob) {
fillLock.Lock()
longFillNum = 0
openPairs = 0
volRates := make([]float64, 0, len(jobs))
for _, s := range jobs {
if len(s.LongOrders) > 0 {
openPairs += 1
for _, od := range s.LongOrders {
if od.Status >= ormo.InOutStatusPartEnter {
longFillNum += 1
}
}
}
m, _ := s.More.(*State3762)
volRates = append(volRates, m.volRate)
}
fillLock.Unlock()
allJobs = jobs
sort.Float64s(volRates)
rangeVolRate = max(1, math.Pow(volRates[len(volRates)/2]/2, 0.3))
allowPairs = int(math.Round(math.Pow(0.8, longFillNum) * float64(len(allJobs))))
//jobs[0].Strat.WriteOutput(fmt.Sprintf("%.3f", rangeVolRate), true)
},
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
if chgType == core.OdChgExitFill {
if od.Enter.Amount > 0 {
m, _ := s.More.(*State3762)
m.NoLongUntil = od.ExitAt + dayMS
fillLock.Lock()
longFillNum -= 1
openPairs -= 1
fillLock.Unlock()
}
} else if chgType == core.OdChgEnterFill {
fillLock.Lock()
longFillNum += 1
openPairs = 0 // 这里应当重新计算,避免并发和回调导致错误
for _, job := range allJobs {
if len(job.LongOrders) > 0 {
openPairs += 1
}
}
fillLock.Unlock()
// 降低允许开仓的品种数,避免交易所故障导致下单太多
allowPairs = int(math.Round(math.Pow(0.85, longFillNum) * float64(len(allJobs))))
needClose := openPairs - allowPairs // 需要关闭的订单数
if needClose > 0 {
// 排序确保多次回测一致
sort.Slice(allJobs, func(i, j int) bool {
return allJobs[i].Symbol.Symbol < allJobs[j].Symbol.Symbol
})
}
odMgr := biz.GetOdMgr(s.Account)
// 移动其他品种的入场价格,避免同时开仓太多
for _, job := range allJobs {
if job == s || len(job.LongOrders) == 0 {
continue
}
m, _ := job.More.(*State3762)
lowPrice := calcBuyPrice(m)
for _, odr := range job.LongOrders {
if odr.Status <= ormo.InOutStatusPartEnter {
if lowPrice <= 0 || needClose > 0 {
needClose -= 1
job.CloseOrders(&strat.ExitReq{
Tag: "cancel",
OrderID: odr.ID,
Force: true,
})
odMgr.ProcessOrders(job)
} else {
err := odr.SetEnterLimit(lowPrice)
if err == nil {
odr.EnterTag = fmt.Sprintf("long%d", int(longFillNum))
odr.SetStopLoss(&ormo.ExitTrigger{
Price: max(lowPrice-m.SlRange, lowPrice*0.1),
})
}
}
} else {
odr.SetStopLoss(&ormo.ExitTrigger{
Price: max(lowPrice-m.SlRange, odr.Enter.Price*0.1),
})
}
}
}
}
},
}
}