180 lines
5.2 KiB
Go
180 lines
5.2 KiB
Go
package hammer
|
||
|
||
import (
|
||
"fmt"
|
||
"github.com/banbox/banbot/com"
|
||
"github.com/banbox/banbot/orm/ormo"
|
||
"github.com/banbox/banbot/strat"
|
||
"github.com/banbox/banexg"
|
||
"github.com/banbox/banstagy/ptn"
|
||
ta "github.com/banbox/banta"
|
||
"math"
|
||
)
|
||
|
||
type OdInfo struct {
|
||
btmRate float64 // 锤子下影线占价格的比率
|
||
closeRate float64 // 锤子收盘价占价格比率,与上一个相同说明是阴线
|
||
}
|
||
|
||
type MoreHammer struct {
|
||
ents [][2]float64 // 前面信号的特征:atrBase比率,回撤比率
|
||
backRate float64 // 历史平均回撤比率
|
||
lastEnt int // 上次出现信号时BarNum
|
||
entBar *banexg.Kline // 上次信号的Bar
|
||
minPrice float64 // 信号后最低价格
|
||
minAt int // 信号后最低价格索引
|
||
atrTimes float64 // 信号是atrBase的倍数
|
||
odInfos map[int64]*OdInfo // 订单ID对应的信息
|
||
}
|
||
|
||
func StartWatch(e *ta.BarEnv, more *MoreHammer, bar *banexg.Kline, atrTimes float64) {
|
||
more.lastEnt = e.BarNum
|
||
more.entBar = bar
|
||
more.atrTimes = atrTimes
|
||
}
|
||
|
||
func UpdateWatch(s *strat.StratJob, more *MoreHammer, c, l float64, watchMin, watchMax, refNum int) {
|
||
distNum := s.Env.BarNum - more.lastEnt
|
||
bar := more.entBar
|
||
if distNum > watchMax || distNum > watchMin && (c-bar.Close)/(max(bar.Open, bar.Close)-bar.Low) > 0.2 {
|
||
// 超过最大观察个数;或者盈利超过锤子的20%,退出观察
|
||
CloseWatch(s, more, refNum)
|
||
return
|
||
}
|
||
// 处于有效观察期内,更新
|
||
if more.minPrice == 0 {
|
||
more.minPrice = l
|
||
} else if l < more.minPrice {
|
||
more.minPrice = l
|
||
more.minAt = distNum
|
||
if more.minPrice < bar.Low {
|
||
CloseWatch(s, more, refNum)
|
||
}
|
||
}
|
||
}
|
||
|
||
func CloseWatch(s *strat.StratJob, more *MoreHammer, refNum int) {
|
||
if more.lastEnt == 0 {
|
||
return
|
||
}
|
||
defer func() {
|
||
more.lastEnt = 0
|
||
more.atrTimes = 0
|
||
more.minPrice = 0
|
||
more.minAt = 0
|
||
more.entBar = nil
|
||
}()
|
||
bar := more.entBar
|
||
if more.minPrice == 0 || more.minPrice < bar.Low {
|
||
// 价格突破锤子下影线,不再看涨,不作为信号参考
|
||
return
|
||
}
|
||
backUp := bar.Close - bar.Low
|
||
backRate := (bar.Close - more.minPrice) / backUp
|
||
backRate = max(0, backRate)
|
||
more.ents = append(more.ents, [2]float64{more.atrTimes, backRate})
|
||
if len(more.ents) > refNum {
|
||
more.ents = more.ents[:refNum]
|
||
}
|
||
sumRate := float64(0)
|
||
for _, it := range more.ents {
|
||
sumRate += it[1]
|
||
}
|
||
more.backRate = sumRate / float64(len(more.ents))
|
||
backUpRate := backUp / bar.Close
|
||
line := fmt.Sprintf("%v,%.1f,%.1f,%.1f,%v", s.Symbol.Symbol, backUpRate*100,
|
||
more.atrTimes, backRate*100, more.minAt)
|
||
s.Strat.WriteOutput(line, true)
|
||
}
|
||
|
||
func makeOnBar(atrLen, refNum, watchMin, watchMax int, baseRate float64) func(s *strat.StratJob) {
|
||
return func(s *strat.StratJob) {
|
||
e := s.Env
|
||
ma5 := ta.SMA(e.Close, 5).Get(0)
|
||
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)
|
||
maSml := ta.SMA(e.Close, 50).Get(0)
|
||
maBig := ta.SMA(e.Close, 300).Get(0)
|
||
|
||
more, _ := s.More.(*MoreHammer)
|
||
|
||
o, h, l, c := ptn.GetMains(e, 0)
|
||
if math.IsNaN(atrBase) || c < maSml && maSml < maBig {
|
||
return
|
||
}
|
||
price := com.GetPrice(s.Symbol.Symbol, "")
|
||
_ = c == price
|
||
v := e.Volume.Get(0)
|
||
solid := math.Abs(o - c)
|
||
btm := min(o, c) - l
|
||
up := h - max(o, c)
|
||
if btm > max(2.0*solid, up*5.0, atrBase*baseRate) && c < ma5 {
|
||
// 检查是否处于信号后观察模式
|
||
CloseWatch(s, more, refNum)
|
||
// 开始新观察
|
||
bar := &banexg.Kline{Time: e.TimeStart, Open: o, High: h, Low: l, Close: c, Volume: v}
|
||
StartWatch(e, more, bar, btm/atrBase)
|
||
// 创建入场订单
|
||
limit := c - (c-l)*more.backRate*0.55
|
||
stopLoss := c - atr.Get(0)*3.0
|
||
if stopLoss >= min(limit, c) {
|
||
stopLoss = min(limit, c) - atr.Get(0)
|
||
}
|
||
err := s.OpenOrder(&strat.EnterReq{
|
||
Tag: "long",
|
||
Limit: limit,
|
||
StopLoss: stopLoss,
|
||
})
|
||
if err != nil {
|
||
fmt.Printf("open fail %v", err)
|
||
}
|
||
} else if more.lastEnt > 0 {
|
||
// 更新观察数据
|
||
UpdateWatch(s, more, c, l, watchMin, watchMax, refNum)
|
||
}
|
||
}
|
||
}
|
||
|
||
func onOrderChange(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||
if chgType != strat.OdChgEnter {
|
||
// 只监听入场事件
|
||
return
|
||
}
|
||
more, _ := s.More.(*MoreHammer)
|
||
if more.entBar == nil {
|
||
return
|
||
}
|
||
bar := more.entBar
|
||
closePct := (bar.Close - bar.Low) / bar.Close
|
||
btmPct := (min(bar.Open, bar.Close) - bar.Low) / bar.Close
|
||
more.odInfos[od.ID] = &OdInfo{
|
||
btmRate: btmPct,
|
||
closeRate: closePct,
|
||
}
|
||
}
|
||
|
||
/*
|
||
生成回撤止损函数
|
||
第一个alpha参数调整反弹值
|
||
thres是二维数组,每一维2个,第一个是反弹阈值,第二个是返回的止损阈值;应按第一个降序排列
|
||
*/
|
||
func makeDrawDownExit(alpha float64, thres [][2]float64) func(s *strat.StratJob, od *ormo.InOutOrder, maxChg float64) float64 {
|
||
return func(s *strat.StratJob, od *ormo.InOutOrder, maxChg float64) float64 {
|
||
more, _ := s.More.(*MoreHammer)
|
||
info, ok := more.odInfos[od.ID]
|
||
if !ok {
|
||
return -1
|
||
}
|
||
// 盈利比率占锤子反弹的比率
|
||
// 建议超参数测试顺序:case upRate -> upRate 尾部系数 -> switch rate -> btmRate/closeRate
|
||
upRate := maxChg / info.btmRate * alpha
|
||
for _, item := range thres {
|
||
if upRate > item[0] {
|
||
return item[1]
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
}
|