init
This commit is contained in:
18
ptn/all/main.go
Normal file
18
ptn/all/main.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banstagy/ptn"
|
||||
"github.com/banbox/banstagy/ptn/hammer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
strat.AddStratGroup("ptn", map[string]strat.FuncMakeStrat{
|
||||
"hm1": hammer.MainV1,
|
||||
"hm2": hammer.MainV2,
|
||||
"hm3": hammer.MainV3,
|
||||
"hm4": hammer.MainV4,
|
||||
"hm5": hammer.MainV5,
|
||||
"tunMo": ptn.TunMo,
|
||||
})
|
||||
}
|
||||
62
ptn/classic.go
Normal file
62
ptn/classic.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package ptn
|
||||
|
||||
import (
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
func getOHLC(e *ta.BarEnv, i int) (float64, float64, float64, float64) {
|
||||
return e.Open.Get(i), e.High.Get(i), e.Low.Get(i), e.Close.Get(i)
|
||||
}
|
||||
|
||||
type EngulfingRes struct {
|
||||
Bottom float64
|
||||
Top float64
|
||||
}
|
||||
|
||||
func makeEngulfingRes(dirt int, minVal, maxVal float64) *PtnRes {
|
||||
return &PtnRes{
|
||||
Type: PtnEngulfing,
|
||||
Dirt: dirt,
|
||||
Confidence: 1,
|
||||
Data: &EngulfingRes{
|
||||
Bottom: minVal,
|
||||
Top: maxVal,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
检查是否是吞没形态
|
||||
1 看涨吞没 -1看跌吞没
|
||||
*/
|
||||
func isEngulfing(e *ta.BarEnv) *PtnRes {
|
||||
o, h, l, c := getOHLC(e, 0)
|
||||
o1, h1, l1, c1 := getOHLC(e, 1)
|
||||
solid := math.Abs(o - c)
|
||||
if solid/(h-l) < 0.6 {
|
||||
// 第二个蜡烛实体至少60%
|
||||
return nil
|
||||
}
|
||||
solid1 := math.Abs(o1 - c1)
|
||||
if solid1/(h1-l1) <= 0.13 && solid > solid1*3 {
|
||||
// 第一个是十字星,直接符合
|
||||
if o > c {
|
||||
return makeEngulfingRes(-1, min(l, l1), max(h, h1))
|
||||
} else {
|
||||
return makeEngulfingRes(1, min(l, l1), max(h, h1))
|
||||
}
|
||||
}
|
||||
if o < c {
|
||||
// 后一个是阳线,要求前一个是阴线
|
||||
if o1 > c1 && o1 <= c && c1 >= o {
|
||||
// 前开<收盘 & 前收 > 开盘
|
||||
return makeEngulfingRes(1, min(l, l1), max(h, h1))
|
||||
}
|
||||
} else if o1 < c1 && c1 <= o && o1 >= c {
|
||||
// 后一个阴线,要求前一个阳线
|
||||
// 前收<开盘 & 前开 > 收盘
|
||||
return makeEngulfingRes(-1, min(l, l1), max(h, h1))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
28
ptn/common.go
Normal file
28
ptn/common.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package ptn
|
||||
|
||||
import (
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
func GetMains(e *ta.BarEnv, i int) (float64, float64, float64, float64) {
|
||||
o, h, l, c := e.Open.Get(i), e.High.Get(i), e.Low.Get(i), e.Close.Get(i)
|
||||
return o, h, l, c
|
||||
}
|
||||
|
||||
type FuncPtnRecognize = func(e *ta.BarEnv) *PtnRes
|
||||
|
||||
var (
|
||||
recognizers = []FuncPtnRecognize{
|
||||
isEngulfing,
|
||||
}
|
||||
)
|
||||
|
||||
func Recognize(e *ta.BarEnv) *PtnRes {
|
||||
for _, reg := range recognizers {
|
||||
res := reg(e)
|
||||
if res != nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
6
ptn/data.go
Normal file
6
ptn/data.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package ptn
|
||||
|
||||
const (
|
||||
PtnUnknown = iota
|
||||
PtnEngulfing // 吞没形态
|
||||
)
|
||||
179
ptn/hammer/common.go
Normal file
179
ptn/hammer/common.go
Normal file
@@ -0,0 +1,179 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
45
ptn/hammer/v1.go
Normal file
45
ptn/hammer/v1.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package hammer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
const v1AtrLen = 14
|
||||
|
||||
func MainV1(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
Version: 1,
|
||||
WarmupNum: 30,
|
||||
MinTfScore: 0.75,
|
||||
DrawDownExit: true,
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
ma5 := ta.SMA(e.Close, 5).Get(0)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, v1AtrLen)
|
||||
baseAtrLen := int(math.Round(float64(v1AtrLen) * 4.3))
|
||||
atrBase := ta.Lowest(ta.Highest(atr, v1AtrLen), baseAtrLen).Get(0)
|
||||
|
||||
if math.IsNaN(atrBase) {
|
||||
return
|
||||
}
|
||||
o, h, l, c := e.Open.Get(0), e.High.Get(0), e.Low.Get(0), e.Close.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*3) && c < ma5 {
|
||||
stopLoss := c - atr.Get(0)*3.0
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("open fail %v", err)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
27
ptn/hammer/v2.go
Normal file
27
ptn/hammer/v2.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package hammer
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
const (
|
||||
v2AtrLen = 14
|
||||
v2WatchMin = 5 // 入场后观察后续蜡烛的最少个数
|
||||
v2WatchMax = 100 // 入场后观察后续蜡烛的最多个数
|
||||
v2RefNum = 20 // 回顾历史回撤的数量
|
||||
)
|
||||
|
||||
func MainV2(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
Version: 1,
|
||||
WarmupNum: 3000,
|
||||
MinTfScore: 0.75,
|
||||
DrawDownExit: true,
|
||||
StopEnterBars: 10,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &MoreHammer{}
|
||||
},
|
||||
OnBar: makeOnBar(v2AtrLen, v2RefNum, v2WatchMin, v2WatchMax, 3),
|
||||
}
|
||||
}
|
||||
35
ptn/hammer/v3.go
Normal file
35
ptn/hammer/v3.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package hammer
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
const (
|
||||
v3AtrLen = 14
|
||||
v3WatchMin = 5 // 入场后观察后续蜡烛的最少个数
|
||||
v3WatchMax = 100 // 入场后观察后续蜡烛的最多个数
|
||||
v3RefNum = 20 // 回顾历史回撤的数量
|
||||
)
|
||||
|
||||
func MainV3(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
Version: 1,
|
||||
WarmupNum: 3000,
|
||||
MinTfScore: 0.75,
|
||||
DrawDownExit: true,
|
||||
StopEnterBars: 10,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &MoreHammer{
|
||||
odInfos: map[int64]*OdInfo{},
|
||||
}
|
||||
},
|
||||
OnBar: makeOnBar(v3AtrLen, v3RefNum, v3WatchMin, v3WatchMax, 3),
|
||||
OnOrderChange: onOrderChange,
|
||||
GetDrawDownExitRate: makeDrawDownExit(0.8, [][2]float64{
|
||||
{0.625, 0.25},
|
||||
{0.3, 0.37},
|
||||
{0.15, 0.5},
|
||||
}),
|
||||
}
|
||||
}
|
||||
35
ptn/hammer/v4.go
Normal file
35
ptn/hammer/v4.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package hammer
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
)
|
||||
|
||||
const (
|
||||
v4AtrLen = 14
|
||||
v4WatchMin = 5 // 入场后观察后续蜡烛的最少个数
|
||||
v4WatchMax = 100 // 入场后观察后续蜡烛的最多个数
|
||||
v4RefNum = 20 // 回顾历史回撤的数量
|
||||
)
|
||||
|
||||
func MainV4(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
Version: 1,
|
||||
WarmupNum: 3000,
|
||||
MinTfScore: 0.75,
|
||||
DrawDownExit: true,
|
||||
StopEnterBars: 10,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &MoreHammer{
|
||||
odInfos: map[int64]*OdInfo{},
|
||||
}
|
||||
},
|
||||
OnBar: makeOnBar(v4AtrLen, v4RefNum, v4WatchMin, v4WatchMax, 2.76),
|
||||
OnOrderChange: onOrderChange,
|
||||
GetDrawDownExitRate: makeDrawDownExit(0.8, [][2]float64{
|
||||
{0.625, 0.25},
|
||||
{0.3, 0.37},
|
||||
{0.15, 0.5},
|
||||
}),
|
||||
}
|
||||
}
|
||||
96
ptn/hammer/v5.go
Normal file
96
ptn/hammer/v5.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package hammer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
/*
|
||||
加入RSI低于55限制,同时调低atr倍率,允许更多机会。
|
||||
测试效果不如V4,弃用
|
||||
*/
|
||||
|
||||
const (
|
||||
v5AtrLen = 14
|
||||
v5WatchMin = 5 // 入场后观察后续蜡烛的最少个数
|
||||
v5WatchMax = 100 // 入场后观察后续蜡烛的最多个数
|
||||
v5RefNum = 20 // 回顾历史回撤的数量
|
||||
)
|
||||
|
||||
func makeV5OnBar(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)
|
||||
|
||||
rsiCol := ta.RSI(e.Close, 20)
|
||||
rsi, rsiPrv := rsiCol.Get(0), rsiCol.Get(1)
|
||||
|
||||
more, _ := s.More.(*MoreHammer)
|
||||
|
||||
if math.IsNaN(atrBase) {
|
||||
return
|
||||
}
|
||||
rsiOk := rsiPrv >= 70 && rsiPrv-rsi > 30 || (rsiPrv+rsi)/2 < 55
|
||||
if !rsiOk {
|
||||
return
|
||||
}
|
||||
o, h, l, c := e.Open.Get(0), e.High.Get(0), e.Low.Get(0), e.Close.Get(0)
|
||||
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 >= limit {
|
||||
stopLoss = limit - 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 MainV5(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
return &strat.TradeStrat{
|
||||
Version: 1,
|
||||
WarmupNum: 3000,
|
||||
MinTfScore: 0.75,
|
||||
DrawDownExit: true,
|
||||
StopEnterBars: 10,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &MoreHammer{
|
||||
odInfos: map[int64]*OdInfo{},
|
||||
}
|
||||
},
|
||||
OnBar: makeV5OnBar(v5AtrLen, v5RefNum, v5WatchMin, v5WatchMax, 2.2),
|
||||
OnOrderChange: onOrderChange,
|
||||
GetDrawDownExitRate: makeDrawDownExit(0.8, [][2]float64{
|
||||
{0.625, 0.25},
|
||||
{0.3, 0.37},
|
||||
{0.15, 0.5},
|
||||
}),
|
||||
}
|
||||
}
|
||||
110
ptn/tunmo.go
Normal file
110
ptn/tunmo.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package ptn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
type tunmoMore struct {
|
||||
topVal float64
|
||||
btmVal float64
|
||||
barNum int
|
||||
}
|
||||
|
||||
/*
|
||||
检查是否是吞没形态
|
||||
*/
|
||||
func isTunMo(e *ta.BarEnv) (int, float64, float64) {
|
||||
o, h, l, c := GetMains(e, 0)
|
||||
o1, h1, l1, c1 := GetMains(e, 1)
|
||||
solid := math.Abs(o - c)
|
||||
if solid/(h-l) < 0.6 {
|
||||
// 第二个蜡烛实体至少60%
|
||||
return 0, 0, 0
|
||||
}
|
||||
solid1 := math.Abs(o1 - c1)
|
||||
if solid1/(h1-l1) <= 0.13 && solid > solid1*3 {
|
||||
// 第一个是十字星,直接符合
|
||||
if o > c {
|
||||
return -1, min(l, l1), max(h, h1)
|
||||
} else {
|
||||
return 1, min(l, l1), max(h, h1)
|
||||
}
|
||||
}
|
||||
if o < c {
|
||||
// 后一个是阳线,要求前一个是阴线
|
||||
if o1 > c1 && o1 <= c && c1 >= o {
|
||||
// 前开<收盘 & 前收 > 开盘
|
||||
return 1, min(l, l1), max(h, h1)
|
||||
}
|
||||
} else if o1 < c1 && c1 <= o && o1 >= c {
|
||||
// 后一个阴线,要求前一个阳线
|
||||
// 前收<开盘 & 前开 > 收盘
|
||||
return -1, min(l, l1), max(h, h1)
|
||||
}
|
||||
return 0, 0, 0
|
||||
}
|
||||
|
||||
func TunMo(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
const v1AtrLen = 14
|
||||
return &strat.TradeStrat{
|
||||
Version: 1,
|
||||
WarmupNum: 30,
|
||||
MinTfScore: 0.75,
|
||||
DrawDownExit: true,
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &tunmoMore{}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
// ma5 := ta.SMA(e.Close, 5).Get(1)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, v1AtrLen)
|
||||
baseAtrLen := int(math.Round(float64(v1AtrLen) * 4.3))
|
||||
atrBase := ta.Lowest(ta.Highest(atr, v1AtrLen), baseAtrLen).Get(0)
|
||||
|
||||
//rsiCol := ta.RSI(e.Close, 14)
|
||||
//rsi := rsiCol.Range(0, 2)
|
||||
|
||||
if math.IsNaN(atrBase) || s.OrderNum > 0 {
|
||||
return
|
||||
}
|
||||
m, _ := s.More.(*tunmoMore)
|
||||
mode, btmVal, topVal := isTunMo(e)
|
||||
if mode != 0 && atr.Get(0) > atrBase*2 {
|
||||
m.btmVal = btmVal - atrBase
|
||||
m.topVal = topVal + atrBase
|
||||
m.barNum = e.BarNum
|
||||
}
|
||||
if e.BarNum-m.barNum > 10 {
|
||||
// 忽略吞没形态10个之后的蜡烛
|
||||
return
|
||||
}
|
||||
c := e.Close.Get(0)
|
||||
// slices.Max(rsi) > 70
|
||||
// && c > ma5
|
||||
if c > m.topVal {
|
||||
stopLoss := max(m.topVal, c) + atr.Get(0)
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "short",
|
||||
StopLoss: stopLoss,
|
||||
Short: true,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("open fail %v", err)
|
||||
}
|
||||
} else if c < m.btmVal {
|
||||
stopLoss := min(m.btmVal, c) - atr.Get(0)
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "long",
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("open fail %v", err)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
8
ptn/types.go
Normal file
8
ptn/types.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package ptn
|
||||
|
||||
type PtnRes struct {
|
||||
Type int
|
||||
Dirt int // 此形态看多1还是看空-1
|
||||
Confidence float64
|
||||
Data interface{}
|
||||
}
|
||||
Reference in New Issue
Block a user