diff --git a/pkg/strategy/fixedmaker/inventory_skew.go b/pkg/strategy/fixedmaker/inventory_skew.go new file mode 100644 index 000000000..fbbc03c60 --- /dev/null +++ b/pkg/strategy/fixedmaker/inventory_skew.go @@ -0,0 +1,43 @@ +package fixedmaker + +import ( + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +var ( + zero = fixedpoint.Zero + two = fixedpoint.NewFromFloat(2.0) +) + +type InventorySkewBidAskRatios struct { + bidRatio fixedpoint.Value + askRatio fixedpoint.Value +} + +// https://hummingbot.org/strategy-configs/inventory-skew/ +// https://github.com/hummingbot/hummingbot/blob/31fc61d5e71b2c15732142d30983f3ea2be4d466/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pyx +type InventorySkew struct { + InventoryRangeMultiplier fixedpoint.Value `json:"inventoryRangeMultiplier"` + TargetBaseRatio fixedpoint.Value `json:"targetBaseRatio"` +} + +func (s *InventorySkew) CalculateBidAskRatios(quantity fixedpoint.Value, price fixedpoint.Value, baseBalance fixedpoint.Value, quoteBalance fixedpoint.Value) *InventorySkewBidAskRatios { + baseValue := baseBalance.Mul(price) + totalValue := baseValue.Add(quoteBalance) + + inventoryRange := s.InventoryRangeMultiplier.Mul(quantity.Mul(two)).Mul(price) + leftLimit := s.TargetBaseRatio.Mul(totalValue).Sub(inventoryRange) + rightLimit := s.TargetBaseRatio.Mul(totalValue).Add(inventoryRange) + + bidAdjustment := interp(baseValue, leftLimit, rightLimit, two, zero).Clamp(zero, two) + askAdjustment := interp(baseValue, leftLimit, rightLimit, zero, two).Clamp(zero, two) + + return &InventorySkewBidAskRatios{ + bidRatio: bidAdjustment, + askRatio: askAdjustment, + } +} + +func interp(x, x0, x1, y0, y1 fixedpoint.Value) fixedpoint.Value { + return y0.Add(x.Sub(x0).Mul(y1.Sub(y0)).Div(x1.Sub(x0))) +} diff --git a/pkg/strategy/fixedmaker/inventory_skew_test.go b/pkg/strategy/fixedmaker/inventory_skew_test.go new file mode 100644 index 000000000..8c7313997 --- /dev/null +++ b/pkg/strategy/fixedmaker/inventory_skew_test.go @@ -0,0 +1,69 @@ +package fixedmaker + +import ( + "testing" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/stretchr/testify/assert" +) + +func Test_InventorySkew_CalculateBidAskRatios(t *testing.T) { + cases := []struct { + quantity fixedpoint.Value + price fixedpoint.Value + baseBalance fixedpoint.Value + quoteBalance fixedpoint.Value + want *InventorySkewBidAskRatios + }{ + { + quantity: fixedpoint.NewFromFloat(1.0), + price: fixedpoint.NewFromFloat(1000), + baseBalance: fixedpoint.NewFromFloat(1.0), + quoteBalance: fixedpoint.NewFromFloat(1000), + want: &InventorySkewBidAskRatios{ + bidRatio: fixedpoint.NewFromFloat(1.0), + askRatio: fixedpoint.NewFromFloat(1.0), + }, + }, + { + quantity: fixedpoint.NewFromFloat(1.0), + price: fixedpoint.NewFromFloat(1000), + baseBalance: fixedpoint.NewFromFloat(1.0), + quoteBalance: fixedpoint.NewFromFloat(1200), + want: &InventorySkewBidAskRatios{ + bidRatio: fixedpoint.NewFromFloat(1.5), + askRatio: fixedpoint.NewFromFloat(0.5), + }, + }, + { + quantity: fixedpoint.NewFromFloat(1.0), + price: fixedpoint.NewFromFloat(1000), + baseBalance: fixedpoint.NewFromFloat(0.0), + quoteBalance: fixedpoint.NewFromFloat(10000), + want: &InventorySkewBidAskRatios{ + bidRatio: fixedpoint.NewFromFloat(2.0), + askRatio: fixedpoint.NewFromFloat(0.0), + }, + }, + { + quantity: fixedpoint.NewFromFloat(1.0), + price: fixedpoint.NewFromFloat(1000), + baseBalance: fixedpoint.NewFromFloat(2.0), + quoteBalance: fixedpoint.NewFromFloat(0.0), + want: &InventorySkewBidAskRatios{ + bidRatio: fixedpoint.NewFromFloat(0.0), + askRatio: fixedpoint.NewFromFloat(2.0), + }, + }, + } + + for _, c := range cases { + s := &InventorySkew{ + InventoryRangeMultiplier: fixedpoint.NewFromFloat(0.1), + TargetBaseRatio: fixedpoint.NewFromFloat(0.5), + } + got := s.CalculateBidAskRatios(c.quantity, c.price, c.baseBalance, c.quoteBalance) + assert.Equal(t, c.want.bidRatio.Float64(), got.bidRatio.Float64()) + assert.Equal(t, c.want.askRatio.Float64(), got.askRatio.Float64()) + } +}