From 69e6e5b12066fba7e2f775421f9348f9e6aeeac0 Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Sat, 9 Mar 2024 22:06:34 +0100 Subject: [PATCH] Fix MarketOrderBook avgPriceAndMatchedVolumeBuy --- src/objects/include/marketorderbook.hpp | 2 +- src/objects/src/marketorderbook.cpp | 43 +++++++++++++++-------- src/objects/test/marketorderbook_test.cpp | 16 +++++++++ 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/objects/include/marketorderbook.hpp b/src/objects/include/marketorderbook.hpp index b3b32f45..8b04515c 100644 --- a/src/objects/include/marketorderbook.hpp +++ b/src/objects/include/marketorderbook.hpp @@ -220,7 +220,7 @@ class MarketOrderBook { AmountAtPrice avgPriceAndMatchedVolumeSell(MonetaryAmount baseAmount, MonetaryAmount price) const; - AmountAtPrice avgPriceAndMatchedVolumeBuy(MonetaryAmount quoteAmount, MonetaryAmount price) const; + AmountAtPrice avgPriceAndMatchedVolumeBuy(MonetaryAmount amountInBaseOrQuote, MonetaryAmount price) const; /// Attempt to convert given amount expressed in base currency to quote currency. /// It may not be possible, in which case an empty optional will be returned. diff --git a/src/objects/src/marketorderbook.cpp b/src/objects/src/marketorderbook.cpp index a0b956c5..3df09945 100644 --- a/src/objects/src/marketorderbook.cpp +++ b/src/objects/src/marketorderbook.cpp @@ -267,6 +267,7 @@ MarketOrderBook::AmountPerPriceVec MarketOrderBook::computePricesAtWhichAmountWo MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeSell(MonetaryAmount baseAmount, MonetaryAmount price) const { MonetaryAmount avgPrice(0, _market.quote()); + MonetaryAmount remainingBaseAmount = baseAmount; for (int pos = _lowestAskPricePos - 1; pos >= 0; --pos) { const MonetaryAmount linePrice = priceAt(pos); @@ -283,13 +284,17 @@ MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeSell(Mon } } MonetaryAmount matchedAmount = baseAmount - remainingBaseAmount; - return {matchedAmount, avgPrice / matchedAmount.toNeutral()}; + if (matchedAmount != 0) { + avgPrice /= matchedAmount.toNeutral(); + } + return {matchedAmount, avgPrice}; } -MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeBuy(MonetaryAmount quoteAmount, +MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeBuy(MonetaryAmount amountInBaseOrQuote, MonetaryAmount price) const { - MonetaryAmount remainingQuoteAmount = quoteAmount; - MonetaryAmount matchedAmount; + MonetaryAmount remainingAmountInBaseOrQuote = amountInBaseOrQuote; + MonetaryAmount matchedAmount(0, _market.base()); + MonetaryAmount avgPrice(0, _market.quote()); const int nbOrders = _orders.size(); for (int pos = _highestBidPricePos + 1; pos < nbOrders; ++pos) { const MonetaryAmount linePrice = priceAt(pos); @@ -299,24 +304,32 @@ MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeBuy(Mone const MonetaryAmount lineAmount = negAmountAt(pos); MonetaryAmount quoteAmountToEat = lineAmount.toNeutral() * linePrice; - if (quoteAmountToEat < remainingQuoteAmount) { - matchedAmount += lineAmount; + if (remainingAmountInBaseOrQuote.currencyCode() == _market.quote()) { + if (quoteAmountToEat < remainingAmountInBaseOrQuote) { + matchedAmount += lineAmount; + } else { + quoteAmountToEat = remainingAmountInBaseOrQuote; + matchedAmount += MonetaryAmount(remainingAmountInBaseOrQuote / linePrice, _market.base()); + } + remainingAmountInBaseOrQuote -= quoteAmountToEat; + avgPrice += quoteAmountToEat; } else { - quoteAmountToEat = remainingQuoteAmount; - matchedAmount += MonetaryAmount(remainingQuoteAmount / linePrice, _market.base()); - } + // amountInBaseOrQuote is in base currency + const MonetaryAmount baseAmountToEat = std::min(lineAmount, remainingAmountInBaseOrQuote); + matchedAmount += baseAmountToEat; + remainingAmountInBaseOrQuote -= baseAmountToEat; - remainingQuoteAmount -= quoteAmountToEat; + avgPrice += baseAmountToEat.toNeutral() * linePrice; + } - if (remainingQuoteAmount == 0 || pos + 1 == nbOrders) { + if (remainingAmountInBaseOrQuote == 0 || pos + 1 == nbOrders) { break; } } - price = quoteAmount - remainingQuoteAmount; if (matchedAmount != 0) { - price /= matchedAmount.toNeutral(); + avgPrice /= matchedAmount.toNeutral(); } - return {matchedAmount, price}; + return {matchedAmount, avgPrice}; } std::optional MarketOrderBook::computeMinPriceAtWhichAmountWouldBeSoldImmediately( @@ -421,7 +434,7 @@ MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolume(TradeSi MonetaryAmount price) const { switch (tradeSide) { case TradeSide::kBuy: - return avgPriceAndMatchedVolumeBuy(amount * price, price); + return avgPriceAndMatchedVolumeBuy(amount, price); case TradeSide::kSell: return avgPriceAndMatchedVolumeSell(amount, price); default: diff --git a/src/objects/test/marketorderbook_test.cpp b/src/objects/test/marketorderbook_test.cpp index cd39583c..d93474bd 100644 --- a/src/objects/test/marketorderbook_test.cpp +++ b/src/objects/test/marketorderbook_test.cpp @@ -219,6 +219,22 @@ TEST_F(MarketOrderBookTestCase3, Convert) { MonetaryAmount("216266.409928471248", "XLM")); } +TEST_F(MarketOrderBookTestCase3, AvgPriceAndMatchedVolume) { + EXPECT_EQ(marketOrderBook.avgPriceAndMatchedVolume(TradeSide::kBuy, MonetaryAmount(100000, "XLM"), + MonetaryAmount("0.000007121", "BTC")), + AmountAtPrice(MonetaryAmount(100000, "XLM"), MonetaryAmount("0.0000071176273715", "BTC"))); + EXPECT_EQ(marketOrderBook.avgPriceAndMatchedVolume(TradeSide::kBuy, MonetaryAmount(100000, "XLM"), + MonetaryAmount("0.000007090", "BTC")), + AmountAtPrice(MonetaryAmount(0, "XLM"), MonetaryAmount(0, "BTC"))); + + EXPECT_EQ(marketOrderBook.avgPriceAndMatchedVolume(TradeSide::kSell, MonetaryAmount("4500000", "XLM"), + MonetaryAmount("0.000007079", "BTC")), + AmountAtPrice(MonetaryAmount("411248.27", "XLM"), MonetaryAmount("0.00000708595487037", "BTC"))); + EXPECT_EQ(marketOrderBook.avgPriceAndMatchedVolume(TradeSide::kSell, MonetaryAmount("4500000", "XLM"), + MonetaryAmount("0.000007110", "BTC")), + AmountAtPrice(MonetaryAmount(0, "XLM"), MonetaryAmount(0, "BTC"))); +} + class MarketOrderBookTestCaseExtended1 : public ::testing::Test { protected: TimePoint time{};