init
This commit is contained in:
302
flux/grid/common.go
Normal file
302
flux/grid/common.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/banbox/banbot/btime"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banbot/utils"
|
||||
"github.com/banbox/banexg/log"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
CheckPos在每个bar的close上检查位置,是否需要补仓或止盈。
|
||||
bar中间的high/low忽略。所以尽量在1m周期执行,避免一个bar的high/low突破网格,close又收回来导致未执行的问题
|
||||
*/
|
||||
|
||||
func init() {
|
||||
strat.AddStratGroup("grid", map[string]strat.FuncMakeStrat{
|
||||
"inv": InvGrid,
|
||||
"long": LongGrid,
|
||||
})
|
||||
}
|
||||
|
||||
func NewGrid(initRate, maxAdd int, slRate float64, debug bool) *Grid {
|
||||
return &Grid{
|
||||
InitRate: float64(initRate),
|
||||
MaxAdd: maxAdd,
|
||||
SLRate: slRate,
|
||||
Debug: debug,
|
||||
}
|
||||
}
|
||||
|
||||
type Grid struct {
|
||||
InitRate float64 // 初次打开网格投入倍数
|
||||
MaxAdd int // 最大加仓次数
|
||||
SLRate float64 // 止损相对Unit的倍数
|
||||
Dirt float64 // 当前是做多网格还是做空;1做多,-1做空
|
||||
HoldSize float64 // 持有仓位倍率;相对单位开仓大小
|
||||
Unit float64 // 网格价格单位
|
||||
OneAmt float64 // 一份的数量,初次入场时计算
|
||||
EntPrice float64 // 入场价格
|
||||
Pos float64 // 网格位置,整数,-6/-5/-4/-3/-2/-1/0/1/2/3/4/5/6
|
||||
OldPos float64 // 上次网格位置
|
||||
openNeed bool // 是否需要打开加仓单
|
||||
takeNeed bool // 是否需要追加止盈单
|
||||
|
||||
Debug bool
|
||||
barLogs []string
|
||||
barMS int64
|
||||
}
|
||||
|
||||
func (m *Grid) UpdatePos(e *ta.BarEnv, price float64) {
|
||||
offRaw := (price - m.EntPrice) / m.Unit
|
||||
rate := math.Floor(math.Abs(offRaw) + 0.1)
|
||||
if offRaw < 0 {
|
||||
rate = -rate
|
||||
}
|
||||
if rate != m.Pos {
|
||||
m.Pos = rate
|
||||
m.takeNeed = true
|
||||
m.openNeed = true
|
||||
if m.Debug {
|
||||
base := m.EntPrice + m.Pos*m.Unit
|
||||
m.log(e, "upper: %.2f lower: %.2f", base+m.Unit, base-m.Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) Open(s *strat.StratJob) {
|
||||
c := s.Env.Close.Get(0)
|
||||
var tag = "short"
|
||||
if m.Dirt > 0 {
|
||||
tag = "long"
|
||||
}
|
||||
m.EntPrice = 0
|
||||
m.Pos = 0
|
||||
m.OldPos = 0
|
||||
m.HoldSize = 0
|
||||
m.OneAmt = s.Strat.GetStakeAmount(s) / c
|
||||
m.openNeed = true
|
||||
m.takeNeed = true
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: tag,
|
||||
Amount: m.InitRate * m.OneAmt,
|
||||
Short: m.Dirt < 0,
|
||||
StopLossVal: m.Unit * m.SLRate,
|
||||
})
|
||||
if m.Debug {
|
||||
m.log(s.Env, "init open, dir: %v, size: %v, grid: %.3f, pct: %.1f",
|
||||
m.Dirt, m.InitRate, m.Unit, m.Unit*100/c)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) CheckPos(s *strat.StratJob) {
|
||||
e := s.Env
|
||||
m.UpdatePos(e, e.Close.Get(0))
|
||||
if !m.openNeed && !m.takeNeed {
|
||||
return
|
||||
}
|
||||
odList := s.GetOrders(m.Dirt)
|
||||
// 取消未入场的触发订单
|
||||
cancels := make([]string, 0)
|
||||
var holds = make(map[string]int)
|
||||
for _, od := range odList {
|
||||
if od.Status < ormo.InOutStatusPartEnter {
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "cancel", OrderID: od.ID, Force: true})
|
||||
if m.Debug {
|
||||
cancels = append(cancels, strconv.FormatFloat(od.Enter.Price, 'f', 4, 64))
|
||||
}
|
||||
} else if m.Debug {
|
||||
num := int(math.Round(od.HoldAmount() / m.OneAmt))
|
||||
row := strconv.FormatFloat(od.Enter.Average, 'f', 2, 64)
|
||||
old, _ := holds[row]
|
||||
holds[row] = old + num
|
||||
}
|
||||
}
|
||||
if m.HoldSize < 0.1 {
|
||||
if m.Debug {
|
||||
m.log(e, "hold reset\n")
|
||||
}
|
||||
s.CloseOrders(&strat.ExitReq{Tag: "clear", Force: true})
|
||||
return
|
||||
}
|
||||
posChg := (m.Pos - m.OldPos) * m.Dirt
|
||||
m.OldPos = m.Pos
|
||||
jumpNum := math.Abs(posChg) - 1
|
||||
var jumpStr string
|
||||
if jumpNum > 0 {
|
||||
// 市场剧烈变化,此时最新价格已偏移多个网格
|
||||
extAmt := m.OneAmt * jumpNum
|
||||
tag := "jadd"
|
||||
if posChg > 0 {
|
||||
// 剧烈盈利,卖出额外持仓
|
||||
tag = "jexit"
|
||||
s.CloseOrders(&strat.ExitReq{
|
||||
Tag: tag,
|
||||
Amount: extAmt,
|
||||
FilledOnly: true,
|
||||
Force: true,
|
||||
})
|
||||
} else {
|
||||
// 剧烈亏损,买入额外持仓
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: tag,
|
||||
Amount: extAmt,
|
||||
Short: m.Dirt < 0,
|
||||
StopLoss: odList[0].GetStopLoss().Price,
|
||||
})
|
||||
}
|
||||
if m.Debug {
|
||||
jumpStr = fmt.Sprintf("jump %v %.0f ", tag, jumpNum)
|
||||
}
|
||||
}
|
||||
// 设置最新的止盈止损等待触发
|
||||
trigPrice := m.EntPrice + m.Pos*m.Unit
|
||||
openPrice, closePrice := trigPrice-m.Unit, trigPrice+m.Unit
|
||||
if m.Dirt < 0 {
|
||||
openPrice, closePrice = closePrice, openPrice
|
||||
}
|
||||
var openInfo string
|
||||
if m.openNeed {
|
||||
if -m.Pos*m.Dirt >= float64(m.MaxAdd) {
|
||||
// 达到最大加仓深度
|
||||
if m.Debug {
|
||||
openInfo = "open=nil "
|
||||
}
|
||||
} else {
|
||||
sl := odList[0].GetStopLoss()
|
||||
stopLoss := e.Close.Get(0) - m.Dirt*m.Unit*m.SLRate
|
||||
if sl != nil {
|
||||
stopLoss = sl.Price
|
||||
} else {
|
||||
log.Info("hit sl nil")
|
||||
}
|
||||
s.OpenOrder(&strat.EnterReq{
|
||||
Tag: "add",
|
||||
Limit: openPrice,
|
||||
Amount: m.OneAmt,
|
||||
Short: m.Dirt < 0,
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if m.Debug {
|
||||
openInfo = fmt.Sprintf("open=%.4f ", openPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.openNeed = false
|
||||
if m.takeNeed {
|
||||
s.SetAllTakeProfit(m.Dirt, &ormo.ExitTrigger{
|
||||
Price: closePrice,
|
||||
Rate: 1 / m.HoldSize,
|
||||
Tag: "exit",
|
||||
})
|
||||
//s.CloseOrders(&strat.ExitReq{
|
||||
// Tag: "exit",
|
||||
// Limit: closePrice,
|
||||
// Amount: m.OneAmt,
|
||||
// Force: true,
|
||||
//})
|
||||
}
|
||||
m.takeNeed = false
|
||||
if m.Debug {
|
||||
var takes = make(map[string]int)
|
||||
for _, od := range odList {
|
||||
tp := od.GetTakeProfit()
|
||||
if tp != nil {
|
||||
tpNum := math.Round(od.HoldAmount() / m.OneAmt)
|
||||
if tp.Rate > 0 && tp.Rate < 1 {
|
||||
tpNum *= tp.Rate
|
||||
}
|
||||
tpPrice := strconv.FormatFloat(tp.Price, 'f', 2, 64)
|
||||
old, _ := takes[tpPrice]
|
||||
takes[tpPrice] = old + int(math.Round(tpNum))
|
||||
}
|
||||
}
|
||||
var cancel string
|
||||
if len(cancels) > 0 {
|
||||
cancel = "cancel: [" + strings.Join(cancels, ", ") + "] "
|
||||
}
|
||||
hold := printAmts(holds, "hold: ")
|
||||
take := printAmts(takes, "take: ")
|
||||
m.log(e, cancel+jumpStr+openInfo+hold+take)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) OnOrderChange(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
if chgType == strat.OdChgEnterFill && od.Enter.Filled > core.AmtDust {
|
||||
// 加仓入场完成事件
|
||||
e := s.Env
|
||||
if m.EntPrice == 0 {
|
||||
// 设置第一个下跌买入和第一个盈利卖出
|
||||
m.EntPrice = od.Enter.Average
|
||||
sl := od.GetStopLoss()
|
||||
stoploss := float64(0)
|
||||
if sl != nil {
|
||||
stoploss = sl.Price
|
||||
}
|
||||
upgaps := make([]string, 0, 5)
|
||||
dngaps := make([]string, 0, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
dist := m.Unit * float64(i+1)
|
||||
upgaps = append(upgaps, strconv.FormatFloat(m.EntPrice+dist, 'f', 4, 64))
|
||||
dngaps = append(dngaps, strconv.FormatFloat(m.EntPrice-dist, 'f', 4, 64))
|
||||
}
|
||||
if m.Debug {
|
||||
m.log(e, "p=%.4f sl=%.4f", m.EntPrice, stoploss)
|
||||
m.log(e, "up: %v", strings.Join(upgaps, " "))
|
||||
m.log(e, "dn: %v", strings.Join(dngaps, " "))
|
||||
}
|
||||
}
|
||||
addNum := math.Round(od.Enter.Filled / m.OneAmt)
|
||||
m.HoldSize += addNum
|
||||
m.openNeed = addNum > 0
|
||||
if m.Debug {
|
||||
m.log(e, "[enter] %.4f %.0f hold %.0f",
|
||||
od.Enter.Average, addNum, m.HoldSize)
|
||||
}
|
||||
} else if chgType == strat.OdChgExitFill && od.Exit.Filled > core.AmtDust {
|
||||
e := s.Env
|
||||
exitNum := math.Round(od.Exit.Filled / m.OneAmt)
|
||||
m.HoldSize -= exitNum
|
||||
m.takeNeed = exitNum > 0
|
||||
if m.Debug {
|
||||
m.log(e, "[exit] %v %.4f -> %.4f amt %.0f hold %.0f",
|
||||
od.ExitTag, od.Enter.Average, od.Exit.Average, exitNum, m.HoldSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Grid) log(e *ta.BarEnv, tpl string, args ...any) {
|
||||
if e.TimeStart > m.barMS {
|
||||
content := strings.Join(m.barLogs, "\n\t")
|
||||
fmt.Printf(content)
|
||||
fmt.Printf("\n")
|
||||
date := btime.ToDateStr(e.TimeStart, core.DefaultDateFmt)
|
||||
m.barLogs = []string{date + fmt.Sprintf(" h: %.2f l: %.2f c: %.2f",
|
||||
e.High.Get(0), e.Low.Get(0), e.Close.Get(0))}
|
||||
m.barMS = e.TimeStart
|
||||
}
|
||||
m.barLogs = append(m.barLogs, fmt.Sprintf(tpl, args...))
|
||||
}
|
||||
|
||||
func printAmts(amts map[string]int, prefix string) string {
|
||||
if len(amts) == 0 {
|
||||
return ""
|
||||
}
|
||||
ents := utils.KeysOfMap(amts)
|
||||
slices.Sort(ents)
|
||||
for i, k := range ents {
|
||||
if amts[k] > 1 {
|
||||
ents[i] += " * " + strconv.Itoa(amts[k])
|
||||
}
|
||||
}
|
||||
return prefix + "[" + strings.Join(ents, ", ") + "] "
|
||||
}
|
||||
81
flux/grid/inv.go
Normal file
81
flux/grid/inv.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
多头网格,下跌买入,上涨卖出。适合波动性较强,方向不明的市场。
|
||||
|
||||
以大周期atrBase的一定比例作为网格的单位大小
|
||||
初始倍率: InitRate
|
||||
|
||||
初次开仓条件:
|
||||
大周期ER20 < 0.3
|
||||
小周期MA5 < MA20 看多网格;否则看空网格
|
||||
持有仓位:
|
||||
|
||||
*/
|
||||
|
||||
type GridV1 struct {
|
||||
*Grid
|
||||
bigER float64 // 大周期ER系数
|
||||
}
|
||||
|
||||
func InvGrid(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(float64(lenAtr) * 4.3)
|
||||
unitRate := pol.Def("unitRate", 2, core.PNorm(0.5, 5))
|
||||
initRate := int(pol.Def("initRate", 4, core.PNorm(0.7, 7.3)))
|
||||
maxAdd := 5
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
StopEnterBars: 9999999,
|
||||
TimeFrames: "1m",
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", 100},
|
||||
}
|
||||
},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
s.More = &GridV1{Grid: NewGrid(initRate, maxAdd, 6, false)}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*GridV1)
|
||||
if s.OrderNum == 0 {
|
||||
if m.Unit == 0 || m.bigER >= 0.1 || s.IsWarmUp {
|
||||
return
|
||||
}
|
||||
m.Open(s)
|
||||
} else {
|
||||
m.CheckPos(s)
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
m, _ := s.More.(*GridV1)
|
||||
m.bigER = ta.ER(e.Close, 20).Get(0)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
ma5 := ta.SMA(e.Close, 10).Get(0)
|
||||
ma20 := ta.SMA(e.Close, 50).Get(0)
|
||||
if s.OrderNum == 0 && !math.IsNaN(atrBase) {
|
||||
// 未持仓时更新网格单元大小
|
||||
m.Unit = atrBase * unitRate
|
||||
m.Dirt = core.OdDirtShort
|
||||
if ma5 < ma20 {
|
||||
m.Dirt = core.OdDirtLong
|
||||
}
|
||||
}
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
m, _ := s.More.(*GridV1)
|
||||
m.OnOrderChange(s, od, chgType)
|
||||
},
|
||||
}
|
||||
}
|
||||
83
flux/grid/long.go
Normal file
83
flux/grid/long.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/orm/ormo"
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
)
|
||||
|
||||
/*
|
||||
多头网格,下跌买入,上涨卖出。适合波动性较强,方向不明的市场。
|
||||
|
||||
以大周期atrBase的一定比例作为网格的单位大小
|
||||
初始倍率: InitRate
|
||||
|
||||
初次开仓条件:
|
||||
大周期ER20 < 0.3
|
||||
小周期MA5 < MA20 看多网格;否则看空网格
|
||||
持有仓位:
|
||||
|
||||
*/
|
||||
|
||||
type GridLong struct {
|
||||
*Grid
|
||||
bigER float64 // 大周期ER系数
|
||||
}
|
||||
|
||||
func LongGrid(pol *config.RunPolicyConfig) *strat.TradeStrat {
|
||||
lenAtr := 20
|
||||
baseAtrLen := int(float64(lenAtr) * 4.3)
|
||||
unitRate := pol.Def("unitRate", 2, core.PNorm(0.5, 5))
|
||||
initRate := int(pol.Def("initRate", 4, core.PNorm(0.7, 7.3)))
|
||||
maxAdd := 5
|
||||
return &strat.TradeStrat{
|
||||
WarmupNum: 100,
|
||||
StopEnterBars: 9999999,
|
||||
TimeFrames: "1m",
|
||||
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
|
||||
return []*strat.PairSub{
|
||||
{"_cur_", "4h", 100},
|
||||
}
|
||||
},
|
||||
OnStartUp: func(s *strat.StratJob) {
|
||||
g := NewGrid(initRate, maxAdd, 6, false)
|
||||
s.More = &GridLong{Grid: g}
|
||||
},
|
||||
OnBar: func(s *strat.StratJob) {
|
||||
m, _ := s.More.(*GridLong)
|
||||
if s.OrderNum == 0 {
|
||||
if m.Unit == 0 || s.IsWarmUp {
|
||||
return
|
||||
}
|
||||
m.Open(s)
|
||||
} else {
|
||||
m.CheckPos(s)
|
||||
}
|
||||
},
|
||||
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
|
||||
m, _ := s.More.(*GridLong)
|
||||
m.bigER = ta.ER(e.Close, 20).Get(0)
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0)
|
||||
//ma5 := ta.SMA(e.Close, 10).Get(0)
|
||||
//ma20 := ta.SMA(e.Close, 50).Get(0)
|
||||
if s.OrderNum == 0 && !math.IsNaN(atrBase) {
|
||||
// 未持仓时更新网格单元大小
|
||||
m.Unit = atrBase * unitRate
|
||||
m.Dirt = core.OdDirtLong
|
||||
//m.Dirt = core.OdDirtShort
|
||||
//if ma5 < ma20 {
|
||||
// m.Dirt = core.OdDirtLong
|
||||
//}
|
||||
}
|
||||
},
|
||||
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
|
||||
m, _ := s.More.(*GridLong)
|
||||
m.OnOrderChange(s, od, chgType)
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user