From 4d3af3a6bc4cb0e34d026b98bffad7f0de18e18e Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 9 Sep 2024 14:41:41 +0800 Subject: [PATCH] xmaker: pull out getLayerPrice and add test against the method --- pkg/strategy/xmaker/strategy.go | 71 +++++++++++++++++------ pkg/strategy/xmaker/strategy_test.go | 87 +++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 31 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 60cff55..11a3566 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -444,6 +444,53 @@ func (s *Strategy) getInitialLayerQuantity(i int) (fixedpoint.Value, error) { return q, nil } +func (s *Strategy) getLayerPrice( + i int, + side types.SideType, + sourceBook *types.StreamOrderBook, + quote *Quote, + requiredDepth fixedpoint.Value, +) (price fixedpoint.Value) { + var margin, delta, pips fixedpoint.Value + + switch side { + case types.SideTypeSell: + margin = quote.AskMargin + delta = margin + + if quote.AskLayerPips.Sign() > 0 { + pips = quote.AskLayerPips + } else { + pips = fixedpoint.One + } + + case types.SideTypeBuy: + margin = quote.BidMargin + delta = margin.Neg() + + if quote.BidLayerPips.Sign() > 0 { + pips = quote.BidLayerPips.Neg() + } else { + pips = fixedpoint.One.Neg() + } + } + + if s.UseDepthPrice { + price = aggregatePrice(sourceBook.SideBook(side), requiredDepth) + price = price.Mul(fixedpoint.One.Add(delta)) + if i > 0 { + price = price.Add(pips.Mul(s.makerMarket.TickSize)) + } + } else { + price = price.Mul(fixedpoint.One.Add(delta)) + if i > 0 { + price = price.Add(pips.Mul(s.makerMarket.TickSize)) + } + } + + return price +} + func (s *Strategy) updateQuote(ctx context.Context) error { if err := s.activeMakerOrders.GracefulCancel(ctx, s.makerSession.Exchange); err != nil { s.logger.Warnf("there are some %s orders not canceled, skipping placing maker orders", s.Symbol) @@ -710,7 +757,6 @@ func (s *Strategy) updateQuote(ctx context.Context) error { var submitOrders []types.SubmitOrder var accumulativeBidQuantity, accumulativeAskQuantity fixedpoint.Value - var askQuantity = s.Quantity var quote = &Quote{ BestBidPrice: bestBidPrice, @@ -798,26 +844,17 @@ func (s *Strategy) updateQuote(ctx context.Context) error { hedgeQuota.Rollback() } - if s.QuantityMultiplier.Sign() > 0 { - bidQuantity = bidQuantity.Mul(s.QuantityMultiplier) - } } } - for i := 0; i < s.NumLayers; i++ { - // for maker ask orders - if !disableMakerAsk { - if s.QuantityScale != nil { - qf, err := s.QuantityScale.Scale(i + 1) - if err != nil { - return fmt.Errorf("quantityScale error: %w", err) - } - - log.Infof("%s scaling ask #%d quantity to %f", s.Symbol, i+1, qf) - - // override the default bid quantity - askQuantity = fixedpoint.NewFromFloat(qf) + // for maker ask orders + if !disableMakerAsk { + for i := 0; i < s.NumLayers; i++ { + askQuantity, err := s.getInitialLayerQuantity(i) + if err != nil { + return err } + accumulativeAskQuantity = accumulativeAskQuantity.Add(askQuantity) if s.UseDepthPrice { diff --git a/pkg/strategy/xmaker/strategy_test.go b/pkg/strategy/xmaker/strategy_test.go index 3012f94..9fcfc67 100644 --- a/pkg/strategy/xmaker/strategy_test.go +++ b/pkg/strategy/xmaker/strategy_test.go @@ -2,28 +2,87 @@ package xmaker import ( "testing" + "time" "git.qtrade.icu/lychiyu/bbgo/pkg/fixedpoint" + . "git.qtrade.icu/lychiyu/bbgo/pkg/testing/testhelper" "git.qtrade.icu/lychiyu/bbgo/pkg/types" "github.com/stretchr/testify/assert" ) -func Test_aggregatePrice(t *testing.T) { - bids := types.PriceVolumeSlice{ - { - Price: fixedpoint.NewFromFloat(1000.0), - Volume: fixedpoint.NewFromFloat(1.0), - }, - { - Price: fixedpoint.NewFromFloat(1200.0), - Volume: fixedpoint.NewFromFloat(1.0), - }, - { - Price: fixedpoint.NewFromFloat(1400.0), - Volume: fixedpoint.NewFromFloat(1.0), - }, +func TestStrategy_getLayerPrice(t *testing.T) { + symbol := "BTCUSDT" + market := Market(symbol) + + s := &Strategy{ + UseDepthPrice: true, + DepthQuantity: Number(3.0), + makerMarket: market, } + sourceBook := types.NewStreamBook(symbol, types.ExchangeBinance) + sourceBook.Load(types.SliceOrderBook{ + Symbol: symbol, + Bids: PriceVolumeSlice( + Number(1300.0), Number(1.0), + Number(1200.0), Number(2.0), + Number(1100.0), Number(3.0), + ), + Asks: PriceVolumeSlice( + Number(1301.0), Number(1.0), + Number(1400.0), Number(2.0), + Number(1500.0), Number(3.0), + ), + Time: time.Time{}, + LastUpdateId: 1, + }) + + quote := &Quote{ + BestBidPrice: Number(1300.0), + BestAskPrice: Number(1301.0), + BidMargin: Number(0.001), + AskMargin: Number(0.001), + BidLayerPips: Number(100.0), + AskLayerPips: Number(100.0), + } + + t.Run("depthPrice bid price at 0", func(t *testing.T) { + price := s.getLayerPrice(0, types.SideTypeBuy, sourceBook, quote, s.DepthQuantity) + + // (1300 + 1200*2)/3 * (1 - 0.001) + assert.InDelta(t, 1232.10, price.Float64(), 0.01) + }) + + t.Run("depthPrice bid price at 1", func(t *testing.T) { + price := s.getLayerPrice(1, types.SideTypeBuy, sourceBook, quote, s.DepthQuantity) + + // (1300 + 1200*2)/3 * (1 - 0.001) - 100 * 0.01 + assert.InDelta(t, 1231.10, price.Float64(), 0.01) + }) + + t.Run("depthPrice ask price at 0", func(t *testing.T) { + price := s.getLayerPrice(0, types.SideTypeSell, sourceBook, quote, s.DepthQuantity) + + // (1301 + 1400*2)/3 * (1 + 0.001) + assert.InDelta(t, 1368.367, price.Float64(), 0.01) + }) + + t.Run("depthPrice ask price at 1", func(t *testing.T) { + price := s.getLayerPrice(1, types.SideTypeSell, sourceBook, quote, s.DepthQuantity) + + // (1301 + 1400*2)/3 * (1 + 0.001) + 100 * 0.01 + assert.InDelta(t, 1369.367, price.Float64(), 0.01) + }) + +} + +func Test_aggregatePrice(t *testing.T) { + bids := PriceVolumeSliceFromText(` + 1000.0, 1.0 + 1200.0, 1.0 + 1400.0, 1.0 +`) + aggregatedPrice1 := aggregatePrice(bids, fixedpoint.NewFromFloat(0.5)) assert.Equal(t, fixedpoint.NewFromFloat(1000.0), aggregatedPrice1)