166 lines
5.1 KiB
Go
166 lines
5.1 KiB
Go
package old
|
||
|
||
import (
|
||
"github.com/banbox/banbot/config"
|
||
"github.com/banbox/banbot/strat"
|
||
"github.com/banbox/banexg/log"
|
||
ta "github.com/banbox/banta"
|
||
"go.uber.org/zap"
|
||
"math"
|
||
"sort"
|
||
)
|
||
|
||
var (
|
||
lenAtr = 9
|
||
rangeRate = 0.6 // 预计bar利润区间比率:60%
|
||
ShakeTop = 0.2 // 只对分数前20%的标的进行震荡交易
|
||
minProfitPct = 0.01 // 最小目标盈利百分比
|
||
)
|
||
|
||
type TMore struct {
|
||
symbol string
|
||
shakeScore float64 // 震荡分数:(1-平均实体比率)*平均bar波动比率
|
||
btmPrice float64 // 平均向下突破比率
|
||
topPrice float64 // 平均向上突破比率
|
||
shakeOK bool // 是否允许震荡中开单
|
||
shakeHit float64 // 震荡首先触发位置:-1触发顶部,1触发底部
|
||
}
|
||
|
||
func Eat(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||
baseAtrLen := int(math.Round(float64(lenAtr) * 5))
|
||
return &strat.TradeStrat{
|
||
Version: 1,
|
||
WarmupNum: max(30, baseAtrLen),
|
||
MinTfScore: 0.75,
|
||
BatchInfo: true,
|
||
OnStartUp: func(s *strat.StratJob) {
|
||
s.More = &TMore{
|
||
symbol: s.Symbol.Symbol,
|
||
}
|
||
},
|
||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||
return []*strat.PairSub{
|
||
{Pair: "_cur_", TimeFrame: "8h", WarmupNum: 30},
|
||
}
|
||
},
|
||
OnBar: func(s *strat.StratJob) {
|
||
e := s.Env
|
||
h, l, c := e.High.Get(0), e.Low.Get(0), e.Close.Get(0)
|
||
m, _ := s.More.(*TMore)
|
||
if m.shakeOK {
|
||
// 允许震荡开单
|
||
if m.shakeHit == 0 {
|
||
if h >= m.topPrice {
|
||
m.shakeHit = -1
|
||
} else if l <= m.btmPrice {
|
||
m.shakeHit = 1
|
||
}
|
||
}
|
||
if s.OrderNum == 0 && m.shakeHit != 0 {
|
||
slRange := (m.topPrice - m.btmPrice)
|
||
tag := "long"
|
||
trigPrice := m.btmPrice
|
||
takeProfit := m.topPrice
|
||
if m.shakeHit < 0 {
|
||
tag = "short"
|
||
trigPrice = m.topPrice
|
||
takeProfit = m.btmPrice
|
||
}
|
||
stopLoss := trigPrice - slRange*m.shakeHit
|
||
slDst := (c - stopLoss) * m.shakeHit / slRange
|
||
tpDst := (takeProfit - c) * m.shakeHit / slRange
|
||
if slDst > 0.5 && tpDst > 0.7 {
|
||
// 到止损的距离不低于50%
|
||
err := s.OpenOrder(&strat.EnterReq{
|
||
Tag: tag,
|
||
Short: m.shakeHit < 0,
|
||
StopLoss: stopLoss,
|
||
TakeProfit: takeProfit,
|
||
})
|
||
//date := btime.ToDateStr(btime.TimeMS(), "")
|
||
//log.Info(fmt.Sprintf("%s %s open %s c:%.3f sl:%.3f, tp:%.3f", s.Symbols.Symbols,
|
||
// date, tag, c, stopLoss, takeProfit))
|
||
if err != nil {
|
||
log.Error("open fail", zap.Error(err))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||
// ma := ta.EMA(e.Close, 5)
|
||
rsi := ta.RSI(e.Close, 9)
|
||
|
||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||
atrRate := atr.Get(0) / atrBase
|
||
|
||
// 计算平均实体比率
|
||
o, h, l, c := e.Open.Get(0), e.High.Get(0), e.Low.Get(0), e.Close.Get(0)
|
||
ph, pl, pc := e.High.Get(1), e.Low.Get(1), e.Close.Get(1)
|
||
solidTop, solidBtm := max(o, c), min(o, c)
|
||
tRange, pRange := h-l, ph-pl
|
||
//tr := e.GetSeries("tRange")
|
||
//tr.Set(math.Sqrt(tRange * 100 / c))
|
||
//maTRate := ta.EMA(tr, 3)
|
||
solRate := e.Close.To("solRate", 0)
|
||
solRate.Set(math.Abs(o-c) / tRange)
|
||
maSRT := ta.EMA(solRate, 3).Get(0)
|
||
|
||
// 计算上下行比率
|
||
dnRate := e.Close.To("dnRate", 0)
|
||
dnRate.Set((l - pc) / pRange)
|
||
maDnRate := ta.EMA(dnRate, 5)
|
||
upRate := e.Close.To("upRate", 0)
|
||
upRate.Set((h - pc) / pRange)
|
||
maUpRate := ta.EMA(upRate, 5)
|
||
|
||
m, _ := s.More.(*TMore)
|
||
m.shakeScore = (1 - maSRT) // * maTRate.Get(0)
|
||
top := c + maUpRate.Get(0)*tRange
|
||
btm := c + maDnRate.Get(0)*tRange
|
||
edgeOff := (top - btm) * (1 - rangeRate) * 0.5
|
||
m.btmPrice = btm - edgeOff
|
||
m.topPrice = top + edgeOff
|
||
//m.maChg = (ma.Get(0) - ma.Get(1)) / pRange
|
||
topLineRate := (h - solidTop) / tRange
|
||
btmLineRate := (solidBtm - l) / tRange
|
||
lineDiff := math.Abs(topLineRate-btmLineRate) / max(topLineRate, btmLineRate)
|
||
rsiVal, pRsi := rsi.Get(0)-50, rsi.Get(1)-50
|
||
m.shakeOK = (top-btm)*rangeRate/c >= minProfitPct && lineDiff < 0.35 && maSRT < 0.3
|
||
m.shakeHit = 0
|
||
//log.Info(fmt.Sprintf("h: %.3f l:%.3f c:%.3f maSRT: %.3f upRate:%.3f dnRate:%.3f %v", h, l, c,
|
||
// maSRT.Get(0), maUpRate.Get(0), maDnRate.Get(0), m.shakeOK))
|
||
if math.Abs(rsiVal) > 20 || math.Abs(pRsi) > 20 {
|
||
// (ATR较大/反向影线占比大)和RSI都极端时,容易反转,禁止震荡开单
|
||
revLineRate := btmLineRate
|
||
if rsiVal > 0 {
|
||
revLineRate = topLineRate
|
||
}
|
||
if atrRate > 2.5 || atrRate > 1.3 && revLineRate > 0.75 {
|
||
m.shakeOK = false
|
||
}
|
||
}
|
||
},
|
||
OnBatchInfos: func(tf string, jobs map[string]*strat.JobEnv) {
|
||
// 筛选出所有币中shakeScore降序的前n个,然后在子周期中低买高卖
|
||
var mores = make([]*TMore, 0, len(jobs))
|
||
for _, job := range jobs {
|
||
m, _ := job.Job.More.(*TMore)
|
||
mores = append(mores, m)
|
||
}
|
||
var shakeTopN = int(math.Ceil(float64(len(jobs)) * ShakeTop))
|
||
sort.Slice(mores, func(i, j int) bool {
|
||
return mores[i].shakeScore > mores[j].shakeScore
|
||
})
|
||
for _, m := range mores {
|
||
if shakeTopN > 0 && m.shakeOK {
|
||
shakeTopN -= 1
|
||
} else {
|
||
m.shakeOK = false
|
||
}
|
||
}
|
||
},
|
||
}
|
||
}
|