diff --git a/CMakeLists.txt b/CMakeLists.txt index 876eba3d..514d41cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,8 +117,8 @@ if(NOT glaze) FetchContent_Declare( glaze - URL https://github.com/stephenberry/glaze/archive/refs/tags/v4.0.2.tar.gz - URL_HASH SHA256=65f01a479c786a21141dc1a77cbb9d4af63beed70fbd6bf42bba4c7c3c62943c + URL https://github.com/stephenberry/glaze/archive/refs/tags/v4.2.0.tar.gz + URL_HASH SHA256=96037265d138a0d778d2b8672c976bc2739b3f753aefabc3fcf55ada3ead1869 ) list(APPEND fetchContentPackagesToMakeAvailable glaze) diff --git a/src/api/common/src/binance-common-api.cpp b/src/api/common/src/binance-common-api.cpp index c7968380..aae35756 100644 --- a/src/api/common/src/binance-common-api.cpp +++ b/src/api/common/src/binance-common-api.cpp @@ -38,17 +38,15 @@ BinanceGlobalInfos::BinanceGlobalInfosFunc::BinanceGlobalInfosFunc(AbstractMetri schema::binance::NetworkCoinDataVector BinanceGlobalInfos::BinanceGlobalInfosFunc::operator()() { RequestRetry requestRetry(_curlHandle, CurlOptions(HttpRequestType::kGet)); - schema::binance::NetworkCoinAll ret = - requestRetry.query( - "/bapi/capital/v1/public/capital/getNetworkCoinAll", [](const auto& response) { - static constexpr std::string_view kExpectedCode = "000000"; - if (response.code != kExpectedCode) { - log::warn("Binance error ({})", response.code); - return RequestRetry::Status::kResponseError; - } - return RequestRetry::Status::kResponseOK; - }); + schema::binance::NetworkCoinAll ret = requestRetry.query( + "/bapi/capital/v1/public/capital/getNetworkCoinAll", [](const auto& response) { + static constexpr std::string_view kExpectedCode = "000000"; + if (response.code != kExpectedCode) { + log::warn("Binance error ({})", response.code); + return RequestRetry::Status::kResponseError; + } + return RequestRetry::Status::kResponseOK; + }); const auto [endIt, oldEndIt] = std::ranges::remove_if(ret.data, [](const auto& el) { return el.coin.size() > CurrencyCode::kMaxLen; }); diff --git a/src/api/exchanges/include/huobi-schema.hpp b/src/api/exchanges/include/huobi-schema.hpp index cf144bac..9bb53b1f 100644 --- a/src/api/exchanges/include/huobi-schema.hpp +++ b/src/api/exchanges/include/huobi-schema.hpp @@ -228,7 +228,7 @@ struct V1AccountAccountsBalance { // https://huobiapi.github.io/docs/spot/v1/en/#query-deposit-address struct V2AccountDepositAddress { - string status; + int code; struct Item { string address; @@ -355,7 +355,7 @@ struct V1OrderOrdersDetail { // https://huobiapi.github.io/docs/spot/v1/en/#query-withdraw-address struct V1QueryWithdrawAddress { - string status; + int code; struct Item { string address; diff --git a/src/api/exchanges/include/kraken-schema.hpp b/src/api/exchanges/include/kraken-schema.hpp new file mode 100644 index 00000000..05281a8a --- /dev/null +++ b/src/api/exchanges/include/kraken-schema.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +#include "cct_string.hpp" +#include "cct_type_traits.hpp" +#include "cct_vector.hpp" +#include "monetaryamount.hpp" + +namespace cct::schema::kraken { + +template +using has_error_t = decltype(std::declval().error); + +// PUBLIC + +// https://docs.kraken.com/api/docs/rest-api/get-system-status + +struct PublicSystemStatus { + vector error; + + struct Result { + string status; + }; + + Result result; +}; + +// https://docs.kraken.com/api/docs/rest-api/get-asset-info + +struct PublicAssets { + vector error; + + struct Result { + string altname; + }; + + std::unordered_map result; +}; + +// https://docs.kraken.com/api/docs/rest-api/get-tradable-asset-pairs + +struct PublicAssetPairs { + vector error; + + struct Result { + string base; + string quote; + MonetaryAmount ordermin; + int8_t lot_decimals; + int8_t pair_decimals; + }; + + std::unordered_map result; +}; + +// https://docs.kraken.com/api/docs/rest-api/get-ticker-information + +struct PublicTicker { + vector error; + + struct Result { + using AskOrBid = std::array; + + AskOrBid a; + AskOrBid b; + std::array c; + std::array v; + }; + + std::unordered_map result; +}; + +// https://docs.kraken.com/api/docs/rest-api/get-order-book + +struct PublicDepth { + vector error; + + struct Result { + using Item = std::variant; + + using Data = std::array; + + vector asks; + vector bids; + }; + + std::unordered_map result; +}; + +// https://docs.kraken.com/api/docs/rest-api/get-recent-trades + +struct PublicTrades { + vector error; + + using Item = std::variant; + + using Data = vector>; + + std::unordered_map> result; +}; + +} // namespace cct::schema::kraken diff --git a/src/api/exchanges/src/binanceprivateapi.cpp b/src/api/exchanges/src/binanceprivateapi.cpp index 9feae42c..4fcc8910 100644 --- a/src/api/exchanges/src/binanceprivateapi.cpp +++ b/src/api/exchanges/src/binanceprivateapi.cpp @@ -205,7 +205,7 @@ T PrivateQuery(CurlHandle& curlHandle, const APIKey& apiKey, HttpRequestType req SetNonceAndSignature(apiKey, opts.mutablePostData(), queryDelay); auto resStr = curlHandle.query(endpoint, opts); - + // NOLINTNEXTLINE(readability-implicit-bool-conversion) auto ec = ReadJson( resStr, "binance private", ret); if (ec) { diff --git a/src/api/exchanges/src/binancepublicapi.cpp b/src/api/exchanges/src/binancepublicapi.cpp index 500795a2..1fd90c79 100644 --- a/src/api/exchanges/src/binancepublicapi.cpp +++ b/src/api/exchanges/src/binancepublicapi.cpp @@ -58,20 +58,18 @@ T PublicQuery(CurlHandle& curlHandle, std::string_view method, const CurlPostDat endpoint.append(curlPostData.str()); } RequestRetry requestRetry(curlHandle, CurlOptions(HttpRequestType::kGet)); + return requestRetry.query(endpoint, [](const T& response) { + if constexpr (amc::is_detected::value && + amc::is_detected::value) { + if (response.code && response.msg) { + const int statusCode = *response.code; // "1100" for instance + log::warn("Binance error ({}), msg: '{}'", statusCode, *response.msg); + return RequestRetry::Status::kResponseError; + } + } - return requestRetry.query( - endpoint, [](const T& response) { - if constexpr (amc::is_detected::value && - amc::is_detected::value) { - if (response.code && response.msg) { - const int statusCode = *response.code; // "1100" for instance - log::warn("Binance error ({}), msg: '{}'", statusCode, *response.msg); - return RequestRetry::Status::kResponseError; - } - } - - return RequestRetry::Status::kResponseOK; - }); + return RequestRetry::Status::kResponseOK; + }); } const auto& RetrieveMarketData(const auto& exchangeInfoData, Market mk) { diff --git a/src/api/exchanges/src/bithumbprivateapi.cpp b/src/api/exchanges/src/bithumbprivateapi.cpp index 09ccf271..c01d96cb 100644 --- a/src/api/exchanges/src/bithumbprivateapi.cpp +++ b/src/api/exchanges/src/bithumbprivateapi.cpp @@ -244,8 +244,7 @@ template T PrivateQueryProcessWithRetries(CurlHandle& curlHandle, const APIKey& apiKey, std::string_view endpoint, CurlOptions&& opts) { RequestRetry requestRetry(curlHandle, std::move(opts)); - - return requestRetry.query( + return requestRetry.query( endpoint, [endpoint](T& jsonResponse) { auto statusCode = StringToIntegral(jsonResponse.status); diff --git a/src/api/exchanges/src/bithumbpublicapi.cpp b/src/api/exchanges/src/bithumbpublicapi.cpp index 71a8233a..5cac7492 100644 --- a/src/api/exchanges/src/bithumbpublicapi.cpp +++ b/src/api/exchanges/src/bithumbpublicapi.cpp @@ -79,20 +79,19 @@ T PublicQuery(CurlHandle& curlHandle, std::string_view method, CurrencyCode base std::string_view urlOpts = "") { RequestRetry requestRetry(curlHandle, CurlOptions(HttpRequestType::kGet)); - return requestRetry.query( - ComputeMethodUrl(method, base, quote, urlOpts), [](const T& response) { - if constexpr (amc::is_detected::value) { - if (!response.status.empty()) { - auto statusCode = StringToIntegral(response.status); - if (statusCode != BithumbPublic::kStatusOK) { - log::warn("Bithumb error ({})", statusCode); - return RequestRetry::Status::kResponseError; - } - } + return requestRetry.query(ComputeMethodUrl(method, base, quote, urlOpts), [](const T& response) { + if constexpr (amc::is_detected::value) { + if (!response.status.empty()) { + auto statusCode = StringToIntegral(response.status); + if (statusCode != BithumbPublic::kStatusOK) { + log::warn("Bithumb error ({})", statusCode); + return RequestRetry::Status::kResponseError; } + } + } - return RequestRetry::Status::kResponseOK; - }); + return RequestRetry::Status::kResponseOK; + }); } } // namespace @@ -119,6 +118,7 @@ BithumbPublic::BithumbPublic(const CoincenterInfo& config, FiatConverter& fiatCo bool BithumbPublic::healthCheck() { auto networkInfoStr = _curlHandle.query("/public/network-info", CurlOptions(HttpRequestType::kGet)); schema::bithumb::V1NetworkInfo networkInfo; + // NOLINTNEXTLINE(readability-implicit-bool-conversion) auto ec = ReadJson( networkInfoStr, "Bithumb network info", networkInfo); if (ec) { diff --git a/src/api/exchanges/src/huobiprivateapi.cpp b/src/api/exchanges/src/huobiprivateapi.cpp index 7a96e345..daa3955c 100644 --- a/src/api/exchanges/src/huobiprivateapi.cpp +++ b/src/api/exchanges/src/huobiprivateapi.cpp @@ -125,13 +125,28 @@ T PrivateQuery(CurlHandle& curlHandle, const APIKey& apiKey, HttpRequestType req RequestRetry requestRetry(curlHandle, CurlOptions(requestType, std::move(postData), postDataFormat), QueryRetryPolicy{.initialRetryDelay = seconds{1}, .nbMaxRetries = 3}); - - return requestRetry.query( + return requestRetry.query( method, [](const T& response) { - if (response.status != "ok") { - log::warn("Huobi status error: {}", response.status); - return RequestRetry::Status::kResponseError; + if constexpr (amc::is_detected::value) { + if (response.code != 200) { + log::warn("Huobi error code: {}", response.code); + return RequestRetry::Status::kResponseError; + } + } else if constexpr (amc::is_detected::value) { + if (response.status != "ok") { + if (response.status.empty()) { + log::warn("Huobi status is empty - is it supposed to be returned by this endpoint?"); + } else { + log::warn("Huobi status error: {}", response.status); + return RequestRetry::Status::kResponseError; + } + } + } else { + // TODO: can be replaced by static_assert(false) in C++23 + static_assert(amc::is_detected::value || + amc::is_detected::value, + "T should have a code or status member"); } return RequestRetry::Status::kResponseOK; }, @@ -159,6 +174,7 @@ HuobiPrivate::HuobiPrivate(const CoincenterInfo& coincenterInfo, HuobiPublic& hu bool HuobiPrivate::validateApiKey() { const auto result = PrivateQuery(_curlHandle, _apiKey, HttpRequestType::kGet, "/v1/account/accounts", CurlPostData()); + return result.status == "ok" && !result.data.empty(); } diff --git a/src/api/exchanges/src/huobipublicapi.cpp b/src/api/exchanges/src/huobipublicapi.cpp index fc365c0f..41bbd233 100644 --- a/src/api/exchanges/src/huobipublicapi.cpp +++ b/src/api/exchanges/src/huobipublicapi.cpp @@ -59,23 +59,26 @@ T PublicQuery(CurlHandle& curlHandle, std::string_view endpoint, const CurlPostD } RequestRetry requestRetry(curlHandle, CurlOptions(HttpRequestType::kGet)); + return requestRetry.query(method, [](const T& response) { + if constexpr (amc::is_detected::value) { + if (response.code != 200) { + log::warn("Huobi error code: {}", response.code); + return RequestRetry::Status::kResponseError; + } + } else if constexpr (amc::is_detected::value) { + if (response.status != "ok") { + log::warn("Huobi status error: {}", response.status); + return RequestRetry::Status::kResponseError; + } + } else { + // TODO: can be replaced by static_assert(false) in C++23 + static_assert(amc::is_detected::value || + amc::is_detected::value, + "T should have a code or status member"); + } - return requestRetry.query( - method, [](const T& response) { - if constexpr (amc::is_detected::value) { - if (response.code != 200) { - log::warn("Huobi error code: {}", response.code); - return RequestRetry::Status::kResponseError; - } - } else if constexpr (amc::is_detected::value) { - if (response.status != "ok") { - log::warn("Huobi status error: {}", response.status); - return RequestRetry::Status::kResponseError; - } - } - - return RequestRetry::Status::kResponseOK; - }); + return RequestRetry::Status::kResponseOK; + }); } } // namespace @@ -113,6 +116,7 @@ HuobiPublic::HuobiPublic(const CoincenterInfo& config, FiatConverter& fiatConver bool HuobiPublic::healthCheck() { auto strData = _healthCheckCurlHandle.query("/api/v2/summary.json", CurlOptions(HttpRequestType::kGet)); schema::huobi::V2SystemStatus networkInfo; + // NOLINTNEXTLINE(readability-implicit-bool-conversion) auto ec = ReadJson( strData, "Huobi system status", networkInfo); if (ec) { diff --git a/src/api/exchanges/src/krakenpublicapi.cpp b/src/api/exchanges/src/krakenpublicapi.cpp index 28e71d1c..512a9031 100644 --- a/src/api/exchanges/src/krakenpublicapi.cpp +++ b/src/api/exchanges/src/krakenpublicapi.cpp @@ -11,7 +11,6 @@ #include "cachedresult.hpp" #include "cct_const.hpp" #include "cct_exception.hpp" -#include "cct_json-container.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "coincenterinfo.hpp" @@ -27,6 +26,7 @@ #include "exchangepublicapitypes.hpp" #include "fiatconverter.hpp" #include "httprequesttype.hpp" +#include "kraken-schema.hpp" #include "market.hpp" #include "marketorderbook.hpp" #include "monetaryamount.hpp" @@ -40,24 +40,19 @@ namespace cct::api { namespace { -json::container PublicQuery(CurlHandle& curlHandle, std::string_view method, CurlPostData&& postData = CurlPostData()) { +template +T PublicQuery(CurlHandle& curlHandle, std::string_view method, CurlPostData&& postData = CurlPostData()) { RequestRetry requestRetry(curlHandle, CurlOptions(HttpRequestType::kGet, std::move(postData))); - - json::container baseRet = requestRetry.queryJson(method, [](const json::container& jsonResponse) { - const auto errorIt = jsonResponse.find("error"); - if (errorIt != jsonResponse.end() && !errorIt->empty()) { - log::warn("Full Kraken error: '{}'", jsonResponse.dump()); - return RequestRetry::Status::kResponseError; + return requestRetry.query(method, [](const T& response) { + if constexpr (amc::is_detected::value) { + if (!response.error.empty()) { + log::error("Kraken error(s): {}", response.error.front()); + return RequestRetry::Status::kResponseError; + } } + return RequestRetry::Status::kResponseOK; }); - - const auto resultIt = baseRet.find("result"); - json::container ret; - if (resultIt != baseRet.end()) { - ret.swap(*resultIt); - } - return ret; } bool CheckCurrencyExchange(std::string_view krakenEntryCurrencyCode, std::string_view krakenAltName, @@ -122,26 +117,9 @@ KrakenPublic::KrakenPublic(const CoincenterInfo& config, FiatConverter& fiatConv _tradableCurrenciesCache, _curlHandle) {} bool KrakenPublic::healthCheck() { - json::container result = - json::container::parse(_curlHandle.query("/public/SystemStatus", CurlOptions(HttpRequestType::kGet))); - auto errorIt = result.find("error"); - if (errorIt != result.end() && !errorIt->empty()) { - log::error("Error in {} status: {}", name(), errorIt->dump()); - return false; - } - auto resultIt = result.find("result"); - if (resultIt == result.end()) { - log::error("Unexpected answer from {} status: {}", name(), result.dump()); - return false; - } - auto statusIt = resultIt->find("status"); - if (statusIt == resultIt->end()) { - log::error("Unexpected answer from {} status: {}", name(), resultIt->dump()); - return false; - } - std::string_view statusStr = statusIt->get(); - log::info("{} status: {}", name(), statusStr); - return statusStr == "online"; + const auto result = PublicQuery(_curlHandle, "/public/SystemStatus"); + log::info("{} status: {}", name(), result.result.status); + return result.result.status == "online"; } std::optional KrakenPublic::queryWithdrawalFee(CurrencyCode currencyCode) { @@ -149,11 +127,12 @@ std::optional KrakenPublic::queryWithdrawalFee(CurrencyCode curr } CurrencyExchangeFlatSet KrakenPublic::TradableCurrenciesFunc::operator()() { - json::container result = PublicQuery(_curlHandle, "/public/Assets"); - CurrencyExchangeVector currencies; + const auto result = PublicQuery(_curlHandle, "/public/Assets"); const CurrencyCodeSet& excludedCurrencies = _assetConfig.allExclude; - for (const auto& [krakenAssetName, value] : result.items()) { - std::string_view altCodeStr = value["altname"].get(); + + CurrencyExchangeVector currencies; + for (const auto& [krakenAssetName, value] : result.result) { + std::string_view altCodeStr = value.altname; if (!CheckCurrencyExchange(krakenAssetName, altCodeStr, excludedCurrencies, _coincenterInfo)) { continue; } @@ -172,18 +151,18 @@ CurrencyExchangeFlatSet KrakenPublic::TradableCurrenciesFunc::operator()() { } std::pair KrakenPublic::MarketsFunc::operator()() { - json::container result = PublicQuery(_curlHandle, "/public/AssetPairs"); + const auto result = PublicQuery(_curlHandle, "/public/AssetPairs"); std::pair ret; - ret.first.reserve(static_cast(result.size())); - ret.second.reserve(result.size()); + ret.first.reserve(static_cast(result.result.size())); + ret.second.reserve(result.result.size()); const CurrencyCodeSet& excludedCurrencies = _assetConfig.allExclude; const CurrencyExchangeFlatSet& currencies = _tradableCurrenciesCache.get(); - for (const auto& [key, value] : result.items()) { - if (!value.contains("ordermin")) { + for (const auto& [key, value] : result.result) { + if (value.ordermin.isDefault()) { log::debug("Discard market {} as it does not contain min order information", key); continue; } - std::string_view krakenBaseStr = value["base"].get(); + std::string_view krakenBaseStr = value.base; CurrencyCode base(_coincenterInfo.standardizeCurrencyCode(krakenBaseStr)); auto baseIt = currencies.find(base); if (baseIt == currencies.end()) { @@ -194,7 +173,7 @@ std::pair KrakenPublic::Mar continue; } - std::string_view krakenQuoteStr = value["quote"].get(); + std::string_view krakenQuoteStr = value.quote; CurrencyCode quote(_coincenterInfo.standardizeCurrencyCode(krakenQuoteStr)); auto quoteIt = currencies.find(quote); if (quoteIt == currencies.end()) { @@ -206,8 +185,8 @@ std::pair KrakenPublic::Mar } auto mkIt = ret.first.emplace(base, quote).first; log::trace("Retrieved Kraken market {}", *mkIt); - MonetaryAmount orderMin(value["ordermin"].get(), base); - ret.second.insert_or_assign(*mkIt, MarketInfo{{value["lot_decimals"], value["pair_decimals"]}, orderMin}); + MonetaryAmount orderMin(value.ordermin, base); + ret.second.insert_or_assign(*mkIt, MarketInfo{{value.lot_decimals, value.pair_decimals}, orderMin}); } log::debug("Retrieved {} markets from kraken", ret.first.size()); return ret; @@ -247,9 +226,10 @@ MarketOrderBookMap KrakenPublic::AllOrderBooksFunc::operator()(int depth) { .assetsPairStrUpper(), mk); } - json::container result = PublicQuery(_curlHandle, "/public/Ticker", {{"pair", allAssetPairs}}); + const auto result = + PublicQuery(_curlHandle, "/public/Ticker", {{"pair", allAssetPairs}}); const auto time = Clock::now(); - for (const auto& [krakenAssetPair, assetPairDetails] : result.items()) { + for (const auto& [krakenAssetPair, assetPairDetails] : result.result) { if (krakenAssetPairToStdMarketMap.find(krakenAssetPair) == krakenAssetPairToStdMarketMap.end()) { log::error("Unable to find {}", krakenAssetPair); continue; @@ -260,12 +240,13 @@ MarketOrderBookMap KrakenPublic::AllOrderBooksFunc::operator()(int depth) { Market(_coincenterInfo.standardizeCurrencyCode(mk.base()), _coincenterInfo.standardizeCurrencyCode(mk.quote())); // a = ask array(, , ) // b = bid array(, , ) - const json::container& askDetails = assetPairDetails["a"]; - const json::container& bidDetails = assetPairDetails["b"]; - MonetaryAmount askPri(askDetails[0].get(), mk.quote()); - MonetaryAmount bidPri(bidDetails[0].get(), mk.quote()); - MonetaryAmount askVol(askDetails[2].get(), mk.base()); - MonetaryAmount bidVol(bidDetails[2].get(), mk.base()); + const auto& askDetails = assetPairDetails.a; + const auto& bidDetails = assetPairDetails.b; + + MonetaryAmount askPri(askDetails[0], mk.quote()); + MonetaryAmount bidPri(bidDetails[0], mk.quote()); + MonetaryAmount askVol(askDetails[2], mk.base()); + MonetaryAmount bidVol(bidDetails[2], mk.base()); const MarketsFunc::MarketInfo& marketInfo = marketInfoMap.find(mk)->second; @@ -296,19 +277,19 @@ MarketOrderBook KrakenPublic::OrderBookFunc::operator()(Market mk, int count) { MarketOrderBookLines orderBookLines; - json::container result = PublicQuery(_curlHandle, "/public/Depth", {{"pair", krakenAssetPair}, {"count", count}}); + const auto result = PublicQuery(_curlHandle, "/public/Depth", + {{"pair", krakenAssetPair}, {"count", count}}); + const auto dataIt = result.result.find(krakenAssetPair); const auto nowTime = Clock::now(); - if (!result.empty()) { - const json::container& entry = result.front(); - const auto asksIt = entry.find("asks"); - const auto bidsIt = entry.find("bids"); - - orderBookLines.reserve(asksIt->size() + bidsIt->size()); - for (const auto& asksOrBids : {asksIt, bidsIt}) { - const auto type = asksOrBids == asksIt ? OrderBookLine::Type::kAsk : OrderBookLine::Type::kBid; + if (dataIt != result.result.end()) { + const auto& asks = dataIt->second.asks; + const auto& bids = dataIt->second.bids; + orderBookLines.reserve(asks.size() + bids.size()); + for (const auto asksOrBids : {&asks, &bids}) { + const auto type = asksOrBids == &asks ? OrderBookLine::Type::kAsk : OrderBookLine::Type::kBid; for (const auto& priceQuantityTuple : *asksOrBids) { - MonetaryAmount amount(priceQuantityTuple[1].get(), mk.base()); - MonetaryAmount price(priceQuantityTuple[0].get(), mk.quote()); + MonetaryAmount price(std::get(priceQuantityTuple[0]), mk.quote()); + MonetaryAmount amount(std::get(priceQuantityTuple[1]), mk.base()); orderBookLines.push(amount, price, type); } @@ -334,13 +315,12 @@ KrakenPublic::TickerFunc::Last24hTradedVolumeAndLatestPricePair KrakenPublic::Ti const Market krakenMarket = GetKrakenMarketOrDefault(_tradableCurrenciesCache.get(), mk); if (krakenMarket.isDefined()) { - const json::container result = - PublicQuery(_curlHandle, "/public/Ticker", {{"pair", krakenMarket.assetsPairStrUpper()}}); - for (const auto& [krakenAssetPair, details] : result.items()) { - std::string_view last24hVol = details["v"][1].get(); - std::string_view lastTickerPrice = details["c"][0].get(); - - return {MonetaryAmount(last24hVol, mk.base()), MonetaryAmount(lastTickerPrice, mk.quote())}; + const auto krakenPair = krakenMarket.assetsPairStrUpper(); + const auto result = + PublicQuery(_curlHandle, "/public/Ticker", {{"pair", krakenPair}}); + const auto dataIt = result.result.find(krakenPair); + if (dataIt != result.result.end()) { + return {MonetaryAmount(dataIt->second.v[1], mk.base()), MonetaryAmount(dataIt->second.c[0], mk.quote())}; } } @@ -352,18 +332,21 @@ PublicTradeVector KrakenPublic::queryLastTrades(Market mk, int nbLastTrades) { const Market krakenMarket = GetKrakenMarketOrDefault(_tradableCurrenciesCache.get(), mk); if (krakenMarket.isDefined()) { - json::container result = PublicQuery(_curlHandle, "/public/Trades", - {{"pair", krakenMarket.assetsPairStrUpper()}, {"count", nbLastTrades}}); + const auto krakenPair = krakenMarket.assetsPairStrUpper(); + const auto result = PublicQuery(_curlHandle, "/public/Trades", + {{"pair", krakenPair}, {"count", nbLastTrades}}); + + const auto dataIt = result.result.find(krakenPair); - if (!result.empty()) { - const auto& lastTrades = result.front(); + if (dataIt != result.result.end()) { + const auto& lastTrades = std::get(dataIt->second); ret.reserve(static_cast(lastTrades.size())); - for (const json::container& det : lastTrades) { - const MonetaryAmount price(det[0].get(), mk.quote()); - const MonetaryAmount amount(det[1].get(), mk.base()); - const auto millisecondsSinceEpoch = static_cast(det[2].get() * 1000); - const TradeSide tradeSide = det[3].get() == "b" ? TradeSide::kBuy : TradeSide::kSell; + for (const auto& det : lastTrades) { + const MonetaryAmount price(std::get(det[0]), mk.quote()); + const MonetaryAmount amount(std::get(det[1]), mk.base()); + const auto millisecondsSinceEpoch = static_cast(std::get(det[2]) * 1000); + const TradeSide tradeSide = std::get(det[3]) == "b" ? TradeSide::kBuy : TradeSide::kSell; ret.emplace_back(tradeSide, amount, price, TimePoint(milliseconds(millisecondsSinceEpoch))); } diff --git a/src/basic-objects/test/currencycode_test.cpp b/src/basic-objects/test/currencycode_test.cpp index de030de3..d447b7d1 100644 --- a/src/basic-objects/test/currencycode_test.cpp +++ b/src/basic-objects/test/currencycode_test.cpp @@ -265,6 +265,7 @@ TEST(CurrencyCodeTest, JsonSerializationKey) { std::map map{{"DOGE", true}, {"BTC", false}}; string buffer; + // NOLINTNEXTLINE(readability-implicit-bool-conversion) auto res = json::write(map, buffer); EXPECT_FALSE(res); diff --git a/src/http-request/include/request-retry.hpp b/src/http-request/include/request-retry.hpp index 091c9696..3f05714a 100644 --- a/src/http-request/include/request-retry.hpp +++ b/src/http-request/include/request-retry.hpp @@ -25,6 +25,12 @@ class RequestRetry { public: enum class Status : int8_t { kResponseError, kResponseOK }; + static constexpr auto kDefaultJsonOpts = + json::opts{.error_on_unknown_keys = false, // NOLINT(readability-implicit-bool-conversion) + .minified = true, // NOLINT(readability-implicit-bool-conversion) + .error_on_const_read = true, // NOLINT(readability-implicit-bool-conversion) + .raw_string = true}; // NOLINT(readability-implicit-bool-conversion) + RequestRetry(CurlHandle &curlHandle, CurlOptions curlOptions, QueryRetryPolicy queryRetryPolicy = QueryRetryPolicy()) : _curlHandle(curlHandle), _curlOptions(std::move(curlOptions)), _queryRetryPolicy(queryRetryPolicy) {} @@ -32,8 +38,7 @@ class RequestRetry { /// responseStatus(jsonResponse) returns kResponseError. /// responseStatus should be a functor taking a single json by const reference argument, returning Status::kResponseOK /// if success, Status::kResponseError in case of error. - template - json::container queryJson(const StringType &endpoint, ResponseStatusT responseStatus) { + json::container queryJson(const auto &endpoint, auto responseStatus) { return queryJson(endpoint, responseStatus, [](CurlOptions &) {}); } @@ -43,20 +48,17 @@ class RequestRetry { /// if success, Status::kResponseError in case of error. /// postDataUpdateFunc is a functor that takes the embedded CurlOptions's reference as single argument and updates it /// before each query - template - json::container queryJson(const StringType &endpoint, ResponseStatusT responseStatus, - PostDataFuncT postDataUpdateFunc) { - return query(endpoint, responseStatus, - postDataUpdateFunc); + json::container queryJson(const auto &endpoint, auto responseStatus, auto postDataUpdateFunc) { + return query(endpoint, responseStatus, postDataUpdateFunc); } - template - T query(const StringType &endpoint, ResponseStatusT responseStatus) { + template + T query(const auto &endpoint, auto responseStatus) { return query(endpoint, responseStatus, [](CurlOptions &) {}); } - template - T query(const StringType &endpoint, ResponseStatusT responseStatus, PostDataFuncT postDataUpdateFunc) { + template + T query(const auto &endpoint, auto responseStatus, auto postDataUpdateFunc) { auto sleepingTime = _queryRetryPolicy.initialRetryDelay; decltype(_queryRetryPolicy.nbMaxRetries) nbRetries = 0; bool parsingError;