feature: add gma, add wdrift, export drift filter, fix: LastPrice truncation

This commit is contained in:
zenix 2022-08-23 17:22:45 +09:00
parent 94615f7ecf
commit 6b6a24a655
12 changed files with 432 additions and 68 deletions

View File

@ -28,28 +28,32 @@ exchangeStrategies:
# kline interval for indicators # kline interval for indicators
interval: 15m interval: 15m
window: 2 window: 2
stoploss: 0.3% stoploss: 4.3%
source: close source: close
predictOffset: 2 predictOffset: 2
noTrailingStopLoss: false noTrailingStopLoss: true
trailingStopLossType: kline trailingStopLossType: kline
# stddev on high/low-source # stddev on high/low-source
hlVarianceMultiplier: 0.23 hlVarianceMultiplier: 0.1
hlRangeWindow: 5 hlRangeWindow: 5
window1m: 24 window1m: 49
smootherWindow1m: 24 smootherWindow1m: 80
fisherTransformWindow1m: 162 fisherTransformWindow1m: 74
smootherWindow: 1 smootherWindow: 3
fisherTransformWindow: 9 fisherTransformWindow: 160
atrWindow: 14 atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes # orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 3 pendingMinutes: 10
noRebalance: true noRebalance: true
trendWindow: 12 trendWindow: 12
rebalanceFilter: 1.5 rebalanceFilter: 1.5
trailingActivationRatio: [0.004] trailingActivationRatio: [0.003]
trailingCallbackRate: [0.001] trailingCallbackRate: [0.0006]
driftFilterPos: 1.2
driftFilterNeg: -1.2
ddriftFilterPos: 0.4
ddriftFilterNeg: -0.4
generateGraph: true generateGraph: true
graphPNLDeductFee: true graphPNLDeductFee: true

View File

@ -27,35 +27,41 @@ exchangeStrategies:
symbol: BTCUSDT symbol: BTCUSDT
# kline interval for indicators # kline interval for indicators
interval: 15m interval: 15m
window: 2 window: 1
stoploss: 0.2% stoploss: 1.2%
source: close source: close
predictOffset: 2 predictOffset: 2
noTrailingStopLoss: false noTrailingStopLoss: false
trailingStopLossType: kline trailingStopLossType: kline
# stddev on high/low-source # stddev on high/low-source
hlVarianceMultiplier: 0.22 hlVarianceMultiplier: 0.1
hlRangeWindow: 5 hlRangeWindow: 5
smootherWindow: 1 smootherWindow: 1
fisherTransformWindow: 9 fisherTransformWindow: 34
window1m: 22 window1m: 58
smootherWindow1m: 18 smootherWindow1m: 118
fisherTransformWindow1m: 162 fisherTransformWindow1m: 319
atrWindow: 14 atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes # orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 5 pendingMinutes: 10
noRebalance: true noRebalance: true
trendWindow: 576 trendWindow: 576
rebalanceFilter: 0 rebalanceFilter: 0
driftFilterPos: 1.8
driftFilterNeg: -1.8
ddriftFilterPos: 0.5
ddriftFilterNeg: -0.5 #-1.6
# ActivationRatio should be increasing order # ActivationRatio should be increasing order
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop # 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.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: [] #trailingActivationRatio: []
#trailingCallbackRate: [] #trailingCallbackRate: []
#trailingCallbackRate: [0.002, 0.01, 0.1] #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 generateGraph: true
graphPNLDeductFee: false graphPNLDeductFee: false
@ -118,8 +124,8 @@ sync:
- BTCUSDT - BTCUSDT
backtest: backtest:
startTime: "2022-01-01" startTime: "2022-08-01"
endTime: "2022-08-30" endTime: "2022-08-31"
symbols: symbols:
- BTCUSDT - BTCUSDT
sessions: [binance] sessions: [binance]
@ -128,5 +134,5 @@ backtest:
makerFeeRate: 0.000 makerFeeRate: 0.000
#takerFeeRate: 0.000 #takerFeeRate: 0.000
balances: balances:
BTC: 1 BTC: 0
USDT: 5000 USDT: 21

View File

