diff --git a/src/api/common/src/commonapi.cpp b/src/api/common/src/commonapi.cpp index efadcd9f..27cc3f53 100644 --- a/src/api/common/src/commonapi.cpp +++ b/src/api/common/src/commonapi.cpp @@ -96,13 +96,13 @@ vector CommonAPI::FiatsFunc::retrieveFiatsSource1() { std::string_view data = _curlHandle1.query("", CurlOptions(HttpRequestType::kGet)); if (data.empty()) { - log::error("Error parsing currency codes, no fiats found from first source"); + log::warn("Error parsing currency codes, no fiats found from first source"); return fiatsVec; } static constexpr bool kAllowExceptions = false; json dataCSV = json::parse(data, nullptr, kAllowExceptions); if (dataCSV.is_discarded()) { - log::error("Error parsing json data of currency codes from source 1"); + log::warn("Error parsing json data of currency codes from source 1"); return fiatsVec; } for (const json& fiatData : dataCSV) { diff --git a/src/api/exchanges/src/binancepublicapi.cpp b/src/api/exchanges/src/binancepublicapi.cpp index fbd8b76f..863aea0a 100644 --- a/src/api/exchanges/src/binancepublicapi.cpp +++ b/src/api/exchanges/src/binancepublicapi.cpp @@ -37,6 +37,7 @@ #include "marketorderbook.hpp" #include "monetaryamount.hpp" #include "monetaryamountbycurrencyset.hpp" +#include "order-book-line.hpp" #include "permanentcurloptions.hpp" #include "runmodes.hpp" #include "timedef.hpp" @@ -485,28 +486,29 @@ MarketOrderBook BinancePublic::OrderBookFunc::operator()(Market mk, int depth) { lb = std::next(kAuthorizedDepths.end(), -1); log::error("Invalid depth {}, default to {}", depth, *lb); } - using OrderBookVec = vector; - OrderBookVec orderBookLines; - CurlPostData postData{{"symbol", mk.assetsPairStrUpper()}, {"limit", *lb}}; - json asksAndBids = PublicQuery(_commonInfo._curlHandle, "/api/v3/depth", postData); + MarketOrderBookLines orderBookLines; + + const CurlPostData postData{{"symbol", mk.assetsPairStrUpper()}, {"limit", *lb}}; + const json asksAndBids = PublicQuery(_commonInfo._curlHandle, "/api/v3/depth", postData); + const auto nowTime = Clock::now(); const auto asksIt = asksAndBids.find("asks"); const auto bidsIt = asksAndBids.find("bids"); if (asksIt != asksAndBids.end() && bidsIt != asksAndBids.end()) { - orderBookLines.reserve(static_cast(asksIt->size() + bidsIt->size())); + orderBookLines.reserve(asksIt->size() + bidsIt->size()); for (const auto& asksOrBids : {asksIt, bidsIt}) { const auto type = asksOrBids == asksIt ? OrderBookLine::Type::kAsk : OrderBookLine::Type::kBid; for (const auto& priceQuantityPair : *asksOrBids) { MonetaryAmount amount(priceQuantityPair.back().get(), mk.base()); MonetaryAmount price(priceQuantityPair.front().get(), mk.quote()); - orderBookLines.emplace_back(amount, price, type); + orderBookLines.push(amount, price, type); } } } - return MarketOrderBook(Clock::now(), mk, orderBookLines); + return MarketOrderBook(nowTime, mk, orderBookLines); } MonetaryAmount BinancePublic::TradedVolumeFunc::operator()(Market mk) { diff --git a/src/api/exchanges/src/bithumbpublicapi.cpp b/src/api/exchanges/src/bithumbpublicapi.cpp index 2360ae5f..24d3b014 100644 --- a/src/api/exchanges/src/bithumbpublicapi.cpp +++ b/src/api/exchanges/src/bithumbpublicapi.cpp @@ -18,7 +18,6 @@ #include "cct_json.hpp" #include "cct_log.hpp" #include "cct_string.hpp" -#include "cct_vector.hpp" #include "coincenterinfo.hpp" #include "commonapi.hpp" #include "curlhandle.hpp" @@ -35,6 +34,7 @@ #include "market.hpp" #include "marketorderbook.hpp" #include "monetaryamount.hpp" +#include "order-book-line.hpp" #include "permanentcurloptions.hpp" #include "stringhelpers.hpp" #include "timedef.hpp" @@ -173,7 +173,7 @@ CurrencyExchangeFlatSet BithumbPublic::TradableCurrenciesFunc::operator()() { } namespace { -MarketOrderBookMap GetOrderbooks(CurlHandle& curlHandle, const CoincenterInfo& config, +MarketOrderBookMap GetOrderBooks(CurlHandle& curlHandle, const CoincenterInfo& config, const ExchangeConfig& exchangeConfig, std::optional optM = std::nullopt, std::optional optDepth = std::nullopt) { MarketOrderBookMap ret; @@ -195,11 +195,11 @@ MarketOrderBookMap GetOrderbooks(CurlHandle& curlHandle, const CoincenterInfo& c if (!result.empty()) { // Note: as of 2021-02-24, Bithumb payment currency is always KRW. Format of json may change once it's not the case // anymore + const auto nowTime = Clock::now(); std::string_view quoteCurrency = result["payment_currency"].get(); if (quoteCurrency != "KRW") { log::error("Unexpected Bithumb reply for orderbook. May require code api update"); } - const auto time = Clock::now(); CurrencyCode quoteCurrencyCode(config.standardizeCurrencyCode(quoteCurrency)); const CurrencyCodeSet& excludedCurrencies = exchangeConfig.excludedCurrenciesAll(); for (const auto& [baseOrSpecial, asksAndBids] : result.items()) { @@ -231,37 +231,36 @@ MarketOrderBookMap GetOrderbooks(CurlHandle& curlHandle, const CoincenterInfo& c "asks": [{"quantity" : "2.67575", "price" : "506000"}, {"quantity" : "3.54343","price" : "507000"}] */ - using OrderBookVec = vector; - OrderBookVec orderBookLines; - orderBookLines.reserve(static_cast(asksBids[0]->size() + asksBids[1]->size())); + MarketOrderBookLines orderBookLines; + orderBookLines.reserve(asksBids[0]->size() + asksBids[1]->size()); for (const json* asksOrBids : asksBids) { const auto type = asksOrBids == asksBids[0] ? OrderBookLine::Type::kAsk : OrderBookLine::Type::kBid; for (const json& priceQuantityPair : *asksOrBids) { MonetaryAmount amount(priceQuantityPair["quantity"].get(), baseCurrencyCode); MonetaryAmount price(priceQuantityPair["price"].get(), quoteCurrencyCode); - orderBookLines.emplace_back(amount, price, type); + orderBookLines.push(amount, price, type); } } Market market(baseCurrencyCode, quoteCurrencyCode); - ret.insert_or_assign(market, MarketOrderBook(time, market, orderBookLines)); + ret.insert_or_assign(market, MarketOrderBook(nowTime, market, orderBookLines)); if (singleMarketQuote) { break; } } } } - log::info("Retrieved {} markets (+ orderbooks) from Bithumb", ret.size()); + log::info("Retrieved {} markets (+ order books) from Bithumb", ret.size()); return ret; } } // namespace MarketOrderBookMap BithumbPublic::AllOrderBooksFunc::operator()() { - return GetOrderbooks(_curlHandle, _coincenterInfo, _exchangeConfig); + return GetOrderBooks(_curlHandle, _coincenterInfo, _exchangeConfig); } MarketOrderBook BithumbPublic::OrderBookFunc::operator()(Market mk, int depth) { - MarketOrderBookMap marketOrderBookMap = GetOrderbooks(_curlHandle, _coincenterInfo, _exchangeConfig, mk, depth); + MarketOrderBookMap marketOrderBookMap = GetOrderBooks(_curlHandle, _coincenterInfo, _exchangeConfig, mk, depth); auto it = marketOrderBookMap.find(mk); if (it == marketOrderBookMap.end()) { throw exception("Cannot find {} in market order book map", mk); diff --git a/src/api/exchanges/src/huobipublicapi.cpp b/src/api/exchanges/src/huobipublicapi.cpp index 8d808c5c..f10a86b1 100644 --- a/src/api/exchanges/src/huobipublicapi.cpp +++ b/src/api/exchanges/src/huobipublicapi.cpp @@ -33,6 +33,7 @@ #include "marketorderbook.hpp" #include "monetaryamount.hpp" #include "monetaryamountbycurrencyset.hpp" +#include "order-book-line.hpp" #include "permanentcurloptions.hpp" #include "timedef.hpp" #include "toupperlower-string.hpp" @@ -378,10 +379,10 @@ MarketOrderBook HuobiPublic::OrderBookFunc::operator()(Market mk, int depth) { postData.append("depth", *lb); } } - using OrderBookVec = vector; - OrderBookVec orderBookLines; + MarketOrderBookLines orderBookLines; const json asksAndBids = PublicQuery(_curlHandle, "/market/depth", postData); + const auto nowTime = Clock::now(); const auto asksIt = asksAndBids.find("asks"); const auto bidsIt = asksAndBids.find("bids"); if (asksIt != asksAndBids.end() && bidsIt != asksAndBids.end()) { @@ -393,7 +394,7 @@ MarketOrderBook HuobiPublic::OrderBookFunc::operator()(Market mk, int depth) { MonetaryAmount amount(priceQuantityPair.back().get(), mk.base()); MonetaryAmount price(priceQuantityPair.front().get(), mk.quote()); - orderBookLines.emplace_back(amount, price, type); + orderBookLines.push(amount, price, type); if (++currentDepth == depth) { if (depth < static_cast(asksOrBids->size())) { log::debug("Truncate number of {} prices in order book to {}", @@ -404,7 +405,7 @@ MarketOrderBook HuobiPublic::OrderBookFunc::operator()(Market mk, int depth) { } } } - return MarketOrderBook(Clock::now(), mk, orderBookLines); + return MarketOrderBook(nowTime, mk, orderBookLines); } MonetaryAmount HuobiPublic::sanitizePrice(Market mk, MonetaryAmount pri) { diff --git a/src/api/exchanges/src/krakenpublicapi.cpp b/src/api/exchanges/src/krakenpublicapi.cpp index b8099737..02d61914 100644 --- a/src/api/exchanges/src/krakenpublicapi.cpp +++ b/src/api/exchanges/src/krakenpublicapi.cpp @@ -13,7 +13,6 @@ #include "cct_json.hpp" #include "cct_log.hpp" #include "cct_string.hpp" -#include "cct_vector.hpp" #include "coincenterinfo.hpp" #include "commonapi.hpp" #include "curlhandle.hpp" @@ -32,6 +31,7 @@ #include "marketorderbook.hpp" #include "monetaryamount.hpp" #include "monetaryamountbycurrencyset.hpp" +#include "order-book-line.hpp" #include "permanentcurloptions.hpp" #include "timedef.hpp" #include "tradeside.hpp" @@ -299,32 +299,29 @@ MarketOrderBook KrakenPublic::OrderBookFunc::operator()(Market mk, int count) { string krakenAssetPair = krakenCurrencyExchangeBase.altStr(); krakenAssetPair.append(krakenCurrencyExchangeQuote.altStr()); - using OrderBookVec = vector; - OrderBookVec orderBookLines; + MarketOrderBookLines orderBookLines; json result = PublicQuery(_curlHandle, "/public/Depth", {{"pair", krakenAssetPair}, {"count", count}}); + const auto nowTime = Clock::now(); if (!result.empty()) { const json& entry = result.front(); const auto asksIt = entry.find("asks"); const auto bidsIt = entry.find("bids"); - orderBookLines.reserve(static_cast(asksIt->size() + bidsIt->size())); + orderBookLines.reserve(asksIt->size() + bidsIt->size()); for (const auto& asksOrBids : {asksIt, bidsIt}) { const auto type = asksOrBids == asksIt ? OrderBookLine::Type::kAsk : OrderBookLine::Type::kBid; for (const auto& priceQuantityTuple : *asksOrBids) { - std::string_view priceStr = priceQuantityTuple[0].get(); - std::string_view amountStr = priceQuantityTuple[1].get(); + MonetaryAmount amount(priceQuantityTuple[1].get(), mk.base()); + MonetaryAmount price(priceQuantityTuple[0].get(), mk.quote()); - MonetaryAmount amount(amountStr, mk.base()); - MonetaryAmount price(priceStr, mk.quote()); - - orderBookLines.emplace_back(amount, price, type); + orderBookLines.push(amount, price, type); } } } const auto volAndPriNbDecimals = _marketsCache.get().second.find(mk)->second.volAndPriNbDecimals; - return MarketOrderBook(Clock::now(), mk, orderBookLines, volAndPriNbDecimals); + return MarketOrderBook(nowTime, mk, orderBookLines, volAndPriNbDecimals); } KrakenPublic::TickerFunc::Last24hTradedVolumeAndLatestPricePair KrakenPublic::TickerFunc::operator()(Market mk) { diff --git a/src/api/exchanges/src/kucoinpublicapi.cpp b/src/api/exchanges/src/kucoinpublicapi.cpp index 63e7490b..c1b3193c 100644 --- a/src/api/exchanges/src/kucoinpublicapi.cpp +++ b/src/api/exchanges/src/kucoinpublicapi.cpp @@ -32,6 +32,7 @@ #include "marketorderbook.hpp" #include "monetaryamount.hpp" #include "monetaryamountbycurrencyset.hpp" +#include "order-book-line.hpp" #include "permanentcurloptions.hpp" #include "stringhelpers.hpp" #include "timedef.hpp" @@ -262,13 +263,13 @@ MarketOrderBookMap KucoinPublic::AllOrderBooksFunc::operator()(int depth) { namespace { template void FillOrderBook(Market mk, int depth, OrderBookLine::Type type, InputIt beg, InputIt end, - vector& orderBookLines) { + MarketOrderBookLines& orderBookLines) { int currentDepth = 0; for (auto it = beg; it != end; ++it) { MonetaryAmount price((*it)[0].template get(), mk.quote()); MonetaryAmount amount((*it)[1].template get(), mk.base()); - orderBookLines.emplace_back(amount, price, type); + orderBookLines.push(amount, price, type); if (++currentDepth == depth) { if (++it != end) { log::debug("Truncate number of {} prices in order book to {}", @@ -292,10 +293,10 @@ MarketOrderBook KucoinPublic::OrderBookFunc::operator()(Market mk, int depth) { string endpoint("/api/v1/market/orderbook/level2_"); AppendString(endpoint, *lb); - using OrderBookVec = vector; - OrderBookVec orderBookLines; + MarketOrderBookLines orderBookLines; - json asksAndBids = PublicQuery(_curlHandle, endpoint, GetSymbolPostData(mk)); + const json asksAndBids = PublicQuery(_curlHandle, endpoint, GetSymbolPostData(mk)); + const auto nowTime = Clock::now(); const auto asksIt = asksAndBids.find("asks"); const auto bidsIt = asksAndBids.find("bids"); if (asksIt != asksAndBids.end() && bidsIt != asksAndBids.end()) { @@ -305,7 +306,7 @@ MarketOrderBook KucoinPublic::OrderBookFunc::operator()(Market mk, int depth) { FillOrderBook(mk, depth, OrderBookLine::Type::kAsk, asksIt->begin(), asksIt->end(), orderBookLines); } - return MarketOrderBook(Clock::now(), mk, orderBookLines); + return MarketOrderBook(nowTime, mk, orderBookLines); } MonetaryAmount KucoinPublic::sanitizePrice(Market mk, MonetaryAmount pri) { diff --git a/src/api/exchanges/src/upbitpublicapi.cpp b/src/api/exchanges/src/upbitpublicapi.cpp index 94cf17fb..65aeaa85 100644 --- a/src/api/exchanges/src/upbitpublicapi.cpp +++ b/src/api/exchanges/src/upbitpublicapi.cpp @@ -13,7 +13,6 @@ #include "cct_exception.hpp" #include "cct_json.hpp" #include "cct_log.hpp" -#include "cct_smallvector.hpp" #include "cct_string.hpp" #include "cct_vector.hpp" #include "coincenterinfo.hpp" @@ -33,6 +32,7 @@ #include "marketorderbook.hpp" #include "monetaryamount.hpp" #include "monetaryamountbycurrencyset.hpp" +#include "order-book-line.hpp" #include "permanentcurloptions.hpp" #include "timedef.hpp" #include "tradeside.hpp" @@ -190,21 +190,26 @@ MarketOrderBookMap ParseOrderBooks(const json& result, int depth) { continue; } - SmallVector orderBookLines; - /// Remember, Upbit markets are inverted, quote first then base CurrencyCode quote(std::string_view(marketStr.begin(), marketStr.begin() + dashPos)); CurrencyCode base(std::string_view(marketStr.begin() + dashPos + 1, marketStr.end())); Market market(base, quote); - for (const json& orderbookDetails : marketDetails["orderbook_units"]) { + + const auto& orderBookLinesJson = marketDetails["orderbook_units"]; + + MarketOrderBookLines orderBookLines; + + orderBookLines.reserve(orderBookLinesJson.size() * 2U); + + for (const json& orderbookDetails : orderBookLinesJson) { // Amounts are not strings, but doubles MonetaryAmount askPri(orderbookDetails["ask_price"].get(), quote); MonetaryAmount bidPri(orderbookDetails["bid_price"].get(), quote); MonetaryAmount askVol(orderbookDetails["ask_size"].get(), base); MonetaryAmount bidVol(orderbookDetails["bid_size"].get(), base); - orderBookLines.emplace_back(askVol, askPri, OrderBookLine::Type::kAsk); - orderBookLines.emplace_back(bidVol, bidPri, OrderBookLine::Type::kBid); + orderBookLines.pushAsk(askVol, askPri); + orderBookLines.pushBid(bidVol, bidPri); if (static_cast(orderBookLines.size() / 2) == depth) { // Upbit does not have a depth parameter, the only thing we can do is to truncate it manually diff --git a/src/engine/src/exchangesorchestrator.cpp b/src/engine/src/exchangesorchestrator.cpp index a934e52c..cd10c242 100644 --- a/src/engine/src/exchangesorchestrator.cpp +++ b/src/engine/src/exchangesorchestrator.cpp @@ -188,6 +188,7 @@ BalancePerExchange ExchangesOrchestrator::getBalance(std::span balancePortfolios(selectedExchanges.size()); + _threadPool.parallelTransform( selectedExchanges.begin(), selectedExchanges.end(), balancePortfolios.begin(), [&balanceOptions](Exchange *exchange) { return exchange->apiPrivate().getAccountBalance(balanceOptions); }); diff --git a/src/engine/src/queryresultprinter.cpp b/src/engine/src/queryresultprinter.cpp index 0d0679a7..0dd1c01e 100644 --- a/src/engine/src/queryresultprinter.cpp +++ b/src/engine/src/queryresultprinter.cpp @@ -154,10 +154,10 @@ json TickerInformationJson(const ExchangeTickerMaps &exchangeTickerMaps) { void AppendOrderbookLine(const MarketOrderBook &marketOrderBook, int pos, std::optional optConversionRate, json &data) { - auto [p, a] = marketOrderBook[pos]; + auto [amount, price] = marketOrderBook[pos]; json &line = data.emplace_back(); - line.emplace("a", a.amountStr()); - line.emplace("p", p.amountStr()); + line.emplace("a", amount.amountStr()); + line.emplace("p", price.amountStr()); if (optConversionRate) { line.emplace("eq", optConversionRate->amountStr()); } diff --git a/src/objects/include/amount-price.hpp b/src/objects/include/amount-price.hpp new file mode 100644 index 00000000..ded5d9cc --- /dev/null +++ b/src/objects/include/amount-price.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "monetaryamount.hpp" + +namespace cct { + +struct AmountPrice { + MonetaryAmount amount; + MonetaryAmount price; +}; + +} // namespace cct \ No newline at end of file diff --git a/src/objects/include/marketorderbook.hpp b/src/objects/include/marketorderbook.hpp index 8b04515c..b11a17da 100644 --- a/src/objects/include/marketorderbook.hpp +++ b/src/objects/include/marketorderbook.hpp @@ -2,13 +2,13 @@ #include #include -#include #include -#include +#include "amount-price.hpp" #include "cct_smallvector.hpp" #include "market.hpp" #include "monetaryamount.hpp" +#include "order-book-line.hpp" #include "simpletable.hpp" #include "timedef.hpp" #include "tradeside.hpp" @@ -18,43 +18,21 @@ namespace cct { class PriceOptions; -/// Represents an entry in an order book, an amount at a given price. -class OrderBookLine { - public: - enum class Type : int8_t { kAsk, kBid }; - - /// Constructs a new OrderBookLine. - OrderBookLine(MonetaryAmount amount, MonetaryAmount price, Type type) - : _amount(type == Type::kAsk ? -amount : amount), _price(price) {} - - private: - friend class MarketOrderBook; - - MonetaryAmount _amount; - MonetaryAmount _price; -}; - /// Represents a full order book associated to a Market. /// Important note: all convert methods do not take fees into account, they should be handled accordingly. class MarketOrderBook { public: static constexpr int kDefaultDepth = 10; - using OrderBookLineSpan = std::span; - - struct AmountAtPrice { - MonetaryAmount amount; - MonetaryAmount price; - }; - - using AmountPerPriceVec = SmallVector; + using AmountPerPriceVec = SmallVector; /// Constructs an empty MarketOrderBook MarketOrderBook() noexcept = default; /// Constructs a new MarketOrderBook given a market and a list of amounts and prices. /// @param volAndPriNbDecimals optional to force number of decimals of amounts - explicit MarketOrderBook(TimePoint timeStamp, Market market, OrderBookLineSpan orderLines = OrderBookLineSpan(), + explicit MarketOrderBook(TimePoint timeStamp, Market market, + const MarketOrderBookLines& orderLines = MarketOrderBookLines(), VolAndPriNbDecimals volAndPriNbDecimals = VolAndPriNbDecimals()); /// Constructs a MarketOrderBook based on simple ticker information and price / amount precision @@ -114,11 +92,11 @@ class MarketOrderBook { /// Given an amount in base currency and the trade side with its price, compute the average matched amount /// and price /// @return a pair of {total matched amount in base currency, average matched price} - AmountAtPrice avgPriceAndMatchedVolume(TradeSide tradeSide, MonetaryAmount amount, MonetaryAmount price) const; + AmountPrice avgPriceAndMatchedVolume(TradeSide tradeSide, MonetaryAmount amount, MonetaryAmount price) const; /// Given an amount in either base or quote currency, attempt to convert it at market price immediately. /// @return a pair of {total matched amount in given currency, average matched price} - AmountAtPrice avgPriceAndMatchedAmountTaker(MonetaryAmount amountInBaseOrQuote) const; + AmountPrice avgPriceAndMatchedAmountTaker(MonetaryAmount amountInBaseOrQuote) const; /// Compute the matched amounts that would occur immediately if an order of given amount were placed at given price AmountPerPriceVec computeMatchedParts(TradeSide tradeSide, MonetaryAmount amount, MonetaryAmount price) const; @@ -140,11 +118,11 @@ class MarketOrderBook { int nbAskPrices() const { return static_cast(_orders.size()) - _lowestAskPricePos; } int nbBidPrices() const { return _lowestAskPricePos; } - /// Get a pair of {Price, Amount} of values positioned at given relative price from limit price. - /// At position 0, the pair will contain average limit prices and average amounts from both highest bid and lowest ask + /// Get an AmountPrice of values positioned at given relative price from limit price. + /// At position 0, it will contain average limit prices and average amounts from both highest bid and lowest ask /// prices. /// No bounds check is made. - std::pair operator[](int relativePosToLimitPrice) const; + AmountPrice operator[](int relativePosToLimitPrice) const; MonetaryAmount getHighestTheoreticalPrice() const; MonetaryAmount getLowestTheoreticalPrice() const; @@ -164,17 +142,17 @@ class MarketOrderBook { private: using AmountType = MonetaryAmount::AmountType; - struct AmountPrice { + struct AmountPriceInt { using AmountType = MonetaryAmount::AmountType; - bool operator==(const AmountPrice&) const noexcept = default; + bool operator==(const AmountPriceInt&) const noexcept = default; AmountType amount = 0; AmountType price = 0; }; - // Use a SmallVector to avoid memory allocation for all order book requests (ticker) - using AmountPriceVector = SmallVector; + // Use a SmallVector with one inline slot per side to avoid memory allocation for all order book requests (ticker) + using AmountPriceVector = SmallVector; public: using trivially_relocatable = is_trivially_relocatable::type; @@ -218,9 +196,9 @@ class MarketOrderBook { return MonetaryAmount(_orders[pos].price, _market.quote(), _volAndPriNbDecimals.priNbDecimals); } - AmountAtPrice avgPriceAndMatchedVolumeSell(MonetaryAmount baseAmount, MonetaryAmount price) const; + AmountPrice avgPriceAndMatchedVolumeSell(MonetaryAmount baseAmount, MonetaryAmount price) const; - AmountAtPrice avgPriceAndMatchedVolumeBuy(MonetaryAmount amountInBaseOrQuote, MonetaryAmount price) const; + AmountPrice 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. @@ -235,8 +213,8 @@ class MarketOrderBook { TimePoint _time{}; Market _market; AmountPriceVector _orders; - int _highestBidPricePos = 0; - int _lowestAskPricePos = 0; + int32_t _highestBidPricePos = 0; + int32_t _lowestAskPricePos = 0; bool _isArtificiallyExtended = false; VolAndPriNbDecimals _volAndPriNbDecimals; }; diff --git a/src/objects/include/order-book-line.hpp b/src/objects/include/order-book-line.hpp new file mode 100644 index 00000000..f5e4c1e0 --- /dev/null +++ b/src/objects/include/order-book-line.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include "amount-price.hpp" +#include "cct_type_traits.hpp" +#include "cct_vector.hpp" +#include "monetaryamount.hpp" + +namespace cct { + +/// Represents an entry in an order book, an amount at a given price. +class OrderBookLine { + public: + enum class Type : int8_t { kAsk, kBid }; + + /// Constructs a new OrderBookLine. + OrderBookLine(MonetaryAmount amount, MonetaryAmount price, Type type) + : _amountPrice(type == Type::kAsk ? -amount : amount, price) {} + + MonetaryAmount amount() const { return _amountPrice.amount; } + + MonetaryAmount price() const { return _amountPrice.price; } + + private: + friend class MarketOrderBook; + + AmountPrice _amountPrice; +}; + +class MarketOrderBookLines { + public: + MarketOrderBookLines() noexcept = default; + + auto begin() const noexcept { return _orderBookLines.begin(); } + auto end() const noexcept { return _orderBookLines.end(); } + + auto size() const noexcept { return _orderBookLines.size(); } + + void reserve(std::size_t capacity) { _orderBookLines.reserve(capacity); } + + void push(MonetaryAmount amount, MonetaryAmount price, OrderBookLine::Type type) { + if (amount != 0) { + _orderBookLines.emplace_back(amount, price, type); + } + } + + void pushAsk(MonetaryAmount amount, MonetaryAmount price) { push(amount, price, OrderBookLine::Type::kAsk); } + + void pushBid(MonetaryAmount amount, MonetaryAmount price) { push(amount, price, OrderBookLine::Type::kBid); } + + using trivially_relocatable = is_trivially_relocatable>::type; + + private: + vector _orderBookLines; +}; + +} // namespace cct \ No newline at end of file diff --git a/src/objects/src/marketorderbook.cpp b/src/objects/src/marketorderbook.cpp index 3df09945..432fb554 100644 --- a/src/objects/src/marketorderbook.cpp +++ b/src/objects/src/marketorderbook.cpp @@ -25,55 +25,54 @@ namespace cct { -MarketOrderBook::MarketOrderBook(TimePoint timeStamp, Market market, OrderBookLineSpan orderLines, +MarketOrderBook::MarketOrderBook(TimePoint timeStamp, Market market, const MarketOrderBookLines& orderLines, VolAndPriNbDecimals volAndPriNbDecimals) : _time(timeStamp), _market(market), _volAndPriNbDecimals(volAndPriNbDecimals) { - const int nbPrices = static_cast(orderLines.size()); - _orders.reserve(nbPrices); + const auto nbPrices = orderLines.size(); + if (nbPrices == 0) { + return; + } if (_volAndPriNbDecimals == VolAndPriNbDecimals()) { for (const OrderBookLine& orderBookLine : orderLines) { _volAndPriNbDecimals.volNbDecimals = - std::min(_volAndPriNbDecimals.volNbDecimals, orderBookLine._amount.currentMaxNbDecimals()); + std::min(_volAndPriNbDecimals.volNbDecimals, orderBookLine.amount().currentMaxNbDecimals()); _volAndPriNbDecimals.priNbDecimals = - std::min(_volAndPriNbDecimals.priNbDecimals, orderBookLine._price.currentMaxNbDecimals()); + std::min(_volAndPriNbDecimals.priNbDecimals, orderBookLine.price().currentMaxNbDecimals()); } } - if (nbPrices == 0) { - _lowestAskPricePos = 0; - _highestBidPricePos = 0; - } else { - for (const OrderBookLine& orderBookLine : orderLines) { - assert(orderBookLine._amount.currencyCode() == market.base() && - orderBookLine._price.currencyCode() == market.quote()); - // amounts cannot be nullopt here - if (orderBookLine._amount == 0) { - // Just ignore empty lines - continue; - } - const auto amountIntegral = orderBookLine._amount.amount(_volAndPriNbDecimals.volNbDecimals); - const auto priceIntegral = orderBookLine._price.amount(_volAndPriNbDecimals.priNbDecimals); - - assert(amountIntegral.has_value()); - assert(priceIntegral.has_value()); + _orders.reserve(nbPrices); - _orders.emplace_back(*amountIntegral, *priceIntegral); + for (const OrderBookLine& orderBookLine : orderLines) { + if (orderBookLine.amount().currencyCode() != market.base() || + orderBookLine.price().currencyCode() != market.quote()) { + throw exception("Invalid market order book currencies"); } - std::ranges::sort(_orders, [](AmountPrice lhs, AmountPrice rhs) { return lhs.price < rhs.price; }); - auto adjacentFindIt = - std::ranges::adjacent_find(_orders, [](AmountPrice lhs, AmountPrice rhs) { return lhs.price == rhs.price; }); - if (adjacentFindIt != _orders.end()) { - throw exception("Forbidden duplicate price {} in the order book for market {}", adjacentFindIt->price, market); - } + const auto amountIntegral = orderBookLine.amount().amount(_volAndPriNbDecimals.volNbDecimals); + const auto priceIntegral = orderBookLine.price().amount(_volAndPriNbDecimals.priNbDecimals); + + // Usage of std::optional::value() instead of operator* here to throw an exception if the value is not present. + // It's not expected at this point to not have a value for asked number of decimals + _orders.emplace_back(amountIntegral.value(), priceIntegral.value()); + } - auto highestBidPriceIt = - std::ranges::partition_point(_orders, [](AmountPrice amountPrice) { return amountPrice.amount > 0; }); - _highestBidPricePos = static_cast(highestBidPriceIt - _orders.begin() - 1); - _lowestAskPricePos = static_cast( - std::find_if(highestBidPriceIt, _orders.end(), [](AmountPrice amountPrice) { return amountPrice.amount < 0; }) - - _orders.begin()); + std::ranges::sort(_orders, [](auto lhs, auto rhs) { return lhs.price < rhs.price; }); + const auto adjacentFindIt = + std::ranges::adjacent_find(_orders, [](auto lhs, auto rhs) { return lhs.price == rhs.price; }); + if (adjacentFindIt != _orders.end()) { + throw exception("Forbidden duplicate price {} in the order book for market {}", adjacentFindIt->price, market); } + + const auto highestBidPriceIt = + std::ranges::partition_point(_orders, [](auto amountPrice) { return amountPrice.amount > 0; }); + + using PricePosT = decltype(_highestBidPricePos); + + _highestBidPricePos = static_cast(highestBidPriceIt - _orders.begin() - 1); + _lowestAskPricePos = static_cast( + std::find_if(highestBidPriceIt, _orders.end(), [](auto amountPrice) { return amountPrice.amount < 0; }) - + _orders.begin()); } MarketOrderBook::MarketOrderBook(TimePoint timeStamp, MonetaryAmount askPrice, MonetaryAmount askVolume, @@ -123,7 +122,7 @@ MarketOrderBook::MarketOrderBook(TimePoint timeStamp, MonetaryAmount askPrice, M "price", askPrice, bidPrice); } - const AmountPrice::AmountType stepPrice = *optStepPrice; + const auto stepPrice = *optStepPrice; std::optional optBidVol = bidVolume.amount(_volAndPriNbDecimals.volNbDecimals); std::optional optAskVol = askVolume.amount(_volAndPriNbDecimals.volNbDecimals); @@ -147,17 +146,18 @@ MarketOrderBook::MarketOrderBook(TimePoint timeStamp, MonetaryAmount askPrice, M _volAndPriNbDecimals.priNbDecimals); } - const AmountPrice refBidAmountPrice(*optBidVol, *optBidPri); - const AmountPrice refAskAmountPrice(-(*optAskVol), *optAskPri); - const AmountPrice::AmountType simulatedStepVol = std::midpoint(refBidAmountPrice.amount, -refAskAmountPrice.amount); + const AmountPriceInt refBidAmountPrice(*optBidVol, *optBidPri); + const AmountPriceInt refAskAmountPrice(-(*optAskVol), *optAskPri); + const auto simulatedStepVol = std::midpoint(refBidAmountPrice.amount, -refAskAmountPrice.amount); - constexpr AmountPrice::AmountType kMaxVol = std::numeric_limits::max() / 2; + constexpr auto kMaxVol = std::numeric_limits::max() / 2; _orders.resize(depth * 2); // Add bid lines first for (int currentDepth = 0; currentDepth < depth; ++currentDepth) { - AmountPrice amountPrice = currentDepth == 0 ? refBidAmountPrice : _orders[depth - currentDepth]; + auto amountPrice = currentDepth == 0 ? refBidAmountPrice : _orders[depth - currentDepth]; + amountPrice.price -= stepPrice * currentDepth; if (currentDepth != 0 && amountPrice.amount < kMaxVol) { amountPrice.amount += simulatedStepVol / 2; @@ -169,7 +169,8 @@ MarketOrderBook::MarketOrderBook(TimePoint timeStamp, MonetaryAmount askPrice, M // Finally add ask lines for (int currentDepth = 0; currentDepth < depth; ++currentDepth) { - AmountPrice amountPrice = currentDepth == 0 ? refAskAmountPrice : _orders[depth + currentDepth - 1]; + auto amountPrice = currentDepth == 0 ? refAskAmountPrice : _orders[depth + currentDepth - 1]; + amountPrice.price += stepPrice * currentDepth; if (currentDepth != 0 && -amountPrice.amount < kMaxVol) { amountPrice.amount -= simulatedStepVol / 2; @@ -264,8 +265,7 @@ MarketOrderBook::AmountPerPriceVec MarketOrderBook::computePricesAtWhichAmountWo return ret; } -MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeSell(MonetaryAmount baseAmount, - MonetaryAmount price) const { +AmountPrice MarketOrderBook::avgPriceAndMatchedVolumeSell(MonetaryAmount baseAmount, MonetaryAmount price) const { MonetaryAmount avgPrice(0, _market.quote()); MonetaryAmount remainingBaseAmount = baseAmount; @@ -290,8 +290,8 @@ MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeSell(Mon return {matchedAmount, avgPrice}; } -MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolumeBuy(MonetaryAmount amountInBaseOrQuote, - MonetaryAmount price) const { +AmountPrice MarketOrderBook::avgPriceAndMatchedVolumeBuy(MonetaryAmount amountInBaseOrQuote, + MonetaryAmount price) const { MonetaryAmount remainingAmountInBaseOrQuote = amountInBaseOrQuote; MonetaryAmount matchedAmount(0, _market.base()); MonetaryAmount avgPrice(0, _market.quote()); @@ -430,8 +430,8 @@ MarketOrderBook::AmountPerPriceVec MarketOrderBook::computeMatchedParts(TradeSid return ret; } -MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolume(TradeSide tradeSide, MonetaryAmount amount, - MonetaryAmount price) const { +AmountPrice MarketOrderBook::avgPriceAndMatchedVolume(TradeSide tradeSide, MonetaryAmount amount, + MonetaryAmount price) const { switch (tradeSide) { case TradeSide::kBuy: return avgPriceAndMatchedVolumeBuy(amount, price); @@ -442,8 +442,7 @@ MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedVolume(TradeSi } } -MarketOrderBook::AmountAtPrice MarketOrderBook::avgPriceAndMatchedAmountTaker( - MonetaryAmount amountInBaseOrQuote) const { +AmountPrice MarketOrderBook::avgPriceAndMatchedAmountTaker(MonetaryAmount amountInBaseOrQuote) const { if (amountInBaseOrQuote.currencyCode() == _market.base()) { return avgPriceAndMatchedVolumeSell(amountInBaseOrQuote, MonetaryAmount(0, _market.quote())); } @@ -487,19 +486,19 @@ std::optional MarketOrderBook::convert(MonetaryAmount amountInBa } // NOLINTNEXTLINE(misc-no-recursion) -std::pair MarketOrderBook::operator[](int relativePosToLimitPrice) const { +AmountPrice MarketOrderBook::operator[](int relativePosToLimitPrice) const { if (relativePosToLimitPrice == 0) { const auto [v11, v12] = (*this)[-1]; const auto [v21, v22] = (*this)[1]; - return std::make_pair(v11 + (v21 - v11) / 2, v12 + (v22 - v12) / 2); + return {v11 + (v21 - v11) / 2, v12 + (v22 - v12) / 2}; } if (relativePosToLimitPrice < 0) { const int pos = _lowestAskPricePos + relativePosToLimitPrice; - return std::make_pair(priceAt(pos), amountAt(pos)); + return {amountAt(pos), priceAt(pos)}; } // > 0 const int pos = _highestBidPricePos + relativePosToLimitPrice; - return std::make_pair(priceAt(pos), negAmountAt(pos)); + return {negAmountAt(pos), priceAt(pos)}; } std::optional MarketOrderBook::convertBaseAmountToQuote(MonetaryAmount amountInBaseCurrency) const { @@ -581,7 +580,7 @@ std::optional ComputeRelativePrice(bool isBuy, const MarketOrder if (relativePrice == 0) { return std::nullopt; } - return marketOrderBook[relativePrice].first; + return marketOrderBook[relativePrice].price; } } // namespace diff --git a/src/objects/test/marketorderbook_test.cpp b/src/objects/test/marketorderbook_test.cpp index d93474bd..12a38c00 100644 --- a/src/objects/test/marketorderbook_test.cpp +++ b/src/objects/test/marketorderbook_test.cpp @@ -2,23 +2,38 @@ #include -#include +#include #include #include #include "cct_exception.hpp" #include "market.hpp" #include "monetaryamount.hpp" +#include "order-book-line.hpp" #include "timedef.hpp" #include "tradeside.hpp" namespace cct { namespace { -using AmountAtPrice = MarketOrderBook::AmountAtPrice; using AmountAtPriceVec = MarketOrderBook::AmountPerPriceVec; } // namespace -inline bool operator==(const AmountAtPrice &lhs, const AmountAtPrice &rhs) { +MarketOrderBookLines CreateMarketOrderBookLines(std::initializer_list init) { + MarketOrderBookLines marketOrderBookLines; + marketOrderBookLines.reserve(init.size()); + + for (const auto &orderBookLine : init) { + if (orderBookLine.amount() < 0) { + marketOrderBookLines.pushAsk(-orderBookLine.amount(), orderBookLine.price()); + } else { + marketOrderBookLines.pushBid(orderBookLine.amount(), orderBookLine.price()); + } + } + + return marketOrderBookLines; +} + +constexpr bool operator==(const AmountPrice &lhs, const AmountPrice &rhs) { return lhs.amount == rhs.amount && lhs.price == rhs.price; } @@ -28,14 +43,14 @@ class MarketOrderBookTestCase1 : public ::testing::Test { protected: MarketOrderBook marketOrderBook{ Clock::now(), Market("ETH", "EUR"), - std::array{ - OrderBookLine(MonetaryAmount("0.65", "ETH"), MonetaryAmount("1300.50", "EUR"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("0.24", "ETH"), MonetaryAmount("1301", "EUR"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount(0, "ETH"), MonetaryAmount("1301.50", "EUR"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("1.4009", "ETH"), MonetaryAmount("1302", "EUR"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("3.78", "ETH"), MonetaryAmount("1302.50", "EUR"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("56.10001267", "ETH"), MonetaryAmount("1303", "EUR"), - OrderBookLine::Type::kAsk)}}; + CreateMarketOrderBookLines( + {OrderBookLine(MonetaryAmount("0.65", "ETH"), MonetaryAmount("1300.50", "EUR"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("0.24", "ETH"), MonetaryAmount("1301", "EUR"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount(0, "ETH"), MonetaryAmount("1301.50", "EUR"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("1.4009", "ETH"), MonetaryAmount("1302", "EUR"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("3.78", "ETH"), MonetaryAmount("1302.50", "EUR"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("56.10001267", "ETH"), MonetaryAmount("1303", "EUR"), + OrderBookLine::Type::kAsk)})}; }; TEST_F(MarketOrderBookTestCase1, NumberOfElements) { @@ -50,12 +65,12 @@ TEST_F(MarketOrderBookTestCase1, MiddleElements) { } TEST_F(MarketOrderBookTestCase1, OperatorBrackets) { - EXPECT_EQ(marketOrderBook[-2], std::make_pair(MonetaryAmount("1300.5EUR"), MonetaryAmount("0.65ETH"))); - EXPECT_EQ(marketOrderBook[-1], std::make_pair(MonetaryAmount("1301EUR"), MonetaryAmount("0.24ETH"))); - EXPECT_EQ(marketOrderBook[0], std::make_pair(MonetaryAmount("1301.5EUR"), MonetaryAmount("0.82045ETH"))); - EXPECT_EQ(marketOrderBook[1], std::make_pair(MonetaryAmount("1302EUR"), MonetaryAmount("1.4009ETH"))); - EXPECT_EQ(marketOrderBook[2], std::make_pair(MonetaryAmount("1302.5EUR"), MonetaryAmount("3.78ETH"))); - EXPECT_EQ(marketOrderBook[3], std::make_pair(MonetaryAmount("1303EUR"), MonetaryAmount("56.10001267ETH"))); + EXPECT_EQ(marketOrderBook[-2], AmountPrice(MonetaryAmount("0.65ETH"), MonetaryAmount("1300.5EUR"))); + EXPECT_EQ(marketOrderBook[-1], AmountPrice(MonetaryAmount("0.24ETH"), MonetaryAmount("1301EUR"))); + EXPECT_EQ(marketOrderBook[0], AmountPrice(MonetaryAmount("0.82045ETH"), MonetaryAmount("1301.5EUR"))); + EXPECT_EQ(marketOrderBook[1], AmountPrice(MonetaryAmount("1.4009ETH"), MonetaryAmount("1302EUR"))); + EXPECT_EQ(marketOrderBook[2], AmountPrice(MonetaryAmount("3.78ETH"), MonetaryAmount("1302.5EUR"))); + EXPECT_EQ(marketOrderBook[3], AmountPrice(MonetaryAmount("56.10001267ETH"), MonetaryAmount("1303EUR"))); } TEST_F(MarketOrderBookTestCase1, ComputeCumulAmountBoughtImmediately) { @@ -101,28 +116,28 @@ TEST_F(MarketOrderBookTestCase1, ComputeMaxPriceAtWhichAmountWouldBeBoughtImmedi TEST_F(MarketOrderBookTestCase1, ComputeAvgPriceForTakerBuy) { EXPECT_EQ(marketOrderBook.avgPriceAndMatchedAmountTaker(MonetaryAmount(1000, "EUR")), - AmountAtPrice(MonetaryAmount("999.99999999998784", "EUR"), MonetaryAmount("1302.00000000000001", "EUR"))); + AmountPrice(MonetaryAmount("999.99999999998784", "EUR"), MonetaryAmount("1302.00000000000001", "EUR"))); EXPECT_EQ(marketOrderBook.avgPriceAndMatchedAmountTaker(MonetaryAmount(5000, "EUR")), - AmountAtPrice(MonetaryAmount("4999.9999119826894", "EUR"), MonetaryAmount("1302.31755833325309", "EUR"))); + AmountPrice(MonetaryAmount("4999.9999119826894", "EUR"), MonetaryAmount("1302.31755833325309", "EUR"))); EXPECT_EQ(marketOrderBook.avgPriceAndMatchedAmountTaker(MonetaryAmount(100000, "EUR")), - AmountAtPrice(MonetaryAmount("79845.737428463776", "EUR"), MonetaryAmount("1302.94629812356546", "EUR"))); + AmountPrice(MonetaryAmount("79845.737428463776", "EUR"), MonetaryAmount("1302.94629812356546", "EUR"))); } TEST_F(MarketOrderBookTestCase1, ComputeAvgPriceForTakerSell) { EXPECT_EQ(marketOrderBook.avgPriceAndMatchedAmountTaker(MonetaryAmount(24, "ETH", 2)), - AmountAtPrice(MonetaryAmount(24, "ETH", 2), MonetaryAmount(1301, "EUR"))); + AmountPrice(MonetaryAmount(24, "ETH", 2), MonetaryAmount(1301, "EUR"))); EXPECT_EQ(marketOrderBook.avgPriceAndMatchedAmountTaker(MonetaryAmount(5, "ETH", 1)), - AmountAtPrice(MonetaryAmount(5, "ETH", 1), MonetaryAmount(130074, "EUR", 2))); + AmountPrice(MonetaryAmount(5, "ETH", 1), MonetaryAmount(130074, "EUR", 2))); EXPECT_EQ(marketOrderBook.avgPriceAndMatchedAmountTaker(MonetaryAmount(4, "ETH")), - AmountAtPrice(MonetaryAmount(89, "ETH", 2), MonetaryAmount("1300.63483146067415", "EUR"))); + AmountPrice(MonetaryAmount(89, "ETH", 2), MonetaryAmount("1300.63483146067415", "EUR"))); } TEST_F(MarketOrderBookTestCase1, MoreComplexListOfPricesComputations) { EXPECT_EQ(marketOrderBook.computePricesAtWhichAmountWouldBeBoughtImmediately(MonetaryAmount(4, "ETH")), - AmountAtPriceVec({AmountAtPrice(MonetaryAmount("1.4009", "ETH"), MonetaryAmount("1302", "EUR")), - AmountAtPrice(MonetaryAmount("2.5991", "ETH"), MonetaryAmount("1302.50", "EUR"))})); + AmountAtPriceVec({AmountPrice(MonetaryAmount("1.4009", "ETH"), MonetaryAmount("1302", "EUR")), + AmountPrice(MonetaryAmount("2.5991", "ETH"), MonetaryAmount("1302.50", "EUR"))})); EXPECT_EQ(marketOrderBook.computePricesAtWhichAmountWouldBeSoldImmediately(MonetaryAmount("0.24", "ETH")), - AmountAtPriceVec({AmountAtPrice(MonetaryAmount("0.24", "ETH"), MonetaryAmount("1301", "EUR"))})); + AmountAtPriceVec({AmountPrice(MonetaryAmount("0.24", "ETH"), MonetaryAmount("1301", "EUR"))})); } TEST_F(MarketOrderBookTestCase1, ConvertBaseAmountToQuote) { @@ -143,18 +158,19 @@ class MarketOrderBookTestCase2 : public ::testing::Test { TimePoint time{}; MarketOrderBook marketOrderBook{ time, Market("APM", "KRW"), - std::array{ - OrderBookLine(MonetaryAmount("1991.3922", "APM"), MonetaryAmount("57.8", "KRW"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("90184.3951", "APM"), MonetaryAmount("57.81", "KRW"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("91.1713", "APM"), MonetaryAmount("57.84", "KRW"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("41.0131", "APM"), MonetaryAmount("57.9", "KRW"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("33.5081914157147802", "APM"), MonetaryAmount("57.78", "KRW"), - OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("3890.879", "APM"), MonetaryAmount("57.19", "KRW"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("14", "APM"), MonetaryAmount("57.18", "KRW"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("14", "APM"), MonetaryAmount("57.17", "KRW"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("3848.8453", "APM"), MonetaryAmount("57.16", "KRW"), - OrderBookLine::Type::kBid)}}; + CreateMarketOrderBookLines( + {OrderBookLine(MonetaryAmount("1991.3922", "APM"), MonetaryAmount("57.8", "KRW"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("90184.3951", "APM"), MonetaryAmount("57.81", "KRW"), + OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("91.1713", "APM"), MonetaryAmount("57.84", "KRW"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("41.0131", "APM"), MonetaryAmount("57.9", "KRW"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("33.5081914157147802", "APM"), MonetaryAmount("57.78", "KRW"), + OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("3890.879", "APM"), MonetaryAmount("57.19", "KRW"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("14", "APM"), MonetaryAmount("57.18", "KRW"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("14", "APM"), MonetaryAmount("57.17", "KRW"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("3848.8453", "APM"), MonetaryAmount("57.16", "KRW"), + OrderBookLine::Type::kBid)})}; }; TEST_F(MarketOrderBookTestCase2, SimpleQueries) { @@ -173,9 +189,9 @@ TEST_F(MarketOrderBookTestCase2, ComputeMatchedPartsBuy) { EXPECT_EQ( marketOrderBook.computeMatchedParts(TradeSide::kBuy, MonetaryAmount(91000, "APM"), MonetaryAmount("57.81", "KRW")), - AmountAtPriceVec({AmountAtPrice(MonetaryAmount("33.5081914157147", "APM"), MonetaryAmount("57.78", "KRW")), - AmountAtPrice(MonetaryAmount("1991.3922", "APM"), MonetaryAmount("57.8", "KRW")), - AmountAtPrice(MonetaryAmount("88975.0996085842853", "APM"), MonetaryAmount("57.81", "KRW"))})); + AmountAtPriceVec({AmountPrice(MonetaryAmount("33.5081914157147", "APM"), MonetaryAmount("57.78", "KRW")), + AmountPrice(MonetaryAmount("1991.3922", "APM"), MonetaryAmount("57.8", "KRW")), + AmountPrice(MonetaryAmount("88975.0996085842853", "APM"), MonetaryAmount("57.81", "KRW"))})); EXPECT_EQ(marketOrderBook.computeMatchedParts(TradeSide::kBuy, MonetaryAmount(91000, "APM"), MonetaryAmount("57.77", "KRW")), AmountAtPriceVec()); @@ -185,7 +201,7 @@ TEST_F(MarketOrderBookTestCase2, ComputeMatchedPartsSell) { EXPECT_EQ(marketOrderBook.computeMatchedParts(TradeSide::kSell, MonetaryAmount(5000, "APM"), MonetaryAmount("57.19", "KRW")), AmountAtPriceVec({ - AmountAtPrice(MonetaryAmount("3890.879", "APM"), MonetaryAmount("57.19", "KRW")), + AmountPrice(MonetaryAmount("3890.879", "APM"), MonetaryAmount("57.19", "KRW")), })); EXPECT_EQ(marketOrderBook.computeMatchedParts(TradeSide::kSell, MonetaryAmount(91000, "APM"), MonetaryAmount("57.23", "KRW")), @@ -197,18 +213,18 @@ class MarketOrderBookTestCase3 : public ::testing::Test { TimePoint time{}; MarketOrderBook marketOrderBook{ time, Market("XLM", "BTC"), - std::array{OrderBookLine(MonetaryAmount("126881.164", "XLM"), - MonetaryAmount("0.000007130", "BTC"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("95716.519", "XLM"), - MonetaryAmount("0.000007120", "BTC"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("23726.285", "XLM"), - MonetaryAmount("0.000007110", "BTC"), OrderBookLine::Type::kAsk), - OrderBookLine(MonetaryAmount("37863.710", "XLM"), - MonetaryAmount("0.000007100", "BTC"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("169165.594", "XLM"), - MonetaryAmount("0.000007090", "BTC"), OrderBookLine::Type::kBid), - OrderBookLine(MonetaryAmount("204218.966", "XLM"), - MonetaryAmount("0.000007080", "BTC"), OrderBookLine::Type::kBid)}}; + CreateMarketOrderBookLines({OrderBookLine(MonetaryAmount("126881.164", "XLM"), + MonetaryAmount("0.000007130", "BTC"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("95716.519", "XLM"), + MonetaryAmount("0.000007120", "BTC"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("23726.285", "XLM"), + MonetaryAmount("0.000007110", "BTC"), OrderBookLine::Type::kAsk), + OrderBookLine(MonetaryAmount("37863.710", "XLM"), + MonetaryAmount("0.000007100", "BTC"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("169165.594", "XLM"), + MonetaryAmount("0.000007090", "BTC"), OrderBookLine::Type::kBid), + OrderBookLine(MonetaryAmount("204218.966", "XLM"), + MonetaryAmount("0.000007080", "BTC"), OrderBookLine::Type::kBid)})}; }; TEST_F(MarketOrderBookTestCase3, Convert) { @@ -222,17 +238,17 @@ TEST_F(MarketOrderBookTestCase3, Convert) { 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"))); + AmountPrice(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"))); + AmountPrice(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"))); + AmountPrice(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"))); + AmountPrice(MonetaryAmount(0, "XLM"), MonetaryAmount(0, "BTC"))); } class MarketOrderBookTestCaseExtended1 : public ::testing::Test {