mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
feature: add gma, add wdrift, export drift filter, fix: LastPrice truncation
This commit is contained in:
parent
94615f7ecf
commit
6b6a24a655
|
@ -28,28 +28,32 @@ exchangeStrategies:
|
|||
# kline interval for indicators
|
||||
interval: 15m
|
||||
window: 2
|
||||
stoploss: 0.3%
|
||||
stoploss: 4.3%
|
||||
source: close
|
||||
predictOffset: 2
|
||||
noTrailingStopLoss: false
|
||||
noTrailingStopLoss: true
|
||||
trailingStopLossType: kline
|
||||
# stddev on high/low-source
|
||||
hlVarianceMultiplier: 0.23
|
||||
hlVarianceMultiplier: 0.1
|
||||
hlRangeWindow: 5
|
||||
window1m: 24
|
||||
smootherWindow1m: 24
|
||||
fisherTransformWindow1m: 162
|
||||
smootherWindow: 1
|
||||
fisherTransformWindow: 9
|
||||
window1m: 49
|
||||
smootherWindow1m: 80
|
||||
fisherTransformWindow1m: 74
|
||||
smootherWindow: 3
|
||||
fisherTransformWindow: 160
|
||||
atrWindow: 14
|
||||
# orders not been traded will be canceled after `pendingMinutes` minutes
|
||||
pendingMinutes: 3
|
||||
pendingMinutes: 10
|
||||
noRebalance: true
|
||||
trendWindow: 12
|
||||
rebalanceFilter: 1.5
|
||||
|
||||
trailingActivationRatio: [0.004]
|
||||
trailingCallbackRate: [0.001]
|
||||
trailingActivationRatio: [0.003]
|
||||
trailingCallbackRate: [0.0006]
|
||||
driftFilterPos: 1.2
|
||||
driftFilterNeg: -1.2
|
||||
ddriftFilterPos: 0.4
|
||||
ddriftFilterNeg: -0.4
|
||||
|
||||
generateGraph: true
|
||||
graphPNLDeductFee: true
|
||||
|
|
|
@ -27,45 +27,51 @@ exchangeStrategies:
|
|||
symbol: BTCUSDT
|
||||
# kline interval for indicators
|
||||
interval: 15m
|
||||
window: 2
|
||||
stoploss: 0.2%
|
||||
window: 1
|
||||
stoploss: 1.2%
|
||||
source: close
|
||||
predictOffset: 2
|
||||
noTrailingStopLoss: false
|
||||
trailingStopLossType: kline
|
||||
# stddev on high/low-source
|
||||
hlVarianceMultiplier: 0.22
|
||||
hlVarianceMultiplier: 0.1
|
||||
hlRangeWindow: 5
|
||||
smootherWindow: 1
|
||||
fisherTransformWindow: 9
|
||||
window1m: 22
|
||||
smootherWindow1m: 18
|
||||
fisherTransformWindow1m: 162
|
||||
fisherTransformWindow: 34
|
||||
window1m: 58
|
||||
smootherWindow1m: 118
|
||||
fisherTransformWindow1m: 319
|
||||
atrWindow: 14
|
||||
# orders not been traded will be canceled after `pendingMinutes` minutes
|
||||
pendingMinutes: 5
|
||||
pendingMinutes: 10
|
||||
noRebalance: true
|
||||
trendWindow: 576
|
||||
rebalanceFilter: 0
|
||||
driftFilterPos: 1.8
|
||||
driftFilterNeg: -1.8
|
||||
ddriftFilterPos: 0.5
|
||||
ddriftFilterNeg: -0.5 #-1.6
|
||||
|
||||
# ActivationRatio should be increasing order
|
||||
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
|
||||
#trailingActivationRatio: [0.01, 0.016, 0.05]
|
||||
trailingActivationRatio: [0.0012, 0.002, 0.01, 0.016]
|
||||
#trailingActivationRatio: [0.001, 0.0081, 0.022]
|
||||
trailingActivationRatio: [0.0029, 0.028]
|
||||
#trailingActivationRatio: []
|
||||
#trailingCallbackRate: []
|
||||
#trailingCallbackRate: [0.002, 0.01, 0.1]
|
||||
trailingCallbackRate: [0.0004, 0.0008, 0.002, 0.01]
|
||||
#trailingCallbackRate: [0.0004, 0.0009, 0.018]
|
||||
trailingCallbackRate: [0.0005, 0.0149]
|
||||
|
||||
generateGraph: true
|
||||
graphPNLDeductFee: false
|
||||
graphPNLPath: "./pnl.png"
|
||||
graphCumPNLPath: "./cumpnl.png"
|
||||
#exits:
|
||||
#- roiStopLoss:
|
||||
# percentage: 0.35%
|
||||
# - roiStopLoss:
|
||||
# percentage: 0.35%
|
||||
#- roiTakeProfit:
|
||||
#percentage: 0.7%
|
||||
# percentage: 0.7%
|
||||
#- protectiveStopLoss:
|
||||
# activationRatio: 0.5%
|
||||
# stopLossRatio: 0.2%
|
||||
|
@ -118,8 +124,8 @@ sync:
|
|||
- BTCUSDT
|
||||
|
||||
backtest:
|
||||
startTime: "2022-01-01"
|
||||
endTime: "2022-08-30"
|
||||
startTime: "2022-08-01"
|
||||
endTime: "2022-08-31"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
sessions: [binance]
|
||||
|
@ -128,5 +134,5 @@ backtest:
|
|||
makerFeeRate: 0.000
|
||||
#takerFeeRate: 0.000
|
||||
balances:
|
||||
BTC: 1
|
||||
USDT: 5000
|
||||
BTC: 0
|
||||
USDT: 21
|
||||
|
|
|
@ -138,7 +138,7 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (*types.Order, *ty
|
|||
|
||||
switch o.Type {
|
||||
case types.OrderTypeMarket:
|
||||
price = m.LastPrice
|
||||
price = m.Market.TruncatePrice(m.LastPrice)
|
||||
|
||||
case types.OrderTypeStopMarket:
|
||||
// the actual price might be different.
|
||||
|
@ -181,9 +181,9 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (*types.Order, *ty
|
|||
|
||||
if isTaker {
|
||||
if order.Type == types.OrderTypeMarket {
|
||||
order.Price = m.LastPrice
|
||||
order.Price = m.Market.TruncatePrice(m.LastPrice)
|
||||
} else if order.Type == types.OrderTypeLimit {
|
||||
order.AveragePrice = m.LastPrice
|
||||
order.AveragePrice = m.Market.TruncatePrice(m.LastPrice)
|
||||
}
|
||||
|
||||
// emit the order update for Status:New
|
||||
|
@ -193,7 +193,7 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (*types.Order, *ty
|
|||
var order2 = order
|
||||
|
||||
// emit trade before we publish order
|
||||
trade := m.newTradeFromOrder(&order2, false, m.LastPrice)
|
||||
trade := m.newTradeFromOrder(&order2, false, m.Market.TruncatePrice(m.LastPrice))
|
||||
m.executeTrade(trade)
|
||||
|
||||
// unlock the rest balances for limit taker
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/bollgrid"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/bollmaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/dca"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/drift"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/emastop"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/etf"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/ewoDgtrd"
|
||||
|
@ -33,5 +34,4 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/xmaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xnav"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/drift"
|
||||
)
|
||||
|
|
72
pkg/indicator/gma.go
Normal file
72
pkg/indicator/gma.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
// Geometric Moving Average
|
||||
//go:generate callbackgen -type GMA
|
||||
type GMA struct {
|
||||
types.SeriesBase
|
||||
types.IntervalWindow
|
||||
SMA *SMA
|
||||
UpdateCallbacks []func(value float64)
|
||||
}
|
||||
|
||||
func (inc *GMA) Last() float64 {
|
||||
if inc.SMA == nil {
|
||||
return 0.0
|
||||
}
|
||||
return math.Exp(inc.SMA.Last())
|
||||
}
|
||||
|
||||
func (inc *GMA) Index(i int) float64 {
|
||||
if inc.SMA == nil {
|
||||
return 0.0
|
||||
}
|
||||
return math.Exp(inc.SMA.Index(i))
|
||||
}
|
||||
|
||||
func (inc *GMA) Length() int {
|
||||
return inc.SMA.Length()
|
||||
}
|
||||
|
||||
func (inc *GMA) Update(value float64) {
|
||||
if inc.SMA == nil {
|
||||
inc.SMA = &SMA{IntervalWindow: inc.IntervalWindow}
|
||||
}
|
||||
inc.SMA.Update(math.Log(value))
|
||||
}
|
||||
|
||||
func (inc *GMA) Clone() (out *GMA) {
|
||||
out = &GMA{
|
||||
IntervalWindow: inc.IntervalWindow,
|
||||
SMA: inc.SMA.Clone().(*SMA),
|
||||
}
|
||||
out.SeriesBase.Series = out
|
||||
return out
|
||||
}
|
||||
|
||||
func (inc *GMA) TestUpdate(value float64) *GMA {
|
||||
out := inc.Clone()
|
||||
out.Update(value)
|
||||
return out
|
||||
}
|
||||
|
||||
var _ types.SeriesExtend = &GMA{}
|
||||
|
||||
func (inc *GMA) PushK(k types.KLine) {
|
||||
inc.Update(k.Close.Float64())
|
||||
}
|
||||
|
||||
func (inc *GMA) LoadK(allKLines []types.KLine) {
|
||||
for _, k := range allKLines {
|
||||
inc.PushK(k)
|
||||
}
|
||||
}
|
||||
|
||||
func (inc *GMA) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {
|
||||
target.OnKLineClosed(types.KLineWith(symbol, interval, inc.PushK))
|
||||
}
|
15
pkg/indicator/gma_callbacks.go
Normal file
15
pkg/indicator/gma_callbacks.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Code generated by "callbackgen -type GMA"; DO NOT EDIT.
|
||||
|
||||
package indicator
|
||||
|
||||
import ()
|
||||
|
||||
func (inc *GMA) OnUpdate(cb func(value float64)) {
|
||||
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (inc *GMA) EmitUpdate(value float64) {
|
||||
for _, cb := range inc.UpdateCallbacks {
|
||||
cb(value)
|
||||
}
|
||||
}
|
61
pkg/indicator/gma_test.go
Normal file
61
pkg/indicator/gma_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
/*
|
||||
python:
|
||||
|
||||
import pandas as pd
|
||||
from scipy.stats.mstats import gmean
|
||||
|
||||
data = pd.Series([1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9])
|
||||
gmean(data[-5:])
|
||||
gmean(data[-6:-1])
|
||||
gmean(pd.concat(data[-4:], pd.Series([1.3])))
|
||||
*/
|
||||
func Test_GMA(t *testing.T) {
|
||||
var randomPrices = []byte(`[1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]`)
|
||||
var input []fixedpoint.Value
|
||||
if err := json.Unmarshal(randomPrices, &input); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kLines []types.KLine
|
||||
want float64
|
||||
next float64
|
||||
update float64
|
||||
updateResult float64
|
||||
all int
|
||||
}{
|
||||
{
|
||||
name: "test",
|
||||
kLines: buildKLines(input),
|
||||
want: 1.6940930229200213,
|
||||
next: 1.5937204331251167,
|
||||
update: 1.3,
|
||||
updateResult: 1.6462950504034335,
|
||||
all: 24,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gma := GMA{IntervalWindow: types.IntervalWindow{Window: 5}}
|
||||
for _, k := range tt.kLines {
|
||||
gma.PushK(k)
|
||||
}
|
||||
assert.InDelta(t, tt.want, gma.Last(), Delta)
|
||||
assert.InDelta(t, tt.next, gma.Index(1), Delta)
|
||||
gma.Update(tt.update)
|
||||
assert.InDelta(t, tt.updateResult, gma.Last(), Delta)
|
||||
assert.Equal(t, tt.all, gma.Length())
|
||||
})
|
||||
}
|
||||
}
|
147
pkg/indicator/wdrift.go
Normal file
147
pkg/indicator/wdrift.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
// Refer: https://tradingview.com/script/aDymGrFx-Drift-Study-Inspired-by-Monte-Carlo-Simulations-with-BM-KL/
|
||||
// Brownian Motion's drift factor
|
||||
// could be used in Monte Carlo Simulations
|
||||
//go:generate callbackgen -type WeightedDrift
|
||||
type WeightedDrift struct {
|
||||
types.SeriesBase
|
||||
types.IntervalWindow
|
||||
chng *types.Queue
|
||||
Values types.Float64Slice
|
||||
MA types.UpdatableSeriesExtend
|
||||
Weight *types.Queue
|
||||
LastValue float64
|
||||
UpdateCallbacks []func(value float64)
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) Update(value float64, weight float64) {
|
||||
if inc.chng == nil {
|
||||
inc.SeriesBase.Series = inc
|
||||
if inc.MA == nil {
|
||||
inc.MA = &SMA{IntervalWindow: types.IntervalWindow{Interval: inc.Interval, Window: inc.Window}}
|
||||
}
|
||||
inc.Weight = types.NewQueue(10)
|
||||
inc.chng = types.NewQueue(inc.Window)
|
||||
inc.LastValue = value
|
||||
inc.Weight.Update(weight)
|
||||
return
|
||||
}
|
||||
inc.Weight.Update(weight)
|
||||
base := inc.Weight.Lowest(10)
|
||||
multiplier := int(weight / base)
|
||||
var chng float64
|
||||
if value == 0 {
|
||||
chng = 0
|
||||
} else {
|
||||
chng = math.Log(value/inc.LastValue) / weight * base
|
||||
inc.LastValue = value
|
||||
}
|
||||
for i := 0; i < multiplier; i++ {
|
||||
inc.MA.Update(chng)
|
||||
inc.chng.Update(chng)
|
||||
}
|
||||
if inc.chng.Length() >= inc.Window {
|
||||
stdev := types.Stdev(inc.chng, inc.Window)
|
||||
drift := inc.MA.Last() - stdev*stdev*0.5
|
||||
inc.Values.Push(drift)
|
||||
}
|
||||
}
|
||||
|
||||
// Assume that MA is SMA
|
||||
func (inc *WeightedDrift) ZeroPoint() float64 {
|
||||
window := float64(inc.Window)
|
||||
stdev := types.Stdev(inc.chng, inc.Window)
|
||||
chng := inc.chng.Index(inc.Window - 1)
|
||||
/*b := -2 * inc.MA.Last() - 2
|
||||
c := window * stdev * stdev - chng * chng + 2 * chng * (inc.MA.Last() + 1) - 2 * inc.MA.Last() * window
|
||||
|
||||
root := math.Sqrt(b*b - 4*c)
|
||||
K1 := (-b + root)/2
|
||||
K2 := (-b - root)/2
|
||||
N1 := math.Exp(K1) * inc.LastValue
|
||||
N2 := math.Exp(K2) * inc.LastValue
|
||||
if math.Abs(inc.LastValue-N1) < math.Abs(inc.LastValue-N2) {
|
||||
return N1
|
||||
} else {
|
||||
return N2
|
||||
}*/
|
||||
return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last()*window)
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) Clone() (out *WeightedDrift) {
|
||||
out = &WeightedDrift{
|
||||
IntervalWindow: inc.IntervalWindow,
|
||||
chng: inc.chng.Clone(),
|
||||
Values: inc.Values[:],
|
||||
MA: types.Clone(inc.MA),
|
||||
Weight: inc.Weight.Clone(),
|
||||
LastValue: inc.LastValue,
|
||||
}
|
||||
out.SeriesBase.Series = out
|
||||
return out
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) TestUpdate(value float64, weight float64) *WeightedDrift {
|
||||
out := inc.Clone()
|
||||
out.Update(value, weight)
|
||||
return out
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) Index(i int) float64 {
|
||||
if inc.Values == nil {
|
||||
return 0
|
||||
}
|
||||
return inc.Values.Index(i)
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) Last() float64 {
|
||||
if inc.Values.Length() == 0 {
|
||||
return 0
|
||||
}
|
||||
return inc.Values.Last()
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) Length() int {
|
||||
if inc.Values == nil {
|
||||
return 0
|
||||
}
|
||||
return inc.Values.Length()
|
||||
}
|
||||
|
||||
var _ types.SeriesExtend = &Drift{}
|
||||
|
||||
func (inc *WeightedDrift) PushK(k types.KLine) {
|
||||
inc.Update(k.Close.Float64(), k.Volume.Float64())
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) CalculateAndUpdate(allKLines []types.KLine) {
|
||||
if inc.chng == nil {
|
||||
for _, k := range allKLines {
|
||||
inc.PushK(k)
|
||||
inc.EmitUpdate(inc.Last())
|
||||
}
|
||||
} else {
|
||||
k := allKLines[len(allKLines)-1]
|
||||
inc.PushK(k)
|
||||
inc.EmitUpdate(inc.Last())
|
||||
}
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
||||
if inc.Interval != interval {
|
||||
return
|
||||
}
|
||||
|
||||
inc.CalculateAndUpdate(window)
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) Bind(updater KLineWindowUpdater) {
|
||||
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||
}
|
47
pkg/indicator/wdrift_test.go
Normal file
47
pkg/indicator/wdrift_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_WDrift(t *testing.T) {
|
||||
var randomPrices = []byte(`[1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9]`)
|
||||
var input []fixedpoint.Value
|
||||
if err := json.Unmarshal(randomPrices, &input); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buildKLines := func(prices []fixedpoint.Value) (klines []types.KLine) {
|
||||
for _, p := range prices {
|
||||
klines = append(klines, types.KLine{Close: p, Volume: fixedpoint.One})
|
||||
}
|
||||
|
||||
return klines
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kLines []types.KLine
|
||||
all int
|
||||
}{
|
||||
{
|
||||
name: "random_case",
|
||||
kLines: buildKLines(input),
|
||||
all: 47,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
drift := WeightedDrift{IntervalWindow: types.IntervalWindow{Window: 3}}
|
||||
drift.CalculateAndUpdate(tt.kLines)
|
||||
assert.Equal(t, drift.Length(), tt.all)
|
||||
for _, v := range drift.Values {
|
||||
assert.LessOrEqual(t, v, 1.0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
15
pkg/indicator/weighteddrift_callbacks.go
Normal file
15
pkg/indicator/weighteddrift_callbacks.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Code generated by "callbackgen -type WeightedDrift"; DO NOT EDIT.
|
||||
|
||||
package indicator
|
||||
|
||||
import ()
|
||||
|
||||
func (inc *WeightedDrift) OnUpdate(cb func(value float64)) {
|
||||
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (inc *WeightedDrift) EmitUpdate(value float64) {
|
||||
for _, cb := range inc.UpdateCallbacks {
|
||||
cb(value)
|
||||
}
|
||||
}
|
|
@ -7,14 +7,14 @@ import (
|
|||
|
||||
type DriftMA struct {
|
||||
types.SeriesBase
|
||||
drift *indicator.WeightedDrift
|
||||
ma1 types.UpdatableSeriesExtend
|
||||
drift *indicator.Drift
|
||||
ma2 types.UpdatableSeriesExtend
|
||||
}
|
||||
|
||||
func (s *DriftMA) Update(value float64) {
|
||||
func (s *DriftMA) Update(value, weight float64) {
|
||||
s.ma1.Update(value)
|
||||
s.drift.Update(s.ma1.Last())
|
||||
s.drift.Update(s.ma1.Last(), weight)
|
||||
s.ma2.Update(s.drift.Last())
|
||||
}
|
||||
|
||||
|
@ -36,16 +36,16 @@ func (s *DriftMA) ZeroPoint() float64 {
|
|||
|
||||
func (s *DriftMA) Clone() *DriftMA {
|
||||
out := DriftMA{
|
||||
ma1: types.Clone(s.ma1),
|
||||
drift: s.drift.Clone(),
|
||||
ma1: types.Clone(s.ma1),
|
||||
ma2: types.Clone(s.ma2),
|
||||
}
|
||||
out.SeriesBase.Series = &out
|
||||
return &out
|
||||
}
|
||||
|
||||
func (s *DriftMA) TestUpdate(v float64) *DriftMA {
|
||||
func (s *DriftMA) TestUpdate(v, weight float64) *DriftMA {
|
||||
out := s.Clone()
|
||||
out.Update(v)
|
||||
out.Update(v, weight)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -24,11 +24,6 @@ import (
|
|||
|
||||
const ID = "drift"
|
||||
|
||||
const DDriftFilterNeg = -0.7
|
||||
const DDriftFilterPos = 0.7
|
||||
const DriftFilterNeg = -1.85
|
||||
const DriftFilterPos = 1.85
|
||||
|
||||
var log = logrus.WithField("strategy", ID)
|
||||
var Four fixedpoint.Value = fixedpoint.NewFromInt(4)
|
||||
var Three fixedpoint.Value = fixedpoint.NewFromInt(3)
|
||||
|
@ -82,8 +77,8 @@ type Strategy struct {
|
|||
TrailingStopLossType string `json:"trailingStopLossType"` // trailing stop sources. Possible options are `kline` for 1m kline and `realtime` from order updates
|
||||
HLRangeWindow int `json:"hlRangeWindow"`
|
||||
Window1m int `json:"window1m"`
|
||||
SmootherWindow1m int `json:"smootherWindow1m"`
|
||||
FisherTransformWindow1m int `json:"fisherTransformWindow1m"`
|
||||
SmootherWindow1m int `json:"smootherWindow1m"`
|
||||
SmootherWindow int `json:"smootherWindow"`
|
||||
FisherTransformWindow int `json:"fisherTransformWindow"`
|
||||
ATRWindow int `json:"atrWindow"`
|
||||
|
@ -94,6 +89,11 @@ type Strategy struct {
|
|||
TrailingCallbackRate []float64 `json:"trailingCallbackRate"`
|
||||
TrailingActivationRatio []float64 `json:"trailingActivationRatio"`
|
||||
|
||||
DriftFilterNeg float64 `json:"driftFilterNeg"`
|
||||
DriftFilterPos float64 `json:"driftFilterPos"`
|
||||
DDriftFilterNeg float64 `json:"ddriftFilterNeg"`
|
||||
DDriftFilterPos float64 `json:"ddriftFilterPos"`
|
||||
|
||||
buyPrice float64 `persistence:"buy_price"`
|
||||
sellPrice float64 `persistence:"sell_price"`
|
||||
highestPrice float64 `persistence:"highest_price"`
|
||||
|
@ -209,7 +209,7 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
|
|||
s.stdevHigh = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}}
|
||||
s.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}}
|
||||
s.drift = &DriftMA{
|
||||
drift: &indicator.Drift{
|
||||
drift: &indicator.WeightedDrift{
|
||||
MA: &indicator.SMA{IntervalWindow: s.IntervalWindow},
|
||||
IntervalWindow: s.IntervalWindow,
|
||||
},
|
||||
|
@ -222,13 +222,14 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
|
|||
}
|
||||
s.drift.SeriesBase.Series = s.drift
|
||||
s.drift1m = &DriftMA{
|
||||
drift: &indicator.Drift{
|
||||
drift: &indicator.WeightedDrift{
|
||||
MA: &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m}},
|
||||
IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m},
|
||||
},
|
||||
ma1: &indicator.EWMA{
|
||||
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SmootherWindow1m},
|
||||
},
|
||||
|
||||
ma2: &indicator.FisherTransform{
|
||||
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FisherTransformWindow1m},
|
||||
},
|
||||
|
@ -250,7 +251,7 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
|
|||
s.ma.Update(source)
|
||||
s.stdevHigh.Update(high - s.ma.Last())
|
||||
s.stdevLow.Update(s.ma.Last() - low)
|
||||
s.drift.Update(source)
|
||||
s.drift.Update(source, kline.Volume.Float64())
|
||||
s.trendLine.Update(source)
|
||||
s.atr.PushK(kline)
|
||||
priceLines.Update(source)
|
||||
|
@ -264,7 +265,7 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
|
|||
}
|
||||
for _, kline := range *klines {
|
||||
source := s.getSource(&kline).Float64()
|
||||
s.drift1m.Update(source)
|
||||
s.drift1m.Update(source, kline.Volume.Float64())
|
||||
if s.drift1m.Last() != s.drift1m.Last() {
|
||||
panic(fmt.Sprintf("%f %v %f %f", source, s.drift1m.drift.Values.Index(1), s.drift1m.ma2.Last(), s.drift1m.drift.LastValue))
|
||||
}
|
||||
|
@ -436,7 +437,7 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
|||
|
||||
}
|
||||
|
||||
func (s *Strategy) DrawIndicators(time types.Time, priceLine types.SeriesExtend, zeroPoints types.Series) *types.Canvas {
|
||||
func (s *Strategy) DrawIndicators(time types.Time, priceLine types.SeriesExtend) *types.Canvas {
|
||||
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
||||
Length := priceLine.Length()
|
||||
if Length > 300 {
|
||||
|
@ -458,7 +459,6 @@ func (s *Strategy) DrawIndicators(time types.Time, priceLine types.SeriesExtend,
|
|||
canvas.Plot("drift1m", s.drift1m.Mul(highestPrice/h1m).Add(mean), time, Length*s.Interval.Minutes(), types.Interval1m)
|
||||
canvas.Plot("zero", types.NumberSeries(mean), time, Length)
|
||||
canvas.Plot("price", priceLine, time, Length)
|
||||
canvas.Plot("zeroPoint", zeroPoints, time, Length)
|
||||
return canvas
|
||||
}
|
||||
|
||||
|
@ -497,8 +497,8 @@ func (s *Strategy) DrawCumPNL(cumProfit types.Series) *types.Canvas {
|
|||
return canvas
|
||||
}
|
||||
|
||||
func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series, zeroPoints types.Series) {
|
||||
canvas := s.DrawIndicators(time, priceLine, zeroPoints)
|
||||
func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series) {
|
||||
canvas := s.DrawIndicators(time, priceLine)
|
||||
f, err := os.Create(s.CanvasPath)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
|
||||
|
@ -678,14 +678,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
s.positionLock.Lock()
|
||||
defer s.positionLock.Unlock()
|
||||
if tag == "close" {
|
||||
// tag == "" is for exits trades
|
||||
if tag == "close" || tag == "" {
|
||||
if s.p.IsDust(trade.Price) {
|
||||
s.buyPrice = 0
|
||||
s.sellPrice = 0
|
||||
s.highestPrice = 0
|
||||
s.lowestPrice = 0
|
||||
} else if s.p.IsLong() {
|
||||
|
||||
s.buyPrice = trade.Price.Float64()
|
||||
s.sellPrice = 0
|
||||
s.highestPrice = s.buyPrice
|
||||
|
@ -735,7 +735,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
s.initTickerFunctions(ctx)
|
||||
|
||||
zeroPoints := types.NewQueue(300)
|
||||
stoploss := s.StopLoss.Float64()
|
||||
// default value: use 1m kline
|
||||
if !s.NoTrailingStopLoss && s.IsBackTesting() || s.TrailingStopLossType == "" {
|
||||
|
@ -743,7 +742,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
|
||||
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
|
||||
canvas := s.DrawIndicators(s.frameKLine.StartTime, priceLine, zeroPoints)
|
||||
canvas := s.DrawIndicators(s.frameKLine.StartTime, priceLine)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render indicators in drift")
|
||||
|
@ -810,7 +809,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
if kline.Interval == types.Interval1m {
|
||||
s.kline1m.Set(&kline)
|
||||
s.drift1m.Update(s.getSource(&kline).Float64())
|
||||
s.drift1m.Update(s.getSource(&kline).Float64(), kline.Volume.Float64())
|
||||
s.minutesCounter += 1
|
||||
if s.Status != types.StrategyStatusRunning {
|
||||
return
|
||||
|
@ -865,10 +864,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
priceLine.Update(sourcef)
|
||||
s.ma.Update(sourcef)
|
||||
s.trendLine.Update(sourcef)
|
||||
s.drift.Update(sourcef)
|
||||
s.drift.Update(sourcef, kline.Volume.Float64())
|
||||
|
||||
zeroPoint := s.drift.ZeroPoint()
|
||||
zeroPoints.Update(zeroPoint)
|
||||
s.atr.PushK(kline)
|
||||
drift = s.drift.Array(2)
|
||||
ddrift := s.drift.drift.Array(2)
|
||||
|
@ -889,7 +886,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
|
||||
s.positionLock.Lock()
|
||||
log.Errorf("highdiff: %3.2f ma: %.2f, close: %8v, high: %8v, low: %8v, time: %v", s.stdevHigh.Last(), s.ma.Last(), kline.Close, kline.High, kline.Low, kline.StartTime)
|
||||
log.Errorf("highdiff: %3.2f ma: %.2f, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), s.ma.Last(), kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime)
|
||||
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
||||
s.lowestPrice = lowf
|
||||
}
|
||||
|
@ -905,17 +902,17 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, ddriftPred: %.4f, drift[1]: %.4f, ddrift[1]: %.4f, atr: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f",
|
||||
sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
|
||||
// Notify will parse args to strings and process separately
|
||||
bbgo.Notify("balances: [Base] %s(%v %s) [Quote] %s [Total] %v %s",
|
||||
bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s",
|
||||
s.CalcAssetValue(price),
|
||||
s.Market.QuoteCurrency,
|
||||
balances[s.Market.BaseCurrency].String(),
|
||||
balances[s.Market.BaseCurrency].Total().Mul(price),
|
||||
s.Market.QuoteCurrency,
|
||||
balances[s.Market.QuoteCurrency].String(),
|
||||
s.CalcAssetValue(price),
|
||||
s.Market.QuoteCurrency,
|
||||
)
|
||||
|
||||
shortCondition := (drift[1] >= DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= DDriftFilterNeg || ddriftPred <= 0) || drift[1] < 0 && drift[0] < 0
|
||||
longCondition := (drift[1] <= DriftFilterPos || ddrift[1] <= 0) && (driftPred >= DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0
|
||||
shortCondition := (drift[1] >= s.DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= s.DDriftFilterNeg || ddriftPred <= 0) || drift[1] < 0 && drift[0] < 0
|
||||
longCondition := (drift[1] <= s.DriftFilterPos || ddrift[1] <= 0) && (driftPred >= s.DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0
|
||||
if shortCondition && longCondition {
|
||||
if drift[1] > drift[0] {
|
||||
longCondition = false
|
||||
|
@ -1036,7 +1033,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
os.Stdout.Write(buffer.Bytes())
|
||||
|
||||
if s.GenerateGraph {
|
||||
s.Draw(s.frameKLine.StartTime, priceLine, &profit, &cumProfit, zeroPoints)
|
||||
s.Draw(s.frameKLine.StartTime, priceLine, &profit, &cumProfit)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
|
|
Loading…
Reference in New Issue
Block a user