diff --git a/config/drift.yaml b/config/drift.yaml index b2459d22e..3d2a5a23e 100644 --- a/config/drift.yaml +++ b/config/drift.yaml @@ -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 diff --git a/config/driftBTC.yaml b/config/driftBTC.yaml index 93e272fe3..14cea486e 100644 --- a/config/driftBTC.yaml +++ b/config/driftBTC.yaml @@ -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 diff --git a/pkg/backtest/matching.go b/pkg/backtest/matching.go index 92b9dafc7..64bcdef23 100644 --- a/pkg/backtest/matching.go +++ b/pkg/backtest/matching.go @@ -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 diff --git a/pkg/cmd/strategy/builtin.go b/pkg/cmd/strategy/builtin.go index d00c5f824..136be0a8a 100644 --- a/pkg/cmd/strategy/builtin.go +++ b/pkg/cmd/strategy/builtin.go @@ -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" ) diff --git a/pkg/indicator/gma.go b/pkg/indicator/gma.go new file mode 100644 index 000000000..c1fb3aa11 --- /dev/null +++ b/pkg/indicator/gma.go @@ -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)) +} diff --git a/pkg/indicator/gma_callbacks.go b/pkg/indicator/gma_callbacks.go new file mode 100644 index 000000000..28e0cd867 --- /dev/null +++ b/pkg/indicator/gma_callbacks.go @@ -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) + } +} diff --git a/pkg/indicator/gma_test.go b/pkg/indicator/gma_test.go new file mode 100644 index 000000000..ab9740e09 --- /dev/null +++ b/pkg/indicator/gma_test.go @@ -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()) + }) + } +} diff --git a/pkg/indicator/wdrift.go b/pkg/indicator/wdrift.go new file mode 100644 index 000000000..01a3ca753 --- /dev/null +++ b/pkg/indicator/wdrift.go @@ -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) +} diff --git a/pkg/indicator/wdrift_test.go b/pkg/indicator/wdrift_test.go new file mode 100644 index 000000000..1cf6c4120 --- /dev/null +++ b/pkg/indicator/wdrift_test.go @@ -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) + } + }) + } +} diff --git a/pkg/indicator/weighteddrift_callbacks.go b/pkg/indicator/weighteddrift_callbacks.go new file mode 100644 index 000000000..476e61506 --- /dev/null +++ b/pkg/indicator/weighteddrift_callbacks.go @@ -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) + } +} diff --git a/pkg/strategy/drift/driftma.go b/pkg/strategy/drift/driftma.go index 6660371c5..6754e0001 100644 --- a/pkg/strategy/drift/driftma.go +++ b/pkg/strategy/drift/driftma.go @@ -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 } diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index c0bd79ae4..73600240d 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -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()