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 } }