139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
package t01
|
||
|
||
import (
|
||
"github.com/banbox/banbot/orm/ormo"
|
||
"github.com/banbox/banbot/strat"
|
||
"github.com/banbox/banexg/log"
|
||
"github.com/banbox/banstagy/com"
|
||
ta "github.com/banbox/banta"
|
||
"go.uber.org/zap"
|
||
"math"
|
||
)
|
||
|
||
var (
|
||
// 这里是默认参数,请尽量在MainVX()中修改,不要在这里修改
|
||
maBig = 30
|
||
maSml = 30
|
||
maParent = 50
|
||
sampleNum = 500 // 斜率采样数量
|
||
sampleStep = 2 // 斜率计算间隔
|
||
atrLen = 14
|
||
openOffRate = float64(0) // 开单时,从均线偏移基准ATR的倍数
|
||
goodRate = float64(10) // 收益超过此倍数的ATR时,将止损更新为均线
|
||
badRate = float64(5) // 损失超过此倍数的ATR时,止损退出
|
||
)
|
||
|
||
type MoreType struct {
|
||
bigSlop *com.Slop
|
||
hitGoods map[int64]bool // 触发goodRate的订单ID,允许每个bar更新止损
|
||
}
|
||
|
||
func openOrder(s *strat.StratJob, closePrice, dirt, price, stopLoss float64) {
|
||
if (stopLoss-closePrice)*dirt >= 0 {
|
||
// 当前止损立刻满足,跳过
|
||
return
|
||
}
|
||
tag := "long"
|
||
if dirt < 0 {
|
||
tag = "short"
|
||
}
|
||
err := s.OpenOrder(&strat.EnterReq{
|
||
Tag: tag,
|
||
Limit: price,
|
||
StopLoss: stopLoss,
|
||
Short: dirt < 0,
|
||
})
|
||
if err != nil {
|
||
log.Error("open order fail", zap.Error(err))
|
||
}
|
||
}
|
||
|
||
func onStartUp(s *strat.StratJob) {
|
||
s.More = &MoreType{
|
||
bigSlop: com.NewSlop(sampleNum, 0.25),
|
||
hitGoods: make(map[int64]bool),
|
||
}
|
||
}
|
||
|
||
func onInfoBar(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||
sma := ta.SMA(e.Close, maParent)
|
||
prevSma := sma.Get(sampleStep)
|
||
if e.BarNum%sampleStep > 0 || math.IsNaN(prevSma) {
|
||
return
|
||
}
|
||
more, _ := s.More.(*MoreType)
|
||
curSma := sma.Get(0)
|
||
slop := (curSma - prevSma) / curSma
|
||
more.bigSlop.Update(e.BarNum, slop)
|
||
}
|
||
|
||
func onBar(s *strat.StratJob) {
|
||
e := s.Env
|
||
atr := ta.ATR(e.High, e.Low, e.Close, atrLen)
|
||
baseAtrLen := int(math.Round(float64(atrLen) * 4.3))
|
||
atrBase := ta.Lowest(ta.Highest(atr, atrLen), baseAtrLen).Get(0)
|
||
c := e.Close.Get(0)
|
||
smaBig := ta.SMA(e.Close, maBig)
|
||
smaVal := smaBig.Get(0)
|
||
// smaSml := ta.SMA(e.Close, maSml)
|
||
// maX := smaSml.Cross(smaBig)
|
||
// rsiVal := ta.RSI(e.Close, 20).Get(0)
|
||
|
||
more, _ := s.More.(*MoreType)
|
||
prevSma := smaBig.Get(sampleStep)
|
||
if e.BarNum%sampleStep == 0 && !math.IsNaN(prevSma) {
|
||
slop := (smaVal - prevSma) / smaVal
|
||
more.bigSlop.Update(e.BarNum, slop)
|
||
}
|
||
|
||
var longOd, shortOd *ormo.InOutOrder
|
||
if len(s.LongOrders) > 0 {
|
||
longOd = s.LongOrders[0]
|
||
}
|
||
if len(s.ShortOrders) > 0 {
|
||
shortOd = s.ShortOrders[0]
|
||
}
|
||
|
||
atrVal := atr.Get(0)
|
||
slRange := max(atrBase*badRate, atrVal*1.5)
|
||
|
||
// 检查是否触发盈利阈值,若触发更新止损
|
||
var orders = []*ormo.InOutOrder{longOd, shortOd}
|
||
for _, od := range orders {
|
||
if od != nil {
|
||
odDirt := 1.0
|
||
if od.Short {
|
||
odDirt = -1
|
||
}
|
||
isGood, ok := more.hitGoods[od.ID]
|
||
if !ok && (c-od.Enter.Average)*odDirt >= atrBase*goodRate {
|
||
more.hitGoods[od.ID] = true
|
||
isGood = true
|
||
}
|
||
if isGood {
|
||
// 已触发盈利阈值,每个bar更新止损
|
||
sl := od.GetStopLoss()
|
||
if sl == nil || (smaVal-sl.Price)*odDirt > 0 {
|
||
od.SetStopLoss(&ormo.ExitTrigger{Price: smaVal})
|
||
}
|
||
} else if od.Status == ormo.InOutStatusInit {
|
||
// 尚未入场,更新入场价格,更新止损
|
||
_ = od.SetEnterLimit(smaVal)
|
||
od.SetStopLoss(&ormo.ExitTrigger{Price: smaVal - odDirt*slRange})
|
||
}
|
||
}
|
||
}
|
||
if atrVal < atrBase {
|
||
// && more.bigSlop.Dirt > 0
|
||
if longOd == nil && more.bigSlop.Dirt >= 0 {
|
||
// 开多单 && c-smaVal+slRange > atrBase*0.5
|
||
openOrder(s, c, 1, 0, c-slRange)
|
||
}
|
||
// && more.bigSlop.Dirt < 0
|
||
if shortOd == nil && more.bigSlop.Dirt <= 0 {
|
||
// 开空单 && smaVal+slRange-c > atrBase*0.5
|
||
openOrder(s, c, -1, 0, c+slRange)
|
||
}
|
||
}
|
||
}
|