mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +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
|
# 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user