From 2a942eab0e9a32bf7e8654bfd731e1095355d427 Mon Sep 17 00:00:00 2001 From: zenix Date: Fri, 15 Apr 2022 19:12:11 +0900 Subject: [PATCH] fix: rename EVWMP to VWEMP, fix backtesting fee --- pkg/backtest/matching.go | 36 +++++++++++++++---------------- pkg/bbgo/config.go | 32 ++++++++++++++++++++++++--- pkg/strategy/ewoDgtrd/strategy.go | 22 +++++++++---------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/pkg/backtest/matching.go b/pkg/backtest/matching.go index 185723d9b..f95a5fca4 100644 --- a/pkg/backtest/matching.go +++ b/pkg/backtest/matching.go @@ -12,12 +12,6 @@ import ( "github.com/c9s/bbgo/pkg/types" ) -// DefaultFeeRate set the fee rate for most cases -// BINANCE uses 0.1% for both maker and taker -// for BNB holders, it's 0.075% for both maker and taker -// MAX uses 0.050% for maker and 0.15% for taker -var DefaultFeeRate = fixedpoint.NewFromFloat(0.075 * 0.01) - var orderID uint64 = 1 var tradeID uint64 = 1 @@ -45,9 +39,6 @@ type SimplePriceMatching struct { Account *types.Account - MakerFeeRate fixedpoint.Value `json:"makerFeeRate"` - TakerFeeRate fixedpoint.Value `json:"takerFeeRate"` - tradeUpdateCallbacks []func(trade types.Trade) orderUpdateCallbacks []func(order types.Order) balanceUpdateCallbacks []func(balances types.BalanceMap) @@ -192,11 +183,11 @@ func (m *SimplePriceMatching) executeTrade(trade types.Trade) { if trade.IsBuyer { err = m.Account.UseLockedBalance(m.Market.QuoteCurrency, trade.Price.Mul(trade.Quantity)) - m.Account.AddBalance(m.Market.BaseCurrency, trade.Quantity) + m.Account.AddBalance(m.Market.BaseCurrency, trade.Quantity.Sub(trade.Fee.Div(trade.Price))) } else { err = m.Account.UseLockedBalance(m.Market.BaseCurrency, trade.Quantity) - m.Account.AddBalance(m.Market.QuoteCurrency, trade.Quantity.Mul(trade.Price)) + m.Account.AddBalance(m.Market.QuoteCurrency, trade.Quantity.Mul(trade.Price).Sub(trade.Fee)) } if err != nil { @@ -211,15 +202,11 @@ func (m *SimplePriceMatching) executeTrade(trade types.Trade) { func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool) types.Trade { // BINANCE uses 0.1% for both maker and taker // MAX uses 0.050% for maker and 0.15% for taker - var feeRate = DefaultFeeRate + var feeRate fixedpoint.Value if isMaker { - if m.MakerFeeRate.Sign() > 0 { - feeRate = m.MakerFeeRate - } + feeRate = m.Account.MakerFeeRate } else { - if m.TakerFeeRate.Sign() > 0 { - feeRate = m.TakerFeeRate - } + feeRate = m.Account.TakerFeeRate } price := order.Price @@ -230,7 +217,6 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool) } price = m.LastPrice - } var fee fixedpoint.Value @@ -297,6 +283,9 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [ // is it a taker order? if price.Compare(o.Price) >= 0 { + if o.Price.Compare(m.LastKLine.Low) < 0 { + o.Price = m.LastKLine.Low + } o.ExecutedQuantity = o.Quantity o.Status = types.OrderStatusFilled closedOrders = append(closedOrders, o) @@ -307,6 +296,9 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [ case types.OrderTypeLimit, types.OrderTypeLimitMaker: if price.Compare(o.Price) >= 0 { + if o.Price.Compare(m.LastKLine.Low) < 0 { + o.Price = m.LastKLine.Low + } o.ExecutedQuantity = o.Quantity o.Status = types.OrderStatusFilled closedOrders = append(closedOrders, o) @@ -358,6 +350,9 @@ func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders o.Type = types.OrderTypeLimit if sellPrice.Compare(o.Price) <= 0 { + if o.Price.Compare(m.LastKLine.High) > 0 { + o.Price = m.LastKLine.High + } o.ExecutedQuantity = o.Quantity o.Status = types.OrderStatusFilled closedOrders = append(closedOrders, o) @@ -370,6 +365,9 @@ func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders case types.OrderTypeLimit, types.OrderTypeLimitMaker: if sellPrice.Compare(o.Price) <= 0 { + if o.Price.Compare(m.LastKLine.High) > 0 { + o.Price = m.LastKLine.High + } o.ExecutedQuantity = o.Quantity o.Status = types.OrderStatusFilled closedOrders = append(closedOrders, o) diff --git a/pkg/bbgo/config.go b/pkg/bbgo/config.go index e1d69948b..81a5a1dd1 100644 --- a/pkg/bbgo/config.go +++ b/pkg/bbgo/config.go @@ -17,6 +17,12 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +// DefaultFeeRate set the fee rate for most cases +// BINANCE uses 0.1% for both maker and taker +// for BNB holders, it's 0.075% for both maker and taker +// MAX uses 0.050% for maker and 0.15% for taker +var DefaultFeeRate = fixedpoint.NewFromFloat(0.075 * 0.01) + type PnLReporterConfig struct { AverageCostBySymbols datatype.StringSlice `json:"averageCostBySymbols" yaml:"averageCostBySymbols"` Of datatype.StringSlice `json:"of" yaml:"of"` @@ -106,10 +112,30 @@ type Backtest struct { type BacktestAccount struct { // TODO: MakerFeeRate should replace the commission fields - MakerFeeRate fixedpoint.Value `json:"makerFeeRate"` - TakerFeeRate fixedpoint.Value `json:"takerFeeRate"` + MakerFeeRate fixedpoint.Value `json:"makerFeeRate,omitempty" yaml:"makerFeeRate,omitempty"` + TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty" yaml:"takerFeeRate,omitempty"` - Balances BacktestAccountBalanceMap `json:"balances" yaml:"balances"` + Balances BacktestAccountBalanceMap `json:"balances" yaml:"balances"` +} + +type BA BacktestAccount + +func (b *BacktestAccount) UnmarshalYAML(value *yaml.Node) error { + bb := &BA{MakerFeeRate: DefaultFeeRate, TakerFeeRate: DefaultFeeRate} + if err := value.Decode(bb); err != nil { + return err + } + *b = BacktestAccount(*bb) + return nil +} + +func (b *BacktestAccount) UnmarshalJSON(input []byte) error { + bb := &BA{MakerFeeRate: DefaultFeeRate, TakerFeeRate: DefaultFeeRate} + if err := json.Unmarshal(input, bb); err != nil { + return err + } + *b = BacktestAccount(*bb) + return nil } type BacktestAccountBalanceMap map[string]fixedpoint.Value diff --git a/pkg/strategy/ewoDgtrd/strategy.go b/pkg/strategy/ewoDgtrd/strategy.go index a4fe469e2..e687b5ec5 100644 --- a/pkg/strategy/ewoDgtrd/strategy.go +++ b/pkg/strategy/ewoDgtrd/strategy.go @@ -66,16 +66,16 @@ type UpdatableSeries interface { Update(value float64) } -type EVWMA struct { +type VWEMA struct { PV UpdatableSeries V UpdatableSeries } -func (inc *EVWMA) Last() float64 { +func (inc *VWEMA) Last() float64 { return inc.PV.Last() / inc.V.Last() } -func (inc *EVWMA) Index(i int) float64 { +func (inc *VWEMA) Index(i int) float64 { if i >= inc.PV.Length() { return 0 } @@ -86,7 +86,7 @@ func (inc *EVWMA) Index(i int) float64 { return inc.PV.Index(i) / vi } -func (inc *EVWMA) Length() int { +func (inc *VWEMA) Length() int { pvl := inc.PV.Length() vl := inc.V.Length() if pvl < vl { @@ -95,12 +95,12 @@ func (inc *EVWMA) Length() int { return vl } -func (inc *EVWMA) Update(kline types.KLine) { +func (inc *VWEMA) Update(kline types.KLine) { inc.PV.Update(kline.Close.Mul(kline.Volume).Float64()) inc.V.Update(kline.Volume.Float64()) } -func (inc *EVWMA) UpdateVal(price float64, vol float64) { +func (inc *VWEMA) UpdateVal(price float64, vol float64) { inc.PV.Update(price * vol) inc.V.Update(vol) } @@ -247,11 +247,11 @@ func (s *Strategy) SetupIndicators() { s.ma5 = sma5 s.ma34 = sma34 } else { - evwma5 := &EVWMA{ + evwma5 := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, } - evwma34 := &EVWMA{ + evwma34 := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, } @@ -289,11 +289,11 @@ func (s *Strategy) SetupIndicators() { s.ma5 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 5}) s.ma34 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 34}) } else { - evwma5 := &EVWMA{ + evwma5 := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, } - evwma34 := &EVWMA{ + evwma34 := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, } @@ -354,7 +354,7 @@ func (s *Strategy) SetupIndicators() { }) s.ewoSignal = sig } else { - sig := &EVWMA{ + sig := &VWEMA{ PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}, V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}, }