diff --git a/CHANGELOG.md b/CHANGELOG.md index c77c95b9e..ddde85957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,12 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +## v2.1.1 + +### Improvements + +* (x/liquidity) [\#50](https://github.com/crescent-network/crescent/pull/50) Enable detailed configuration for order book responses + ## v2.1.0 ### Client Breaking Changes diff --git a/x/liquidity/keeper/grpc_query.go b/x/liquidity/keeper/grpc_query.go index 7a4060207..adf78306f 100644 --- a/x/liquidity/keeper/grpc_query.go +++ b/x/liquidity/keeper/grpc_query.go @@ -555,7 +555,21 @@ func (k Querier) OrderBooks(c context.Context, req *types.QueryOrderBooksRequest ov := ob.MakeView() ov.Match() - pairs = append(pairs, types.MakeOrderBookPairResponse(pair.Id, ov, lowestPrice, highestPrice, int(tickPrec), int(req.NumTicks))) + pairs = append( + pairs, types.MakeOrderBookPairResponse( + pair.Id, ov, lowestPrice, highestPrice, int(tickPrec), + types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: int(req.NumTicks), + }, + types.OrderBookConfig{ + PriceUnitPower: 1, + MaxNumTicks: int(req.NumTicks), + }, + types.OrderBookConfig{ + PriceUnitPower: 2, + MaxNumTicks: int(req.NumTicks), + })) } return &types.QueryOrderBooksResponse{ diff --git a/x/liquidity/types/example_orderbook_test.go b/x/liquidity/types/example_orderbook_test.go index 03f19474f..fd8f47371 100644 --- a/x/liquidity/types/example_orderbook_test.go +++ b/x/liquidity/types/example_orderbook_test.go @@ -25,7 +25,10 @@ func ExampleMakeOrderBookPairResponse() { tickPrec := 1 lowestPrice := utils.ParseDec("0") highestPrice := utils.ParseDec("20") - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, tickPrec, 20) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, tickPrec, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 20, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], resp.BasePrice) // Output: @@ -62,7 +65,10 @@ func ExampleMakeOrderBookPairResponse_pool() { ov := ob.MakeView() ov.Match() tickPrec := 3 - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, tickPrec, 10) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, tickPrec, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], resp.BasePrice) // Output: @@ -113,7 +119,10 @@ func ExampleMakeOrderBookPairResponse_userOrder() { ov := ob.MakeView() ov.Match() tickPrec := 3 - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, tickPrec, 10) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, tickPrec, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }) basePrice, found := types.OrderBookBasePrice(ov, tickPrec) if !found { panic("base price not found") @@ -159,7 +168,10 @@ func ExampleMakeOrderBookPairResponse_match() { ov := ob.MakeView() ov.Match() tickPrec := 3 - resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), tickPrec, 10) + resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), tickPrec, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }) basePrice, found := types.OrderBookBasePrice(ov, tickPrec) if !found { panic("base price not found") @@ -188,7 +200,10 @@ func ExampleMakeOrderBookPairResponse_zigzag() { ov.Match() basePrice, _ := types.OrderBookBasePrice(ov, 4) - resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), 3, 20) + resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), 3, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 20, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], basePrice) // Output: @@ -217,7 +232,10 @@ func ExampleMakeOrderBookPairResponse_edgecase1() { ov.Match() basePrice, _ := types.OrderBookBasePrice(ov, 4) - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 3, 10) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 3, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], basePrice) // Output: @@ -260,7 +278,10 @@ func ExampleMakeOrderBookPairResponse_edgecase2() { ov := ob.MakeView() ov.Match() - resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), 3, 10) + resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), 3, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], resp.BasePrice) // Output: @@ -283,7 +304,10 @@ func ExampleMakeOrderBookPairResponse_edgecase3() { ov := ob.MakeView() ov.Match() - resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), 3, 10) + resp := types.MakeOrderBookPairResponse(1, ov, utils.ParseDec("0.9"), utils.ParseDec("1.1"), 3, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], resp.BasePrice) // Output: @@ -307,7 +331,20 @@ func ExampleMakeOrderBookPairResponse_priceUnits1() { ov := ob.MakeView() ov.Match() - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, 10) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, + types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }, + types.OrderBookConfig{ + PriceUnitPower: 1, + MaxNumTicks: 10, + }, + types.OrderBookConfig{ + PriceUnitPower: 2, + MaxNumTicks: 10, + }, + ) types.PrintOrderBookResponse(resp.OrderBooks[0], resp.BasePrice) types.PrintOrderBookResponse(resp.OrderBooks[1], resp.BasePrice) types.PrintOrderBookResponse(resp.OrderBooks[2], resp.BasePrice) @@ -401,7 +438,20 @@ func ExampleMakeOrderBookPairResponse_priceUnits2() { ov := ob.MakeView() ov.Match() - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, 10) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, + types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 10, + }, + types.OrderBookConfig{ + PriceUnitPower: 1, + MaxNumTicks: 10, + }, + types.OrderBookConfig{ + PriceUnitPower: 2, + MaxNumTicks: 10, + }, + ) types.PrintOrderBookResponse(resp.OrderBooks[0], resp.BasePrice) types.PrintOrderBookResponse(resp.OrderBooks[1], resp.BasePrice) types.PrintOrderBookResponse(resp.OrderBooks[2], resp.BasePrice) diff --git a/x/liquidity/types/orderbook.go b/x/liquidity/types/orderbook.go index 7ab46ba31..71bb5193c 100644 --- a/x/liquidity/types/orderbook.go +++ b/x/liquidity/types/orderbook.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "sort" sdk "github.com/cosmos/cosmos-sdk/types" @@ -24,7 +25,13 @@ func OrderBookBasePrice(ov amm.OrderView, tickPrec int) (sdk.Dec, bool) { } } -func MakeOrderBookPairResponse(pairId uint64, ov *amm.OrderBookView, lowestPrice, highestPrice sdk.Dec, tickPrec, numTicks int) OrderBookPairResponse { +// OrderBookConfig defines configuration parameter for an order book response. +type OrderBookConfig struct { + PriceUnitPower int + MaxNumTicks int +} + +func MakeOrderBookPairResponse(pairId uint64, ov *amm.OrderBookView, lowestPrice, highestPrice sdk.Dec, tickPrec int, configs ...OrderBookConfig) OrderBookPairResponse { resp := OrderBookPairResponse{ PairId: pairId, } @@ -35,19 +42,24 @@ func MakeOrderBookPairResponse(pairId uint64, ov *amm.OrderBookView, lowestPrice resp.BasePrice = basePrice ammTickPrec := amm.TickPrecision(tickPrec) + sort.Slice(configs, func(i, j int) bool { + return configs[i].PriceUnitPower < configs[j].PriceUnitPower + }) + lowestPriceUnitMaxNumTicks := configs[0].MaxNumTicks + highestBuyPrice, foundHighestBuyPrice := ov.HighestBuyPrice() lowestSellPrice, foundLowestSellPrice := ov.LowestSellPrice() var smallestPriceUnit sdk.Dec if foundLowestSellPrice { currentPrice := lowestSellPrice - for i := 0; i < numTicks && currentPrice.LTE(highestPrice); { + for i := 0; i < lowestPriceUnitMaxNumTicks && currentPrice.LTE(highestPrice); { amtInclusive := ov.SellAmountOver(currentPrice, true) amtExclusive := ov.SellAmountOver(currentPrice, false) amt := amtInclusive.Sub(amtExclusive) if amt.IsPositive() { i++ - if i == numTicks { + if i == lowestPriceUnitMaxNumTicks { break } } @@ -61,9 +73,9 @@ func MakeOrderBookPairResponse(pairId uint64, ov *amm.OrderBookView, lowestPrice smallestPriceUnit = ammTickPrec.TickGap(highestBuyPrice) } - for i := 0; i < 3; i++ { + for _, config := range configs { priceUnit := smallestPriceUnit - for j := 0; j < i; j++ { + for j := 0; j < config.PriceUnitPower; j++ { priceUnit = priceUnit.MulInt64(10) } ob := OrderBookResponse{ @@ -75,7 +87,7 @@ func MakeOrderBookPairResponse(pairId uint64, ov *amm.OrderBookView, lowestPrice startPrice := FitPriceToTickGap(lowestSellPrice, priceUnit, false) currentPrice := startPrice accAmt := sdk.ZeroInt() - for j := 0; j < numTicks && currentPrice.LTE(highestPrice); { + for j := 0; j < config.MaxNumTicks && currentPrice.LTE(highestPrice); { amt := ov.SellAmountUnder(currentPrice, true).Sub(accAmt) if amt.IsPositive() { ob.Sells = append(ob.Sells, OrderBookTickResponse{ @@ -100,7 +112,7 @@ func MakeOrderBookPairResponse(pairId uint64, ov *amm.OrderBookView, lowestPrice startPrice := FitPriceToTickGap(highestBuyPrice, priceUnit, true) currentPrice := startPrice accAmt := sdk.ZeroInt() - for j := 0; j < numTicks && currentPrice.GTE(lowestPrice) && !currentPrice.IsNegative(); { + for j := 0; j < config.MaxNumTicks && currentPrice.GTE(lowestPrice) && !currentPrice.IsNegative(); { amt := ov.BuyAmountOver(currentPrice, true).Sub(accAmt) if amt.IsPositive() { ob.Buys = append(ob.Buys, OrderBookTickResponse{ diff --git a/x/liquidity/types/orderbook_test.go b/x/liquidity/types/orderbook_test.go index 651dc0694..bbb2ddb65 100644 --- a/x/liquidity/types/orderbook_test.go +++ b/x/liquidity/types/orderbook_test.go @@ -30,7 +30,10 @@ func TestMakeOrderBookResponse(t *testing.T) { panic("base price not found") } - resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, 20) + resp := types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: 20, + }) types.PrintOrderBookResponse(resp.OrderBooks[0], basePrice) } @@ -67,5 +70,18 @@ func makeOrderBookPairResponse(numOrders, numPools, numTicks, tickPrec int) type ov := ob.MakeView() ov.Match() - return types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, numTicks) + return types.MakeOrderBookPairResponse(1, ov, lowestPrice, highestPrice, 4, + types.OrderBookConfig{ + PriceUnitPower: 0, + MaxNumTicks: numTicks, + }, + types.OrderBookConfig{ + PriceUnitPower: 1, + MaxNumTicks: numTicks, + }, + types.OrderBookConfig{ + PriceUnitPower: 2, + MaxNumTicks: numTicks, + }, + ) }