@ -138,7 +138,7 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (*types.Order, *ty
switch o.Type { switch o.Type {
case types.OrderTypeMarket: case types.OrderTypeMarket:
price = m.LastPrice price = m.Market.TruncatePrice(m.LastPrice)
case types.OrderTypeStopMarket: case types.OrderTypeStopMarket:
// the actual price might be different. // the actual price might be different.
@ -181,9 +181,9 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (*types.Order, *ty
if isTaker { if isTaker {
if order.Type == types.OrderTypeMarket { if order.Type == types.OrderTypeMarket {
order.Price = m.LastPrice order.Price = m.Market.TruncatePrice(m.LastPrice)
} else if order.Type == types.OrderTypeLimit { } else if order.Type == types.OrderTypeLimit {
order.AveragePrice = m.LastPrice order.AveragePrice = m.Market.TruncatePrice(m.LastPrice)
} }
// emit the order update for Status:New // emit the order update for Status:New
@ -193,7 +193,7 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (*types.Order, *ty
var order2 = order var order2 = order
// emit trade before we publish 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) m.executeTrade(trade)
// unlock the rest balances for limit taker // unlock the rest balances for limit taker

View File

@ -6,6 +6,7 @@ import (
_ "github.com/c9s/bbgo/pkg/strategy/bollgrid" _ "github.com/c9s/bbgo/pkg/strategy/bollgrid"
_ "github.com/c9s/bbgo/pkg/strategy/bollmaker" _ "github.com/c9s/bbgo/pkg/strategy/bollmaker"
_ "github.com/c9s/bbgo/pkg/strategy/dca" _ "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/emastop"
_ "github.com/c9s/bbgo/pkg/strategy/etf" _ "github.com/c9s/bbgo/pkg/strategy/etf"
_ "github.com/c9s/bbgo/pkg/strategy/ewoDgtrd" _ "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/xmaker"
_ "github.com/c9s/bbgo/pkg/strategy/xnav" _ "github.com/c9s/bbgo/pkg/strategy/xnav"
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker" _ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
_ "github.com/c9s/bbgo/pkg/strategy/drift"
) )

72
pkg/indicator/gma.go Normal file
View 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))
}

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

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

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

View File

@ -7,14 +7,14 @@ import (
type DriftMA struct { type DriftMA struct {
types.SeriesBase types.SeriesBase
drift *indicator.WeightedDrift
ma1 types.UpdatableSeriesExtend ma1 types.UpdatableSeriesExtend
drift *indicator.Drift
ma2 types.UpdatableSeriesExtend ma2 types.UpdatableSeriesExtend
} }
func (s *DriftMA) Update(value float64) { func (s *DriftMA) Update(value, weight float64) {
s.ma1.Update(value) s.ma1.Update(value)
s.drift.Update(s.ma1.Last()) s.drift.Update(s.ma1.Last(), weight)
s.ma2.Update(s.drift.Last()) s.ma2.Update(s.drift.Last())
} }
@ -36,16 +36,16 @@ func (s *DriftMA) ZeroPoint() float64 {
func (s *DriftMA) Clone() *DriftMA { func (s *DriftMA) Clone() *DriftMA {
out := DriftMA{ out := DriftMA{
ma1: types.Clone(s.ma1),
drift: s.drift.Clone(), drift: s.drift.Clone(),
ma1: types.Clone(s.ma1),
ma2: types.Clone(s.ma2), ma2: types.Clone(s.ma2),
} }
out.SeriesBase.Series = &out out.SeriesBase.Series = &out
return &out return &out
} }
func (s *DriftMA) TestUpdate(v float64) *DriftMA { func (s *DriftMA) TestUpdate(v, weight float64) *DriftMA {
out := s.Clone() out := s.Clone()
out.Update(v) out.Update(v, weight)
return out return out
} }

View File

@ -24,11 +24,6 @@ import (
const ID = "drift" 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 log = logrus.WithField("strategy", ID)
var Four fixedpoint.Value = fixedpoint.NewFromInt(4) var Four fixedpoint.Value = fixedpoint.NewFromInt(4)
var Three fixedpoint.Value = fixedpoint.NewFromInt(3) 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 TrailingStopLossType string `json:"trailingStopLossType"` // trailing stop sources. Possible options are `kline` for 1m kline and `realtime` from order updates
HLRangeWindow int `json:"hlRangeWindow"` HLRangeWindow int `json:"hlRangeWindow"`
Window1m int `json:"window1m"` Window1m int `json:"window1m"`
SmootherWindow1m int `json:"smootherWindow1m"`
FisherTransformWindow1m int `json:"fisherTransformWindow1m"` FisherTransformWindow1m int `json:"fisherTransformWindow1m"`
SmootherWindow1m int `json:"smootherWindow1m"`
SmootherWindow int `json:"smootherWindow"` SmootherWindow int `json:"smootherWindow"`
FisherTransformWindow int `json:"fisherTransformWindow"` FisherTransformWindow int `json:"fisherTransformWindow"`
ATRWindow int `json:"atrWindow"` ATRWindow int `json:"atrWindow"`
@ -94,6 +89,11 @@ type Strategy struct {
TrailingCallbackRate []float64 `json:"trailingCallbackRate"` TrailingCallbackRate []float64 `json:"trailingCallbackRate"`
TrailingActivationRatio []float64 `json:"trailingActivationRatio"` 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"` buyPrice float64 `persistence:"buy_price"`
sellPrice float64 `persistence:"sell_price"` sellPrice float64 `persistence:"sell_price"`
highestPrice float64 `persistence:"highest_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.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.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}}
s.drift = &DriftMA{ s.drift = &DriftMA{
drift: &indicator.Drift{ drift: &indicator.WeightedDrift{
MA: &indicator.SMA{IntervalWindow: s.IntervalWindow}, MA: &indicator.SMA{IntervalWindow: s.IntervalWindow},
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.drift.SeriesBase.Series = s.drift
s.drift1m = &DriftMA{ s.drift1m = &DriftMA{
drift: &indicator.Drift{ drift: &indicator.WeightedDrift{
MA: &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m}}, MA: &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m}},
IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m}, IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m},
}, },
ma1: &indicator.EWMA{ ma1: &indicator.EWMA{
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SmootherWindow1m}, IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SmootherWindow1m},
}, },
ma2: &indicator.FisherTransform{ ma2: &indicator.FisherTransform{
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FisherTransformWindow1m}, 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.ma.Update(source)
s.stdevHigh.Update(high - s.ma.Last()) s.stdevHigh.Update(high - s.ma.Last())
s.stdevLow.Update(s.ma.Last() - low) s.stdevLow.Update(s.ma.Last() - low)
s.drift.Update(source) s.drift.Update(source, kline.Volume.Float64())
s.trendLine.Update(source) s.trendLine.Update(source)
s.atr.PushK(kline) s.atr.PushK(kline)
priceLines.Update(source) priceLines.Update(source)
@ -264,7 +265,7 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
} }
for _, kline := range *klines { for _, kline := range *klines {
source := s.getSource(&kline).Float64() source := s.getSource(&kline).Float64()
s.drift1m.Update(source) s.drift1m.Update(source, kline.Volume.Float64())
if s.drift1m.Last() != s.drift1m.Last() { 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)) 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) canvas := types.NewCanvas(s.InstanceID(), s.Interval)
Length := priceLine.Length() Length := priceLine.Length()
if Length > 300 { 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("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("zero", types.NumberSeries(mean), time, Length)
canvas.Plot("price", priceLine, time, Length) canvas.Plot("price", priceLine, time, Length)
canvas.Plot("zeroPoint", zeroPoints, time, Length)
return canvas return canvas
} }
@ -497,8 +497,8 @@ func (s *Strategy) DrawCumPNL(cumProfit types.Series) *types.Canvas {
return canvas return canvas
} }
func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series, zeroPoints types.Series) { func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series) {
canvas := s.DrawIndicators(time, priceLine, zeroPoints) canvas := s.DrawIndicators(time, priceLine)
f, err := os.Create(s.CanvasPath) f, err := os.Create(s.CanvasPath)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath) 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() s.positionLock.Lock()
defer s.positionLock.Unlock() defer s.positionLock.Unlock()
if tag == "close" { // tag == "" is for exits trades
if tag == "close" || tag == "" {
if s.p.IsDust(trade.Price) { if s.p.IsDust(trade.Price) {
s.buyPrice = 0 s.buyPrice = 0
s.sellPrice = 0 s.sellPrice = 0
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = 0 s.lowestPrice = 0
} else if s.p.IsLong() { } else if s.p.IsLong() {
s.buyPrice = trade.Price.Float64() s.buyPrice = trade.Price.Float64()
s.sellPrice = 0 s.sellPrice = 0
s.highestPrice = s.buyPrice s.highestPrice = s.buyPrice
@ -735,7 +735,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
s.initTickerFunctions(ctx) s.initTickerFunctions(ctx)
zeroPoints := types.NewQueue(300)
stoploss := s.StopLoss.Float64() stoploss := s.StopLoss.Float64()
// default value: use 1m kline // default value: use 1m kline
if !s.NoTrailingStopLoss && s.IsBackTesting() || s.TrailingStopLossType == "" { 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) { 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 var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil { if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render indicators in drift") 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 { if kline.Interval == types.Interval1m {
s.kline1m.Set(&kline) s.kline1m.Set(&kline)
s.drift1m.Update(s.getSource(&kline).Float64()) s.drift1m.Update(s.getSource(&kline).Float64(), kline.Volume.Float64())
s.minutesCounter += 1 s.minutesCounter += 1
if s.Status != types.StrategyStatusRunning { if s.Status != types.StrategyStatusRunning {
return return
@ -865,10 +864,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
priceLine.Update(sourcef) priceLine.Update(sourcef)
s.ma.Update(sourcef) s.ma.Update(sourcef)
s.trendLine.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) s.atr.PushK(kline)
drift = s.drift.Array(2) drift = s.drift.Array(2)
ddrift := s.drift.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() 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 { if s.lowestPrice > 0 && lowf < s.lowestPrice {
s.lowestPrice = lowf 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", 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) 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 // 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].String(),
balances[s.Market.BaseCurrency].Total().Mul(price), balances[s.Market.BaseCurrency].Total().Mul(price),
s.Market.QuoteCurrency, s.Market.QuoteCurrency,
balances[s.Market.QuoteCurrency].String(), 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 shortCondition := (drift[1] >= s.DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= s.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 longCondition := (drift[1] <= s.DriftFilterPos || ddrift[1] <= 0) && (driftPred >= s.DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0
if shortCondition && longCondition { if shortCondition && longCondition {
if drift[1] > drift[0] { if drift[1] > drift[0] {
longCondition = false longCondition = false
@ -1036,7 +1033,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
os.Stdout.Write(buffer.Bytes()) os.Stdout.Write(buffer.Bytes())
if s.GenerateGraph { if s.GenerateGraph {
s.Draw(s.frameKLine.StartTime, priceLine, &profit, &cumProfit, zeroPoints) s.Draw(s.frameKLine.StartTime, priceLine, &profit, &cumProfit)
} }
wg.Done() wg.Done()