init
This commit is contained in:
91
com/common.go
Normal file
91
com/common.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/banbox/banbot/config"
|
||||
"github.com/banbox/banbot/core"
|
||||
"github.com/banbox/banbot/utils"
|
||||
ta "github.com/banbox/banta"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
)
|
||||
|
||||
const (
|
||||
MaSMA = "sma"
|
||||
MaEMA = "ema"
|
||||
MaRMA = "rma"
|
||||
MaWMA = "wma"
|
||||
MaVWMA = "vwma"
|
||||
MaHMA = "hma"
|
||||
MaKAMA = "kama"
|
||||
MaALMA = "alma"
|
||||
)
|
||||
|
||||
var MATypes = []string{MaSMA, MaEMA, MaRMA, MaWMA, MaVWMA, MaHMA, MaKAMA, MaALMA}
|
||||
|
||||
var maMap = make(map[string]bool)
|
||||
|
||||
func init() {
|
||||
for _, name := range MATypes {
|
||||
maMap[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func DefMaType(p *config.RunPolicyConfig, key, defVal string, allows ...string) string {
|
||||
if len(allows) == 0 {
|
||||
allows = MATypes
|
||||
} else {
|
||||
for _, k := range allows {
|
||||
if _, ok := maMap[k]; !ok {
|
||||
panic(fmt.Sprintf("unsupport maType: %s", k))
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := maMap[defVal]; !ok {
|
||||
panic(fmt.Sprintf("unsupport maType: %s", defVal))
|
||||
}
|
||||
defValInt := slices.Index(allows, defVal)
|
||||
if defValInt < 0 {
|
||||
defValInt = 0
|
||||
allows = append([]string{defVal}, allows...)
|
||||
}
|
||||
pVal := p.DefInt(key, defValInt, core.PUniform(0, float64(len(allows)-1)))
|
||||
return allows[pVal]
|
||||
}
|
||||
|
||||
func GetMA(price, vol *ta.Series, maType string, maLen int) *ta.Series {
|
||||
switch maType {
|
||||
case MaSMA:
|
||||
return ta.SMA(price, maLen)
|
||||
case MaEMA:
|
||||
return ta.EMA(price, maLen)
|
||||
case MaRMA:
|
||||
return ta.RMA(price, maLen)
|
||||
case MaWMA:
|
||||
return ta.WMA(price, maLen)
|
||||
case MaVWMA:
|
||||
return ta.VWMA(price, vol, maLen)
|
||||
case MaHMA:
|
||||
return ta.HMA(price, maLen)
|
||||
case MaKAMA:
|
||||
return ta.KAMA(price, maLen)
|
||||
case MaALMA:
|
||||
return ta.ALMA(price, maLen, 6, 0.85)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown maType: " + maType))
|
||||
}
|
||||
}
|
||||
|
||||
func GetTmpPath(name string) (string, error) {
|
||||
path, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpDir := filepath.Join(path, "tmp")
|
||||
err = utils.EnsureDir(tmpDir, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(tmpDir, name), nil
|
||||
}
|
||||
38
com/cross.go
Normal file
38
com/cross.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package com
|
||||
|
||||
import "math"
|
||||
|
||||
func NewCrossLog() *CrossLog {
|
||||
return &CrossLog{}
|
||||
}
|
||||
|
||||
type CrossLog struct {
|
||||
PrevVal float64
|
||||
Sign int // 最近一次交叉符号:1上穿,-1下穿
|
||||
XAt int // 最近一次交叉序号
|
||||
Num int // 当前序号
|
||||
}
|
||||
|
||||
func (l *CrossLog) Update(sub float64) {
|
||||
l.Num += 1
|
||||
if sub == 0 {
|
||||
return
|
||||
}
|
||||
if math.IsNaN(l.PrevVal) || l.PrevVal == 0 {
|
||||
l.PrevVal = sub
|
||||
} else {
|
||||
if factor := l.PrevVal * sub; factor < 0 {
|
||||
l.PrevVal = sub
|
||||
l.XAt = l.Num
|
||||
if sub > 0 {
|
||||
l.Sign = 1
|
||||
} else {
|
||||
l.Sign = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CrossLog) Get() int {
|
||||
return l.Sign * (l.Num - l.XAt + 1)
|
||||
}
|
||||
44
com/eff_trail_stop.go
Normal file
44
com/eff_trail_stop.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/utils"
|
||||
"math"
|
||||
)
|
||||
|
||||
type EFTRS struct {
|
||||
SLLong float64
|
||||
SLShort float64
|
||||
|
||||
EFLen int
|
||||
}
|
||||
|
||||
func NewEFTRS(efLen int) *EFTRS {
|
||||
return &EFTRS{EFLen: efLen}
|
||||
}
|
||||
|
||||
func (t *EFTRS) Calc(s *strat.StratJob, atr float64) (float64, float64) {
|
||||
e := s.Env
|
||||
c, c1 := e.Close.Get(0), e.Close.Get(1)
|
||||
totSum := utils.ArrSum(e.Close.Sub(e.Close.Back(1)).Abs().Range(0, t.EFLen))
|
||||
effRate := math.Abs(c-e.Close.Get(t.EFLen)) / totSum
|
||||
slLong := e.Low.Get(0) - atr
|
||||
slShort := e.High.Get(0) + atr
|
||||
if len(s.LongOrders) > 0 {
|
||||
if c > c1 {
|
||||
slLong = t.SLLong + effRate*(c-c1)
|
||||
} else {
|
||||
slLong = t.SLLong
|
||||
}
|
||||
}
|
||||
if len(s.ShortOrders) > 0 {
|
||||
if c < c1 {
|
||||
slShort = t.SLShort - effRate*(c1-c)
|
||||
} else {
|
||||
slShort = t.SLShort
|
||||
}
|
||||
}
|
||||
t.SLLong = slLong
|
||||
t.SLShort = slShort
|
||||
return slLong, slShort
|
||||
}
|
||||
28
com/ema.go
Normal file
28
com/ema.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package com
|
||||
|
||||
import "math"
|
||||
|
||||
type Ema struct {
|
||||
Alpha float64
|
||||
Val float64
|
||||
}
|
||||
|
||||
func NewEMA(alpha float64) *Ema {
|
||||
return &Ema{Alpha: alpha}
|
||||
}
|
||||
|
||||
func (e *Ema) Update(val float64) float64 {
|
||||
if math.IsNaN(val) || math.IsInf(val, 0) {
|
||||
return math.NaN()
|
||||
}
|
||||
if e.Val == 0 {
|
||||
e.Val = val
|
||||
} else {
|
||||
e.Val = e.Val*(1-e.Alpha) + val*e.Alpha
|
||||
}
|
||||
return e.Val
|
||||
}
|
||||
|
||||
func (e *Ema) Reset() {
|
||||
e.Val = 0
|
||||
}
|
||||
135
com/exm.go
Normal file
135
com/exm.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type ExmItem struct {
|
||||
BarNum int
|
||||
TimeMS int64
|
||||
Value float64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
type ExmTrace struct {
|
||||
Hist []*ExmItem
|
||||
Expect int // 1寻找极大值,-1寻找极小值
|
||||
PrevVals []float64 // 前一个值
|
||||
PrevDist float64 // 前两个极值的距离
|
||||
distList []float64 // 前面极值距离,保留最近3个
|
||||
}
|
||||
|
||||
/*
|
||||
Update
|
||||
val: 当前指标值
|
||||
pNum: 前一个索引
|
||||
ptime:前一个时间戳
|
||||
*/
|
||||
func (m *ExmTrace) Update(val float64, pNum int, ptime int64) bool {
|
||||
if math.IsNaN(val) {
|
||||
return false
|
||||
}
|
||||
if len(m.PrevVals) < 2 {
|
||||
// 至少积累2个
|
||||
if len(m.PrevVals) > 0 && m.PrevVals[len(m.PrevVals)-1] == val {
|
||||
// 和前一个值相同,跳过
|
||||
return false
|
||||
}
|
||||
m.PrevVals = append(m.PrevVals, val)
|
||||
return false
|
||||
}
|
||||
// 前1个,前2个
|
||||
p1, p2 := m.PrevVals[1], m.PrevVals[0]
|
||||
if p1 == val {
|
||||
// 当前值无效
|
||||
return false
|
||||
}
|
||||
// 更新
|
||||
m.PrevVals = append(m.PrevVals, val)
|
||||
m.PrevVals = m.PrevVals[len(m.PrevVals)-2:]
|
||||
pSub := p1 - p2
|
||||
sub := p1 - val
|
||||
var last *ExmItem
|
||||
if len(m.Hist) > 0 {
|
||||
last = m.Hist[len(m.Hist)-1]
|
||||
}
|
||||
getMinDist := func() float64 {
|
||||
if last == nil {
|
||||
return math.Abs(p1) * 0.3
|
||||
}
|
||||
if m.PrevDist != 0 {
|
||||
return m.PrevDist * 0.3
|
||||
}
|
||||
return max(math.Abs(last.Value), math.Abs(p1)) * 0.3
|
||||
}
|
||||
if pSub*sub <= 0 {
|
||||
// 非极大值或极小值
|
||||
if last != nil && !last.Valid && (val-last.Value)*float64(m.Expect) > getMinDist() {
|
||||
// 检查最后一个是否有效
|
||||
last.Valid = true
|
||||
if len(m.Hist) > 1 {
|
||||
dist := math.Abs(m.Hist[len(m.Hist)-2].Value - m.Hist[len(m.Hist)-1].Value)
|
||||
m.distList = append(m.distList, dist)
|
||||
if len(m.distList) > 3 {
|
||||
m.distList = m.distList[len(m.distList)-3:]
|
||||
}
|
||||
m.PrevDist = slices.Max(m.distList)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
valid := 0
|
||||
if sub > 0 {
|
||||
if m.Expect == 0 || m.Expect == 1 {
|
||||
// 新增极大值
|
||||
if last == nil || p1-last.Value >= getMinDist() {
|
||||
valid = -1
|
||||
}
|
||||
} else if p1 > last.Value {
|
||||
// 更新极大值
|
||||
valid = -1
|
||||
}
|
||||
} else if sub < 0 {
|
||||
if m.Expect == 0 || m.Expect == -1 {
|
||||
// 新增极小值
|
||||
if last == nil || last.Value-p1 >= getMinDist() {
|
||||
valid = 1
|
||||
}
|
||||
} else if p1 < last.Value {
|
||||
// 更新极小值
|
||||
valid = 1
|
||||
}
|
||||
}
|
||||
if valid != 0 {
|
||||
exm := &ExmItem{
|
||||
TimeMS: ptime,
|
||||
BarNum: pNum,
|
||||
Value: p1,
|
||||
}
|
||||
if m.Expect != valid {
|
||||
m.Hist = append(m.Hist, exm)
|
||||
//log.Info("add exm", zap.String("date", btime.ToDateStr(ptime, "")),
|
||||
// zap.Float64("val", p1))
|
||||
} else {
|
||||
m.Hist[len(m.Hist)-1] = exm
|
||||
//log.Info("update exm", zap.String("date", btime.ToDateStr(ptime, "")),
|
||||
// zap.Float64("val", p1))
|
||||
}
|
||||
if len(m.Hist) > 2 {
|
||||
dist := math.Abs(m.Hist[len(m.Hist)-3].Value - m.Hist[len(m.Hist)-2].Value)
|
||||
if len(m.distList) == 0 || dist != m.distList[len(m.distList)-1] {
|
||||
m.distList = append(m.distList, dist)
|
||||
if len(m.distList) > 3 {
|
||||
m.distList = m.distList[len(m.distList)-3:]
|
||||
}
|
||||
m.PrevDist = slices.Max(m.distList)
|
||||
}
|
||||
}
|
||||
m.Expect = valid
|
||||
m.PrevVals = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
41
com/inds.go
Normal file
41
com/inds.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package com
|
||||
|
||||
import "github.com/banbox/banta"
|
||||
|
||||
/*
|
||||
IchimokuCloud
|
||||
|
||||
sml: 9, mid: 26, big: 52, disp: 26
|
||||
return [smlMa, midMa, spanA, spanB]
|
||||
*/
|
||||
func IchimokuCloud(e *banta.BarEnv, sml, mid, big, disp int) *banta.Series {
|
||||
res := e.Close.To("_ichimokuCld", big*100000+mid*1000+disp*10+sml)
|
||||
if res.Cached() {
|
||||
return res
|
||||
}
|
||||
// earlier
|
||||
h := e.High.Back(disp - 1)
|
||||
l := e.Low.Back(disp - 1)
|
||||
hhSml := banta.Highest(h, sml).Get(0)
|
||||
llSml := banta.Lowest(l, sml).Get(0)
|
||||
hhMid := banta.Highest(h, mid).Get(0)
|
||||
llMid := banta.Lowest(l, mid).Get(0)
|
||||
hhBig := banta.Highest(h, big).Get(0)
|
||||
llBig := banta.Lowest(l, big).Get(0)
|
||||
|
||||
smlMa := (hhSml + llSml) / 2
|
||||
midMa := (hhMid + llMid) / 2
|
||||
bigMa := (hhBig + llBig) / 2
|
||||
spanA := (smlMa + midMa) / 2
|
||||
|
||||
// Current
|
||||
h, l = e.High, e.Low
|
||||
hhSml = banta.Highest(h, sml).Get(0)
|
||||
llSml = banta.Lowest(l, sml).Get(0)
|
||||
hhMid = banta.Highest(h, mid).Get(0)
|
||||
llMid = banta.Lowest(l, mid).Get(0)
|
||||
|
||||
smlMa2 := (hhSml + llSml) / 2
|
||||
midMa2 := (hhMid + llMid) / 2
|
||||
return res.Append([]float64{smlMa2, midMa2, spanA, bigMa})
|
||||
}
|
||||
65
com/slop.go
Normal file
65
com/slop.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Slop struct {
|
||||
lastCalc int
|
||||
sampleNum int
|
||||
Dirt int // 大周期斜率方向:1向上,0不明确,-1向下
|
||||
rate float64 // 斜率阈值,推荐0.25
|
||||
Data []float64
|
||||
Top float64
|
||||
Btm float64
|
||||
}
|
||||
|
||||
func NewSlop(sampleNum int, rate float64) *Slop {
|
||||
return &Slop{
|
||||
Dirt: 0,
|
||||
sampleNum: sampleNum,
|
||||
rate: rate,
|
||||
Top: math.NaN(),
|
||||
Btm: math.NaN(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Slop) Update(barNum int, slop float64) {
|
||||
s.Data = append(s.Data, math.Abs(slop))
|
||||
if len(s.Data) < s.sampleNum {
|
||||
return
|
||||
}
|
||||
if !math.IsNaN(s.Top) {
|
||||
s.Dirt = 0
|
||||
if slop >= s.Top {
|
||||
s.Dirt = 1
|
||||
} else if slop <= s.Btm {
|
||||
s.Dirt = -1
|
||||
}
|
||||
}
|
||||
if barNum-s.lastCalc < s.sampleNum/2 {
|
||||
return
|
||||
}
|
||||
if len(s.Data) > s.sampleNum {
|
||||
s.Data = s.Data[len(s.Data)-s.sampleNum:]
|
||||
}
|
||||
thres := CutScore(s.Data, s.rate)
|
||||
s.Top = thres
|
||||
s.Btm = -thres
|
||||
s.lastCalc = barNum
|
||||
s.Dirt = 0
|
||||
if slop >= s.Top {
|
||||
s.Dirt = 1
|
||||
} else if slop <= s.Btm {
|
||||
s.Dirt = -1
|
||||
}
|
||||
}
|
||||
|
||||
func CutScore(vals []float64, rate float64) float64 {
|
||||
arr := make([]float64, len(vals))
|
||||
copy(arr, vals)
|
||||
slices.Sort(arr)
|
||||
idx := int(float64(len(vals)) * rate)
|
||||
return arr[idx]
|
||||
}
|
||||
85
com/trailing_stop.go
Normal file
85
com/trailing_stop.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
ta "github.com/banbox/banta"
|
||||
"math"
|
||||
)
|
||||
|
||||
type TRS struct {
|
||||
MaxLow float64
|
||||
MinHigh float64
|
||||
SLLong float64 // 止损的tr倍率,有持仓时,逐渐减小
|
||||
SLShort float64 // 止损的tr倍率,有持仓时,逐渐减小
|
||||
InitSLTR float64
|
||||
minSLTR float64
|
||||
stepSLTR float64
|
||||
}
|
||||
|
||||
func NewTRS(initSLTR, minRate, costStep float64) *TRS {
|
||||
var minSLTR = initSLTR * minRate
|
||||
var stepSLTR = (initSLTR - minSLTR) / costStep
|
||||
return &TRS{
|
||||
InitSLTR: initSLTR,
|
||||
minSLTR: minSLTR,
|
||||
stepSLTR: stepSLTR,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TRS) TrialLong(price float64) {
|
||||
m.SLLong = m.InitSLTR
|
||||
m.MaxLow = price
|
||||
}
|
||||
|
||||
func (m *TRS) TrialShort(price float64) {
|
||||
m.SLShort = m.InitSLTR
|
||||
m.MinHigh = price
|
||||
}
|
||||
|
||||
func (m *TRS) setTRS(e *ta.BarEnv, longNum, shortNum int) {
|
||||
h, l := e.High.Get(0), e.Low.Get(0)
|
||||
if longNum == 0 {
|
||||
if m.SLLong != m.InitSLTR {
|
||||
m.MaxLow = 0
|
||||
}
|
||||
} else {
|
||||
m.MaxLow = max(m.MaxLow, l)
|
||||
if m.SLLong > m.minSLTR+0.01 {
|
||||
m.SLLong -= m.stepSLTR
|
||||
}
|
||||
}
|
||||
if shortNum == 0 {
|
||||
if m.SLShort != m.InitSLTR {
|
||||
m.MinHigh = 0
|
||||
}
|
||||
} else {
|
||||
m.MinHigh = min(m.MinHigh, h)
|
||||
if m.SLShort > m.minSLTR+0.01 {
|
||||
m.SLShort -= m.stepSLTR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TRS) Calc(s *strat.StratJob, atr float64) (float64, float64) {
|
||||
m.setTRS(s.Env, len(s.LongOrders), len(s.ShortOrders))
|
||||
var stopLong, stopShort float64
|
||||
if m.MaxLow > 0 {
|
||||
stopLong = m.MaxLow - atr*m.SLLong
|
||||
}
|
||||
if m.MinHigh > 0 {
|
||||
stopShort = m.MinHigh + atr*m.SLShort
|
||||
}
|
||||
return stopLong, stopShort
|
||||
}
|
||||
|
||||
func (m *TRS) CalcSL(s *strat.StratJob, atr *ta.Series, lenAtr, lenAtrB int, pow, rate float64) (float64, float64) {
|
||||
atrBase := ta.Lowest(ta.Highest(atr, lenAtr), lenAtrB).Get(0)
|
||||
atrRate := math.Pow(atr.Get(0)/atrBase, pow)
|
||||
return m.Calc(s, atrBase*atrRate*rate)
|
||||
}
|
||||
|
||||
func (m *TRS) CalcSL2(s *strat.StratJob, lenAtr, lenAtrB int, pow, rate float64) (float64, float64) {
|
||||
e := s.Env
|
||||
atr := ta.ATR(e.High, e.Low, e.Close, lenAtr)
|
||||
return m.CalcSL(s, atr, lenAtr, lenAtrB, pow, rate)
|
||||
}
|
||||
6
com/types.go
Normal file
6
com/types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package com
|
||||
|
||||
type CodeVal struct {
|
||||
Code string
|
||||
Val float64
|
||||
}
|
||||
51
com/utils.go
Normal file
51
com/utils.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package com
|
||||
|
||||
import (
|
||||
"github.com/banbox/banbot/strat"
|
||||
"github.com/banbox/banexg/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func FastOpen(s *strat.StratJob, dirt, slLong, slShort, limit float64) {
|
||||
openTag := "long"
|
||||
stopLoss := slLong
|
||||
if dirt < 0 {
|
||||
openTag = "short"
|
||||
stopLoss = slShort
|
||||
}
|
||||
if !s.CanOpen(dirt < 0) {
|
||||
return
|
||||
}
|
||||
err := s.OpenOrder(&strat.EnterReq{
|
||||
Tag: openTag,
|
||||
Short: dirt < 0,
|
||||
Limit: limit,
|
||||
StopLoss: stopLoss,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("open fail", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func MakePairInfos(warmNum int) func(s *strat.StratJob) []*strat.PairSub {
|
||||
return func(s *strat.StratJob) []*strat.PairSub {
|
||||
var bigTF string
|
||||
switch s.TimeFrame {
|
||||
case "1d":
|
||||
bigTF = "1w"
|
||||
case "4h":
|
||||
bigTF = "1d"
|
||||
case "1h":
|
||||
bigTF = "8h"
|
||||
case "15m":
|
||||
bigTF = "2h"
|
||||
case "5m":
|
||||
bigTF = "1h"
|
||||
case "1m":
|
||||
bigTF = "15m"
|
||||
}
|
||||
return []*strat.PairSub{
|
||||
{Pair: "_cur_", TimeFrame: bigTF, WarmupNum: warmNum},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user