diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c33bc876..5ffd2ebab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Next version - feat: Moved MangroveJsDeploy from mangrove-strats to this package. Renamed script to EmptyChainDeployer +- fix: rounding when deducing tick from price for LiquidityProvider # 2.0.0 diff --git a/src/liquidityProvider.ts b/src/liquidityProvider.ts index 24a1ecb73..0fc750a21 100644 --- a/src/liquidityProvider.ts +++ b/src/liquidityProvider.ts @@ -217,8 +217,8 @@ class LiquidityProvider { } else if ("price" in p) { // deduce tick & gives from volume & price const price = Big(p.price); - // round down to ensure price is not exceeded - tick = tickPriceHelper.tickFromPrice(price, "roundDown"); + // round up to ensure we get at least the tick we want + tick = tickPriceHelper.tickFromPrice(price, "roundUp"); if (p.ba === "bids") { gives = Big(p.volume).mul(price); } else { diff --git a/src/util/trade.ts b/src/util/trade.ts index 195da0a88..3e6df5505 100644 --- a/src/util/trade.ts +++ b/src/util/trade.ts @@ -25,50 +25,55 @@ class Trade { tradeEventManagement = new TradeEventManagement(); /** - * Get raw parameters to send to Mangrove for a buy order for the given trade and market parameters. + * Get raw parameters to send to Mangrove for a buy or sell order for the given trade and market parameters. */ - getParamsForBuy( + getParamsForTrade( params: Market.TradeParams, market: Market.KeyResolvedForCalculation, + bs: Market.BS, ) { - // validate parameters and setup tickPriceHelper - let fillVolume: Big, maxTick: number, fillWants: boolean; + let fillVolume: Big, + maxTick: number, + fillWants: boolean = bs === "buy"; + const ba = this.bsToBa(bs); const slippage = this.validateSlippage(params.slippage); - const tickPriceHelper = new TickPriceHelper("asks", market); + const tickPriceHelper = new TickPriceHelper(ba, market); if ("limitPrice" in params) { if (Big(params.limitPrice).lte(0)) { throw new Error("Cannot buy at or below price 0"); } - const priceWithSlippage = this.adjustForSlippage( + const limitPriceWithSlippage = this.adjustForSlippage( Big(params.limitPrice), slippage, - "buy", + bs, ); // round down to not exceed the price - maxTick = tickPriceHelper.tickFromPrice(priceWithSlippage, "roundDown"); + maxTick = tickPriceHelper.tickFromPrice( + limitPriceWithSlippage, + "roundDown", + ); if ("volume" in params) { fillVolume = Big(params.volume); - fillWants = true; } else { fillVolume = Big(params.total); - fillWants = false; + fillWants = !fillWants; } } else if ("maxTick" in params) { // in this case, we're merely asking to get the tick adjusted for slippage fillVolume = Big(params.fillVolume); - fillWants = params.fillWants ?? true; + fillWants = params.fillWants ?? fillWants; if (slippage > 0) { - // round down to not exceed the price + // round down to not exceed the price when buying, and up to get at least price for when selling const limitPrice = tickPriceHelper.priceFromTick( params.maxTick, - "roundDown", + bs === "buy" ? "roundDown" : "roundUp", ); const limitPriceWithSlippage = this.adjustForSlippage( limitPrice, slippage, - "buy", + bs, ); - // round down to not exceed the price + // round down to not exceed the price expectations maxTick = tickPriceHelper.tickFromPrice( limitPriceWithSlippage, "roundDown", @@ -78,26 +83,32 @@ class Trade { maxTick = params.maxTick; } } else { - const givesWithSlippage = this.adjustForSlippage( - Big(params.gives), - slippage, - "buy", - ); - fillWants = params.fillWants ?? true; - fillVolume = fillWants ? Big(params.wants) : givesWithSlippage; - // round down to not exceed price expectations - maxTick = tickPriceHelper.tickFromVolumes( - givesWithSlippage, - params.wants, - "roundDown", - ); + let wants = Big(params.wants); + let gives = Big(params.gives); + if (bs === "buy") { + gives = this.adjustForSlippage(gives, slippage, bs); + } else { + wants = this.adjustForSlippage(wants, slippage, bs); + } + fillWants = params.fillWants ?? fillWants; + fillVolume = fillWants ? wants : gives; + // round down to not exceed the price expectations + maxTick = tickPriceHelper.tickFromVolumes(gives, wants, "roundDown"); } return { maxTick, fillVolume: fillWants - ? market.base.toUnits(fillVolume) - : market.quote.toUnits(fillVolume), + ? Market.getOutboundInbound( + ba, + market.base, + market.quote, + ).outbound_tkn.toUnits(fillVolume) + : Market.getOutboundInbound( + ba, + market.base, + market.quote, + ).inbound_tkn.toUnits(fillVolume), fillWants: fillWants, }; } @@ -118,79 +129,6 @@ class Trade { return value.mul(100 + adjustment).div(100); } - /** - * Get raw parameters to send to Mangrove for a sell order for the given trade and market parameters. - */ - getParamsForSell( - params: Market.TradeParams, - market: Market.KeyResolvedForCalculation, - ) { - let fillVolume: Big, maxTick: number, fillWants: boolean; - const slippage = this.validateSlippage(params.slippage); - const tickPriceHelper = new TickPriceHelper("bids", market); - if ("limitPrice" in params) { - if (Big(params.limitPrice).lte(0)) { - throw new Error("Cannot buy at or below price 0"); - } - const priceWithSlippage = this.adjustForSlippage( - Big(params.limitPrice), - slippage, - "sell", - ); - // round down to not exceed the price - maxTick = tickPriceHelper.tickFromPrice(priceWithSlippage, "roundDown"); - if ("volume" in params) { - fillVolume = Big(params.volume); - fillWants = false; - } else { - fillVolume = Big(params.total); - fillWants = true; - } - } else if ("maxTick" in params) { - // in this case, we're merely asking to get the tick adjusted for slippage - fillVolume = Big(params.fillVolume); - fillWants = params.fillWants ?? false; - if (slippage > 0) { - // Round up since a higher price is better for sell - const limitPrice = tickPriceHelper.priceFromTick( - params.maxTick, - "roundUp", - ); - const priceWithSlippage = this.adjustForSlippage( - limitPrice, - slippage, - "sell", - ); - // round down to not exceed the price expectations - maxTick = tickPriceHelper.tickFromPrice(priceWithSlippage, "roundDown"); - } else { - maxTick = params.maxTick; - } - } else { - const wantsWithSlippage = this.adjustForSlippage( - Big(params.wants), - slippage, - "sell", - ); - fillWants = params.fillWants ?? false; - fillVolume = fillWants ? wantsWithSlippage : Big(params.gives); - // round down to not exceed the price expectations - maxTick = tickPriceHelper.tickFromVolumes( - params.gives, - wantsWithSlippage, - "roundDown", - ); - } - - return { - fillVolume: fillWants - ? market.quote.toUnits(fillVolume) - : market.base.toUnits(fillVolume), - maxTick, - fillWants: fillWants, - }; - } - validateSlippage = (slippage = 0) => { if (typeof slippage === "undefined") { return 0; @@ -254,10 +192,11 @@ class Trade { * @returns raw parameters for a market order to send to Mangrove */ getRawParams(bs: Market.BS, params: Market.TradeParams, market: Market) { - const { maxTick, fillVolume, fillWants } = - bs === "buy" - ? this.getParamsForBuy(params, market) - : this.getParamsForSell(params, market); + const { maxTick, fillVolume, fillWants } = this.getParamsForTrade( + params, + market, + bs, + ); const restingOrderParams = "restingOrder" in params ? params.restingOrder : null; diff --git a/test/integration/restingOrder.integration.test.ts b/test/integration/restingOrder.integration.test.ts index 0b2fe1758..d8c0c3eb7 100644 --- a/test/integration/restingOrder.integration.test.ts +++ b/test/integration/restingOrder.integration.test.ts @@ -115,7 +115,10 @@ describe("RestingOrder", () => { fund: askProvision, }); await meAsLP.newAsk({ - price: 10 / 10, + // Set price so that takers will hit it with a price of 1 + price: market + .getSemibook("asks") + .tickPriceHelper.coercePrice(1, "roundDown"), volume: 10, fund: askProvision, }); diff --git a/test/unit/liquidityProvider.unit.test.ts b/test/unit/liquidityProvider.unit.test.ts index 42b9abaa7..46899b340 100644 --- a/test/unit/liquidityProvider.unit.test.ts +++ b/test/unit/liquidityProvider.unit.test.ts @@ -74,7 +74,7 @@ describe("Liquidity provider unit tests suite", () => { ); assert.deepStrictEqual(gives, Big(1)); assert.deepStrictEqual(fund, undefined); - assert.deepStrictEqual(tick, -276400); + assert.deepStrictEqual(tick, -276300); }); it("normalizeOfferParams, with volume and price 1, as bids", async function () { @@ -92,7 +92,7 @@ describe("Liquidity provider unit tests suite", () => { ); assert.deepStrictEqual(gives, Big(1)); assert.deepStrictEqual(fund, undefined); - assert.deepStrictEqual(tick, 276324); + assert.deepStrictEqual(tick, 276325); }); it("normalizeOfferParams, with volume and price 2, as asks", async function () { @@ -110,7 +110,7 @@ describe("Liquidity provider unit tests suite", () => { ); assert.deepStrictEqual(gives, Big(1)); assert.deepStrictEqual(fund, undefined); - assert.deepStrictEqual(tick, 6931); + assert.deepStrictEqual(tick, 6932); }); it("normalizeOfferParams, with volume and price 2, as bids", async function () { @@ -128,7 +128,7 @@ describe("Liquidity provider unit tests suite", () => { ); assert.deepStrictEqual(gives, Big(2)); assert.deepStrictEqual(fund, undefined); - assert.deepStrictEqual(tick, -6932); + assert.deepStrictEqual(tick, -6931); }); it("normalizeOfferParams, with volume and price 2, as asks", async function () { @@ -146,7 +146,7 @@ describe("Liquidity provider unit tests suite", () => { ); assert.deepStrictEqual(gives, Big(1)); assert.deepStrictEqual(fund, undefined); - assert.deepStrictEqual(tick, 6931); + assert.deepStrictEqual(tick, 6932); }); it("normalizeOfferParams, with volume and price 2, as bids", async function () { @@ -164,7 +164,7 @@ describe("Liquidity provider unit tests suite", () => { ); assert.deepStrictEqual(gives, Big(2)); assert.deepStrictEqual(fund, undefined); - assert.deepStrictEqual(tick, -6932); + assert.deepStrictEqual(tick, -6931); }); it("normalizeOfferParams, with gives and wants, as bids", async function () { diff --git a/test/unit/trade.unit.test.ts b/test/unit/trade.unit.test.ts index 6227da5d2..b47d63d17 100644 --- a/test/unit/trade.unit.test.ts +++ b/test/unit/trade.unit.test.ts @@ -11,7 +11,7 @@ import TickPriceHelper from "../../src/util/tickPriceHelper"; import { TokenCalculations } from "../../src/token"; describe("Trade unit tests suite", () => { - describe("getParamsForBuy", () => { + describe("getParamsForTrade bs=buy", () => { let market: Market.KeyResolvedForCalculation; let trade: Trade; const slippage = 3; @@ -35,7 +35,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForBuy(params, market); + const result = trade.getParamsForTrade(params, market, "buy"); //Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -66,7 +66,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForBuy(params, market); + const result = trade.getParamsForTrade(params, market, "buy"); //Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -96,7 +96,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForBuy(params, market); + const result = trade.getParamsForTrade(params, market, "buy"); //Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -128,7 +128,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForBuy(params, market); + const result = trade.getParamsForTrade(params, market, "buy"); //Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -160,7 +160,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForBuy(params, market); + const result = trade.getParamsForTrade(params, market, "buy"); //Assert const givesWithSlippage = Big(params.gives) @@ -195,11 +195,11 @@ describe("Trade unit tests suite", () => { }; // Act - assert.throws(() => trade.getParamsForBuy(params, market)); + assert.throws(() => trade.getParamsForTrade(params, market, "buy")); }); }); - describe("getParamsForSell", () => { + describe("getParamsForTrade bs=sell", () => { let market: Market.KeyResolvedForCalculation; let trade: Trade; const slippage = 3; @@ -223,7 +223,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForSell(params, market); + const result = trade.getParamsForTrade(params, market, "sell"); //Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -254,7 +254,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForSell(params, market); + const result = trade.getParamsForTrade(params, market, "sell"); //Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -284,7 +284,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForSell(params, market); + const result = trade.getParamsForTrade(params, market, "sell"); // Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -316,7 +316,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForSell(params, market); + const result = trade.getParamsForTrade(params, market, "sell"); // Assert const expectedTickWithSlippage = tickPriceHelper.tickFromPrice( @@ -348,7 +348,7 @@ describe("Trade unit tests suite", () => { }; //Act - const result = trade.getParamsForSell(params, market); + const result = trade.getParamsForTrade(params, market, "sell"); //Assert const wantsWithSlippage = Big(params.wants) @@ -381,7 +381,7 @@ describe("Trade unit tests suite", () => { }; // Act - assert.throws(() => trade.getParamsForSell(params, market)); + assert.throws(() => trade.getParamsForTrade(params, market, "sell")); }); });