Files
banstagy/ptn/hammer/common.go
2026-08-24 20:53:21 +08:00

180 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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