From 7a7ff44104ab6650ba957df639cd149c0918ee7d Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Sat, 10 Feb 2024 13:02:51 +0100 Subject: [PATCH] Fix Bithumb withdrawal fees parsing, factorize crawlers code --- src/api-objects/include/ordersconstraints.hpp | 3 +- src/api-objects/include/publictrade.hpp | 4 +- src/api-objects/include/recentdeposit.hpp | 3 +- .../withdrawsordepositsconstraints.hpp | 3 +- src/api/common/CMakeLists.txt | 7 +- src/api/common/include/commonapi.hpp | 24 +- .../common/include/withdrawalfees-crawler.hpp | 51 ++++ src/api/common/src/commonapi.cpp | 21 +- src/api/common/src/withdrawalfees-crawler.cpp | 244 ++++++++++++++++++ src/api/common/test/commonapi_test.cpp | 9 +- .../exchanges/include/bithumbpublicapi.hpp | 25 +- src/api/exchanges/include/krakenpublicapi.hpp | 28 +- src/api/exchanges/src/bithumbpublicapi.cpp | 57 +--- src/api/exchanges/src/krakenpublicapi.cpp | 230 +---------------- src/api/exchanges/test/binanceapi_test.cpp | 2 +- src/api/exchanges/test/bithumbapi_test.cpp | 2 +- ...pi_test.hpp => exchangecommonapi_test.hpp} | 0 src/api/exchanges/test/huobiapi_test.cpp | 2 +- src/api/exchanges/test/krakenapi_test.cpp | 2 +- src/api/exchanges/test/kucoinapi_test.cpp | 2 +- src/api/exchanges/test/upbitapi_test.cpp | 2 +- 21 files changed, 369 insertions(+), 352 deletions(-) create mode 100644 src/api/common/include/withdrawalfees-crawler.hpp create mode 100644 src/api/common/src/withdrawalfees-crawler.cpp rename src/api/exchanges/test/{commonapi_test.hpp => exchangecommonapi_test.hpp} (100%) diff --git a/src/api-objects/include/ordersconstraints.hpp b/src/api-objects/include/ordersconstraints.hpp index 666d1a2f..8bfe8b22 100644 --- a/src/api-objects/include/ordersconstraints.hpp +++ b/src/api-objects/include/ordersconstraints.hpp @@ -120,7 +120,8 @@ class OrdersConstraints { template <> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + const auto it = ctx.begin(); + const auto end = ctx.end(); if (it != end && *it != '}') { throw format_error("invalid format"); } diff --git a/src/api-objects/include/publictrade.hpp b/src/api-objects/include/publictrade.hpp index 96671651..350840f6 100644 --- a/src/api-objects/include/publictrade.hpp +++ b/src/api-objects/include/publictrade.hpp @@ -26,7 +26,7 @@ class PublicTrade { /// 3 way operator - make compiler generate all 6 operators (including == and !=) /// we order by time first, then amount, price, etc. Do not change the fields order! - auto operator<=>(const PublicTrade &) const = default; + std::strong_ordering operator<=>(const PublicTrade &) const = default; private: TimePoint _time; @@ -34,4 +34,4 @@ class PublicTrade { MonetaryAmount _price; TradeSide _side; }; -} // namespace cct \ No newline at end of file +} // namespace cct diff --git a/src/api-objects/include/recentdeposit.hpp b/src/api-objects/include/recentdeposit.hpp index 66f3de5a..639f489a 100644 --- a/src/api-objects/include/recentdeposit.hpp +++ b/src/api-objects/include/recentdeposit.hpp @@ -49,7 +49,8 @@ class ClosestRecentDepositPicker { template <> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + const auto it = ctx.begin(); + const auto end = ctx.end(); if (it != end && *it != '}') { throw format_error("invalid format"); } diff --git a/src/api-objects/include/withdrawsordepositsconstraints.hpp b/src/api-objects/include/withdrawsordepositsconstraints.hpp index 46602211..7f19bf14 100644 --- a/src/api-objects/include/withdrawsordepositsconstraints.hpp +++ b/src/api-objects/include/withdrawsordepositsconstraints.hpp @@ -66,7 +66,8 @@ class WithdrawsOrDepositsConstraints { template <> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + const auto it = ctx.begin(); + const auto end = ctx.end(); if (it != end && *it != '}') { throw format_error("invalid format"); } diff --git a/src/api/common/CMakeLists.txt b/src/api/common/CMakeLists.txt index 1b3d9b30..64e97f3c 100644 --- a/src/api/common/CMakeLists.txt +++ b/src/api/common/CMakeLists.txt @@ -4,8 +4,8 @@ aux_source_directory(src API_COMMON_SRC) include(FindOpenSSL) add_library(coincenter_api-common STATIC ${API_COMMON_SRC}) -target_link_libraries(coincenter_api-common PUBLIC coincenter_objects) target_link_libraries(coincenter_api-common PUBLIC coincenter_api-objects) +target_link_libraries(coincenter_api-common PUBLIC coincenter_objects) target_link_libraries(coincenter_api-common PUBLIC coincenter_http-request) target_link_libraries(coincenter_api-common PRIVATE OpenSSL::SSL) @@ -19,11 +19,8 @@ function(add_common_test name) add_unit_test( ${name} ${MY_UNPARSED_ARGUMENTS} - src/commonapi.cpp LIBRARIES - coincenter_api-objects - coincenter_http-request - coincenter_objects + coincenter_api-common ) endfunction() diff --git a/src/api/common/include/commonapi.hpp b/src/api/common/include/commonapi.hpp index 351506a7..e50c2c4e 100644 --- a/src/api/common/include/commonapi.hpp +++ b/src/api/common/include/commonapi.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "cachedresult.hpp" #include "cct_flatset.hpp" @@ -11,6 +12,7 @@ #include "currencycode.hpp" #include "exchangebase.hpp" #include "timedef.hpp" +#include "withdrawalfees-crawler.hpp" namespace cct { class CoincenterInfo; @@ -22,22 +24,20 @@ class CommonAPI : public ExchangeBase { enum class AtInit : int8_t { kLoadFromFileCache, kNoLoadFromFileCache }; - CommonAPI(const CoincenterInfo &config, Duration fiatsUpdateFrequency = std::chrono::hours(96), - AtInit atInit = AtInit::kLoadFromFileCache); + explicit CommonAPI(const CoincenterInfo &coincenterInfo, Duration fiatsUpdateFrequency = std::chrono::hours(96), + Duration withdrawalFeesUpdateFrequency = std::chrono::hours(96), + AtInit atInit = AtInit::kLoadFromFileCache); /// Returns a new set of fiat currencies. - Fiats queryFiats() { - std::lock_guard guard(_fiatsMutex); - return _fiatsCache.get(); - } + Fiats queryFiats(); /// Tells whether given currency code is a fiat currency or not. /// Fiat currencies are traditional currencies, such as EUR, USD, GBP, KRW, etc. /// Information here: https://en.wikipedia.org/wiki/Fiat_money - bool queryIsCurrencyCodeFiat(CurrencyCode currencyCode) { - std::lock_guard guard(_fiatsMutex); - return _fiatsCache.get().contains(currencyCode); - } + bool queryIsCurrencyCodeFiat(CurrencyCode currencyCode); + + /// Query withdrawal fees from crawler sources. It's not guaranteed to work though. + WithdrawalFeesCrawler::WithdrawalInfoMaps queryWithdrawalFees(std::string_view exchangeName); void updateCacheFile() const override; @@ -58,8 +58,10 @@ class CommonAPI : public ExchangeBase { CachedResultVault _cachedResultVault; const CoincenterInfo &_coincenterInfo; - std::mutex _fiatsMutex; + // A single mutex is needed as the cached result vault is shared + std::mutex _globalMutex; CachedResult _fiatsCache; + WithdrawalFeesCrawler _withdrawalFeesCrawler; }; } // namespace api } // namespace cct \ No newline at end of file diff --git a/src/api/common/include/withdrawalfees-crawler.hpp b/src/api/common/include/withdrawalfees-crawler.hpp new file mode 100644 index 00000000..0e2a295f --- /dev/null +++ b/src/api/common/include/withdrawalfees-crawler.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include "cachedresult.hpp" +#include "cachedresultvault.hpp" +#include "coincenterinfo.hpp" +#include "curlhandle.hpp" +#include "currencycode.hpp" +#include "monetaryamount.hpp" +#include "monetaryamountbycurrencyset.hpp" +#include "timedef.hpp" + +namespace cct { + +/// This class is able to crawl some public withdrawal fees web pages in order to retrieve them from unofficial sources, +/// which is better than nothing. This class is non thread-safe. +class WithdrawalFeesCrawler { + public: + WithdrawalFeesCrawler(const CoincenterInfo& coincenterInfo, Duration minDurationBetweenQueries, + CachedResultVault& cachedResultVault); + + using WithdrawalMinMap = std::unordered_map; + using WithdrawalInfoMaps = std::pair; + + WithdrawalInfoMaps get(std::string_view exchangeName) { return _withdrawalFeesCache.get(exchangeName); } + + void updateCacheFile() const; + + private: + class WithdrawalFeesFunc { + public: + explicit WithdrawalFeesFunc(const CoincenterInfo& coincenterInfo); + + WithdrawalInfoMaps operator()(std::string_view exchangeName); + + private: + WithdrawalInfoMaps get1(std::string_view exchangeName); + WithdrawalInfoMaps get2(std::string_view exchangeName); + + CurlHandle _curlHandle1; + CurlHandle _curlHandle2; + }; + + const CoincenterInfo& _coincenterInfo; + CachedResult _withdrawalFeesCache; +}; + +} // namespace cct \ No newline at end of file diff --git a/src/api/common/src/commonapi.cpp b/src/api/common/src/commonapi.cpp index 1c360e3b..506f942c 100644 --- a/src/api/common/src/commonapi.cpp +++ b/src/api/common/src/commonapi.cpp @@ -26,8 +26,11 @@ File GetFiatCacheFile(std::string_view dataDir) { } // namespace -CommonAPI::CommonAPI(const CoincenterInfo& config, Duration fiatsUpdateFrequency, AtInit atInit) - : _coincenterInfo(config), _fiatsCache(CachedResultOptions(fiatsUpdateFrequency, _cachedResultVault)) { +CommonAPI::CommonAPI(const CoincenterInfo& coincenterInfo, Duration fiatsUpdateFrequency, + Duration withdrawalFeesUpdateFrequency, AtInit atInit) + : _coincenterInfo(coincenterInfo), + _fiatsCache(CachedResultOptions(fiatsUpdateFrequency, _cachedResultVault)), + _withdrawalFeesCrawler(coincenterInfo, withdrawalFeesUpdateFrequency, _cachedResultVault) { if (atInit == AtInit::kLoadFromFileCache) { json data = GetFiatCacheFile(_coincenterInfo.dataDir()).readAllJson(); if (!data.empty()) { @@ -45,6 +48,18 @@ CommonAPI::CommonAPI(const CoincenterInfo& config, Duration fiatsUpdateFrequency } } +CommonAPI::Fiats CommonAPI::queryFiats() { + std::lock_guard guard(_globalMutex); + return _fiatsCache.get(); +} + +bool CommonAPI::queryIsCurrencyCodeFiat(CurrencyCode currencyCode) { return queryFiats().contains(currencyCode); } + +WithdrawalFeesCrawler::WithdrawalInfoMaps CommonAPI::queryWithdrawalFees(std::string_view exchangeName) { + std::lock_guard guard(_globalMutex); + return _withdrawalFeesCrawler.get(exchangeName); +} + namespace { constexpr std::string_view kFiatsUrlSource1 = "https://datahub.io/core/currency-codes/r/codes-all.json"; constexpr std::string_view kFiatsUrlSource2 = "https://www.iban.com/currency-codes"; @@ -157,5 +172,7 @@ void CommonAPI::updateCacheFile() const { data["timeepoch"] = TimestampToS(fiatsPtrLastUpdatedTimePair.second); fiatsCacheFile.write(data); } + + _withdrawalFeesCrawler.updateCacheFile(); } } // namespace cct::api diff --git a/src/api/common/src/withdrawalfees-crawler.cpp b/src/api/common/src/withdrawalfees-crawler.cpp new file mode 100644 index 00000000..6d5f766e --- /dev/null +++ b/src/api/common/src/withdrawalfees-crawler.cpp @@ -0,0 +1,244 @@ +#include "withdrawalfees-crawler.hpp" + +#include +#include +#include +#include + +#include "cachedresultvault.hpp" +#include "cct_cctype.hpp" +#include "cct_json.hpp" +#include "cct_log.hpp" +#include "coincenterinfo.hpp" +#include "curloptions.hpp" +#include "file.hpp" +#include "httprequesttype.hpp" +#include "timedef.hpp" + +namespace cct { + +namespace { +constexpr std::string_view kUrlWithdrawFee1 = "https://withdrawalfees.com/exchanges/"; +constexpr std::string_view kUrlWithdrawFee2 = "https://www.cryptofeesaver.com/exchanges/fees/"; + +File GetWithdrawInfoFile(std::string_view dataDir) { + return {dataDir, File::Type::kCache, "withdrawinfo.json", File::IfError::kNoThrow}; +} +} // namespace + +WithdrawalFeesCrawler::WithdrawalFeesCrawler(const CoincenterInfo& coincenterInfo, Duration minDurationBetweenQueries, + CachedResultVault& cachedResultVault) + : _coincenterInfo(coincenterInfo), + _withdrawalFeesCache(CachedResultOptions(minDurationBetweenQueries, cachedResultVault), coincenterInfo) { + json data = GetWithdrawInfoFile(_coincenterInfo.dataDir()).readAllJson(); + if (!data.empty()) { + const auto nowTime = Clock::now(); + for (const auto& [exchangeName, exchangeData] : data.items()) { + TimePoint lastUpdatedTime(TimeInS(exchangeData["timeepoch"].get())); + if (nowTime < lastUpdatedTime + minDurationBetweenQueries) { + // we can reuse file data + WithdrawalInfoMaps withdrawalInfoMaps; + + for (const auto& [curCodeStr, val] : exchangeData["assets"].items()) { + CurrencyCode cur(curCodeStr); + MonetaryAmount withdrawMin(val["min"].get(), cur); + MonetaryAmount withdrawFee(val["fee"].get(), cur); + + log::trace("Updated {} withdrawal fee {} from cache", exchangeName, withdrawFee); + log::trace("Updated {} min withdraw {} from cache", exchangeName, withdrawMin); + + withdrawalInfoMaps.first.insert(withdrawFee); + withdrawalInfoMaps.second.insert_or_assign(cur, withdrawMin); + } + + _withdrawalFeesCache.set(std::move(withdrawalInfoMaps), lastUpdatedTime, exchangeName); + } + } + } +} + +WithdrawalFeesCrawler::WithdrawalFeesFunc::WithdrawalFeesFunc(const CoincenterInfo& coincenterInfo) + : _curlHandle1(kUrlWithdrawFee1, coincenterInfo.metricGatewayPtr(), PermanentCurlOptions(), + coincenterInfo.getRunMode()), + _curlHandle2(kUrlWithdrawFee2, coincenterInfo.metricGatewayPtr(), PermanentCurlOptions(), + coincenterInfo.getRunMode()) {} + +WithdrawalFeesCrawler::WithdrawalInfoMaps WithdrawalFeesCrawler::WithdrawalFeesFunc::operator()( + std::string_view exchangeName) { + auto [withdrawFees1, withdrawMinMap1] = get1(exchangeName); + auto [withdrawFees2, withdrawMinMap2] = get2(exchangeName); + + withdrawFees1.insert(withdrawFees2.begin(), withdrawFees2.end()); + withdrawMinMap1.merge(std::move(withdrawMinMap2)); + + if (withdrawFees1.empty() || withdrawMinMap1.empty()) { + throw exception("Unable to parse {} withdrawal fees", exchangeName); + } + return std::make_pair(std::move(withdrawFees1), std::move(withdrawMinMap1)); +} + +void WithdrawalFeesCrawler::updateCacheFile() const { + json data; + for (const std::string_view exchangeName : kSupportedExchanges) { + const auto [withdrawalInfoMapsPtr, latestUpdate] = _withdrawalFeesCache.retrieve(exchangeName); + if (withdrawalInfoMapsPtr != nullptr) { + const WithdrawalInfoMaps& withdrawalInfoMaps = *withdrawalInfoMapsPtr; + + json exchangeData; + exchangeData["timeepoch"] = TimestampToS(latestUpdate); + for (const auto withdrawFee : withdrawalInfoMaps.first) { + string curCodeStr = withdrawFee.currencyCode().str(); + exchangeData["assets"][curCodeStr]["min"] = + withdrawalInfoMaps.second.find(withdrawFee.currencyCode())->second.amountStr(); + exchangeData["assets"][curCodeStr]["fee"] = withdrawFee.amountStr(); + } + + data.emplace(exchangeName, std::move(exchangeData)); + } + } + GetWithdrawInfoFile(_coincenterInfo.dataDir()).write(data); +} + +WithdrawalFeesCrawler::WithdrawalInfoMaps WithdrawalFeesCrawler::WithdrawalFeesFunc::get1( + std::string_view exchangeName) { + std::string_view withdrawalFeesCsv = _curlHandle1.query(exchangeName, CurlOptions(HttpRequestType::kGet)); + + static constexpr std::string_view kBeginWithdrawalFeeHtmlTag = ""; + static constexpr std::string_view kBeginMinWithdrawalHtmlTag = ""; + static constexpr std::string_view kParseError1Msg = + "Parse error from source 1 - either site information unavailable or code to be updated"; + + WithdrawalInfoMaps ret; + + std::size_t searchPos = 0; + while ((searchPos = withdrawalFeesCsv.find(kBeginWithdrawalFeeHtmlTag, searchPos)) != string::npos) { + auto parseNextFee = [&withdrawalFeesCsv](std::size_t& begPos) { + static constexpr std::string_view kBeginFeeHtmlTag = "
"; + static constexpr std::string_view kEndHtmlTag = "
"; + + MonetaryAmount ret; + + begPos = withdrawalFeesCsv.find(kBeginFeeHtmlTag, begPos); + if (begPos == string::npos) { + log::error(kParseError1Msg); + return ret; + } + begPos += kBeginFeeHtmlTag.size(); + // There are sometimes strange characters at beginning of the amount + while (!isdigit(withdrawalFeesCsv[begPos])) { + ++begPos; + } + std::size_t endPos = withdrawalFeesCsv.find(kEndHtmlTag, begPos + 1); + if (endPos == string::npos) { + log::error(kParseError1Msg); + return ret; + } + ret = MonetaryAmount(std::string_view(withdrawalFeesCsv.begin() + begPos, withdrawalFeesCsv.begin() + endPos)); + begPos = endPos + kEndHtmlTag.size(); + return ret; + }; + + // Locate withdrawal fee + searchPos += kBeginWithdrawalFeeHtmlTag.size(); + MonetaryAmount withdrawalFee = parseNextFee(searchPos); + if (withdrawalFee.currencyCode().isNeutral()) { + ret.first.clear(); + break; + } + + log::trace("Updated {} withdrawal fee {} from first source", exchangeName, withdrawalFee); + ret.first.insert(withdrawalFee); + + // Locate min withdrawal + searchPos = withdrawalFeesCsv.find(kBeginMinWithdrawalHtmlTag, searchPos) + kBeginMinWithdrawalHtmlTag.size(); + if (searchPos == string::npos) { + log::error(kParseError1Msg); + ret.first.clear(); + break; + } + + MonetaryAmount minWithdrawal = parseNextFee(searchPos); + if (minWithdrawal.currencyCode().isNeutral()) { + ret.first.clear(); + break; + } + + log::trace("Updated {} min withdrawal {} from first source", exchangeName, minWithdrawal); + ret.second.insert_or_assign(minWithdrawal.currencyCode(), minWithdrawal); + } + if (ret.first.empty() || ret.second.empty()) { + log::error("Unable to parse {} withdrawal fees from first source", exchangeName); + } else { + log::info("Updated {} withdraw infos for {} coins from first source", exchangeName, ret.first.size()); + } + return ret; +} + +WithdrawalFeesCrawler::WithdrawalInfoMaps WithdrawalFeesCrawler::WithdrawalFeesFunc::get2( + std::string_view exchangeName) { + std::string_view withdrawalFeesCsv = _curlHandle2.query(exchangeName, CurlOptions(HttpRequestType::kGet)); + + static constexpr std::string_view kBeginTableTitle = "Deposit & Withdrawal fees"; + + std::size_t begPos = withdrawalFeesCsv.find(kBeginTableTitle); + WithdrawalInfoMaps ret; + if (begPos != string::npos) { + static constexpr std::string_view kBeginTable = " MonetaryAmount { + static constexpr std::string_view kBeginFeeHtmlTag = ""; + + // Skip one column + for (int colPos = 0; colPos < 2; ++colPos) { + begPos = withdrawalFeesCsv.find(kBeginFeeHtmlTag, begPos); + if (begPos == string::npos) { + throw exception("Unable to parse {} withdrawal fees from source 2: expecting begin HTML tag", + exchangeName); + } + begPos += kBeginFeeHtmlTag.size(); + } + // Scan until next non space char + while (begPos < withdrawalFeesCsv.size() && isspace(withdrawalFeesCsv[begPos])) { + ++begPos; + } + std::size_t endPos = withdrawalFeesCsv.find(kEndHtmlTag, begPos + 1); + if (endPos == string::npos) { + throw exception("Unable to parse {} withdrawal fees from source 2: expecting end HTML tag", exchangeName); + } + std::size_t endHtmlTagPos = endPos; + while (endPos > begPos && isspace(withdrawalFeesCsv[endPos - 1])) { + --endPos; + } + MonetaryAmount ret(std::string_view(withdrawalFeesCsv.begin() + begPos, withdrawalFeesCsv.begin() + endPos)); + begPos = endHtmlTagPos + kEndHtmlTag.size(); + return ret; + }; + + // Locate withdrawal fee + searchPos += kBeginWithdrawalFeeHtmlTag.size(); + MonetaryAmount withdrawalFee = parseNextFee(searchPos); + + log::trace("Updated {} withdrawal fee {} from source 2, simulate min withdrawal amount", exchangeName, + withdrawalFee); + ret.first.insert(withdrawalFee); + + ret.second.insert_or_assign(withdrawalFee.currencyCode(), 3 * withdrawalFee); + } + } + } + + if (ret.first.empty() || ret.second.empty()) { + log::error("Unable to parse {} withdrawal fees from second source", exchangeName); + } else { + log::info("Updated {} withdraw infos for {} coins from second source", exchangeName, ret.first.size()); + } + return ret; +} + +} // namespace cct \ No newline at end of file diff --git a/src/api/common/test/commonapi_test.cpp b/src/api/common/test/commonapi_test.cpp index 44d0b606..f5a4ead8 100644 --- a/src/api/common/test/commonapi_test.cpp +++ b/src/api/common/test/commonapi_test.cpp @@ -11,8 +11,8 @@ namespace cct::api { class CommonAPITest : public ::testing::Test { protected: settings::RunMode runMode = settings::RunMode::kTestKeys; - CoincenterInfo config{runMode}; - CommonAPI commonAPI{config, Duration::max(), CommonAPI::AtInit::kNoLoadFromFileCache}; + CoincenterInfo coincenterInfo{runMode}; + CommonAPI commonAPI{coincenterInfo, Duration::max(), Duration::max(), CommonAPI::AtInit::kNoLoadFromFileCache}; }; TEST_F(CommonAPITest, IsFiatService) { @@ -22,4 +22,9 @@ TEST_F(CommonAPITest, IsFiatService) { EXPECT_FALSE(commonAPI.queryIsCurrencyCodeFiat("BTC")); EXPECT_FALSE(commonAPI.queryIsCurrencyCodeFiat("XRP")); } + +TEST_F(CommonAPITest, WithdrawalFeesCrawlerService) { + EXPECT_GT(commonAPI.queryWithdrawalFees("kraken").first.size(), 0UL); + EXPECT_GT(commonAPI.queryWithdrawalFees("bithumb").first.size(), 0UL); +} } // namespace cct::api \ No newline at end of file diff --git a/src/api/exchanges/include/bithumbpublicapi.hpp b/src/api/exchanges/include/bithumbpublicapi.hpp index 558bfc66..9e354640 100644 --- a/src/api/exchanges/include/bithumbpublicapi.hpp +++ b/src/api/exchanges/include/bithumbpublicapi.hpp @@ -5,8 +5,13 @@ #include "cachedresult.hpp" #include "curlhandle.hpp" +#include "currencyexchange.hpp" #include "exchangepublicapi.hpp" #include "exchangepublicapitypes.hpp" +#include "market.hpp" +#include "marketorderbook.hpp" +#include "monetaryamount.hpp" +#include "monetaryamountbycurrencyset.hpp" namespace cct { @@ -19,6 +24,7 @@ class CommonAPI; class BithumbPublic : public ExchangePublic { public: + static constexpr std::string_view kExchangeName = "bithumb"; static constexpr std::string_view kStatusOKStr = "0000"; BithumbPublic(const CoincenterInfo& config, FiatConverter& fiatConverter, CommonAPI& commonAPI); @@ -35,11 +41,13 @@ class BithumbPublic : public ExchangePublic { MarketPriceMap queryAllPrices() override { return MarketPriceMapFromMarketOrderBookMap(_allOrderBooksCache.get()); } - MonetaryAmountByCurrencySet queryWithdrawalFees() override { return _withdrawalFeesCache.get(); } + MonetaryAmountByCurrencySet queryWithdrawalFees() override { + return _commonApi.queryWithdrawalFees(kExchangeName).first; + } std::optional queryWithdrawalFee(CurrencyCode currencyCode) override; - bool isWithdrawalFeesSourceReliable() const override { return true; } + bool isWithdrawalFeesSourceReliable() const override { return false; } MarketOrderBookMap queryAllApproximatedOrderBooks([[maybe_unused]] int depth = kDefaultDepth) override { return _allOrderBooksCache.get(); @@ -68,18 +76,6 @@ class BithumbPublic : public ExchangePublic { CurlHandle& _curlHandle; }; - struct WithdrawalFeesFunc { - static constexpr std::string_view kFeeUrl = "https://www.bithumb.com"; - - WithdrawalFeesFunc(AbstractMetricGateway* pMetricGateway, const PermanentCurlOptions& permanentCurlOptions, - settings::RunMode runMode) - : _curlHandle(kFeeUrl, pMetricGateway, permanentCurlOptions, runMode) {} - - MonetaryAmountByCurrencySet operator()(); - - CurlHandle _curlHandle; - }; - struct AllOrderBooksFunc { MarketOrderBookMap operator()(); @@ -104,7 +100,6 @@ class BithumbPublic : public ExchangePublic { CurlHandle _curlHandle; CachedResult _tradableCurrenciesCache; - CachedResult _withdrawalFeesCache; CachedResult _allOrderBooksCache; CachedResult _orderbookCache; CachedResult _tradedVolumeCache; diff --git a/src/api/exchanges/include/krakenpublicapi.hpp b/src/api/exchanges/include/krakenpublicapi.hpp index 4c164168..637fd2cf 100644 --- a/src/api/exchanges/include/krakenpublicapi.hpp +++ b/src/api/exchanges/include/krakenpublicapi.hpp @@ -20,6 +20,8 @@ class CommonAPI; class KrakenPublic : public ExchangePublic { public: + static constexpr std::string_view kExchangeName = "kraken"; + KrakenPublic(const CoincenterInfo& config, FiatConverter& fiatConverter, CommonAPI& commonAPI); bool healthCheck() override; @@ -36,7 +38,9 @@ class KrakenPublic : public ExchangePublic { MarketPriceMap queryAllPrices() override { return MarketPriceMapFromMarketOrderBookMap(_allOrderBooksCache.get(1)); } - MonetaryAmountByCurrencySet queryWithdrawalFees() override { return _withdrawalFeesCache.get().first; } + MonetaryAmountByCurrencySet queryWithdrawalFees() override { + return _commonApi.queryWithdrawalFees(kExchangeName).first; + } std::optional queryWithdrawalFee(CurrencyCode currencyCode) override; @@ -56,8 +60,6 @@ class KrakenPublic : public ExchangePublic { MonetaryAmount queryLastPrice(Market mk) override { return _tickerCache.get(mk).second; } - void updateCacheFile() const override; - static constexpr std::string_view kUrlPrefix = "https://api.kraken.com"; static constexpr std::string_view kVersion = "/0"; static constexpr std::string_view kUrlBase = JoinStringView_v; @@ -74,25 +76,6 @@ class KrakenPublic : public ExchangePublic { const ExchangeConfig& _exchangeConfig; }; - class WithdrawalFeesFunc { - public: - using WithdrawalMinMap = std::unordered_map; - using WithdrawalInfoMaps = std::pair; - - WithdrawalFeesFunc(const CoincenterInfo& coincenterInfo, Duration minDurationBetweenQueries); - - WithdrawalInfoMaps operator()(); - - private: - WithdrawalInfoMaps updateFromSource1(); - WithdrawalInfoMaps updateFromSource2(); - - // Use different curl handles as it is not from Kraken official REST API. - // There are two of them such that second one may be used in case of failure of the first one - CurlHandle _curlHandle1; - CurlHandle _curlHandle2; - }; - struct MarketsFunc { struct MarketInfo { VolAndPriNbDecimals volAndPriNbDecimals; @@ -137,7 +120,6 @@ class KrakenPublic : public ExchangePublic { CurlHandle _curlHandle; CachedResult _tradableCurrenciesCache; - CachedResult _withdrawalFeesCache; CachedResult _marketsCache; CachedResult _allOrderBooksCache; CachedResult _orderBookCache; diff --git a/src/api/exchanges/src/bithumbpublicapi.cpp b/src/api/exchanges/src/bithumbpublicapi.cpp index d7e2fe96..3f82bc29 100644 --- a/src/api/exchanges/src/bithumbpublicapi.cpp +++ b/src/api/exchanges/src/bithumbpublicapi.cpp @@ -82,7 +82,7 @@ json PublicQuery(CurlHandle& curlHandle, std::string_view endpoint, CurrencyCode } // namespace BithumbPublic::BithumbPublic(const CoincenterInfo& config, FiatConverter& fiatConverter, CommonAPI& commonAPI) - : ExchangePublic("bithumb", fiatConverter, commonAPI, config), + : ExchangePublic(kExchangeName, fiatConverter, commonAPI, config), _curlHandle(kUrlBase, config.metricGatewayPtr(), PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeConfig().publicAPIRate()) @@ -94,14 +94,6 @@ BithumbPublic::BithumbPublic(const CoincenterInfo& config, FiatConverter& fiatCo _tradableCurrenciesCache( CachedResultOptions(exchangeConfig().getAPICallUpdateFrequency(kCurrencies), _cachedResultVault), config, commonAPI, _curlHandle), - _withdrawalFeesCache( - CachedResultOptions(exchangeConfig().getAPICallUpdateFrequency(kWithdrawalFees), _cachedResultVault), - config.metricGatewayPtr(), - PermanentCurlOptions::Builder() - .setMinDurationBetweenQueries(exchangeConfig().publicAPIRate()) - .setAcceptedEncoding(exchangeConfig().acceptEncoding()) - .build(), - config.getRunMode()), _allOrderBooksCache( CachedResultOptions(exchangeConfig().getAPICallUpdateFrequency(kAllOrderBooks), _cachedResultVault), config, _curlHandle, exchangeConfig()), @@ -137,7 +129,7 @@ MarketSet BithumbPublic::queryTradableMarkets() { } std::optional BithumbPublic::queryWithdrawalFee(CurrencyCode currencyCode) { - const auto& map = _withdrawalFeesCache.get(); + const auto& map = _commonApi.queryWithdrawalFees(kExchangeName).first; auto it = map.find(currencyCode); if (it == map.end()) { return {}; @@ -155,51 +147,6 @@ MonetaryAmount BithumbPublic::queryLastPrice(Market mk) { return *avgPrice; } -MonetaryAmountByCurrencySet BithumbPublic::WithdrawalFeesFunc::operator()() { - vector fees; - // This is not a published API and only a "standard" html page. We will capture the text information in it. - // Warning, it's not in json format so we will need manual parsing. - std::string_view dataStr = _curlHandle.query("/customer_support/info_fee", CurlOptions(HttpRequestType::kGet)); - // Now, we have the big string containing the html data. The following should work as long as format is unchanged. - // here is a line containing our coin with its additional withdrawal fees: - // - //
"; + static constexpr std::string_view kEndHtmlTag = "
Bitcoin(BTC)
0.001
- static constexpr std::string_view kCoinSep = "tr data-coin="; - static constexpr std::string_view kFeeSep = "right out_fee"; - for (std::size_t charPos = dataStr.find(kCoinSep); charPos != string::npos; - charPos = dataStr.find(kCoinSep, charPos)) { - charPos = dataStr.find("money_type tx_c", charPos); - charPos = dataStr.find('(', charPos) + 1; - std::size_t endP = dataStr.find(')', charPos); - CurrencyCode coinAcro(std::string_view(dataStr.begin() + charPos, dataStr.begin() + endP)); - std::size_t nextRightOutFee = dataStr.find(kFeeSep, endP); - std::size_t nextCoinSep = dataStr.find(kCoinSep, endP); - if (nextRightOutFee > nextCoinSep) { - // This means no withdraw fee data, probably 0 ? - fees.emplace_back(0, coinAcro); - continue; - } - charPos = dataStr.find(kFeeSep, endP); - if (charPos == string::npos) { - break; - } - charPos = dataStr.find('>', charPos) + 1; - endP = dataStr.find('<', charPos); - std::string_view withdrawFee(dataStr.begin() + charPos, dataStr.begin() + endP); - MonetaryAmount ma(withdrawFee, coinAcro); - log::debug("Updated Bithumb withdrawal fee {}", ma); - fees.push_back(std::move(ma)); - } - if (fees.empty()) { - log::error("Unable to parse Bithumb withdrawal fees, syntax might have changed"); - } else { - log::info("Updated Bithumb withdrawal fees for {} coins", fees.size()); - } - return MonetaryAmountByCurrencySet(std::move(fees)); -} - CurrencyExchangeFlatSet BithumbPublic::TradableCurrenciesFunc::operator()() { json result = PublicQuery(_curlHandle, "/public/assetsstatus/", "all"); CurrencyExchangeVector currencies; diff --git a/src/api/exchanges/src/krakenpublicapi.cpp b/src/api/exchanges/src/krakenpublicapi.cpp index e1872c5f..f6cd3fef 100644 --- a/src/api/exchanges/src/krakenpublicapi.cpp +++ b/src/api/exchanges/src/krakenpublicapi.cpp @@ -1,7 +1,6 @@ #include "krakenpublicapi.hpp" #include -#include #include #include #include @@ -10,7 +9,6 @@ #include "apiquerytypeenum.hpp" #include "cachedresult.hpp" -#include "cct_cctype.hpp" #include "cct_exception.hpp" #include "cct_json.hpp" #include "cct_log.hpp" @@ -28,7 +26,6 @@ #include "exchangepublicapi.hpp" #include "exchangepublicapitypes.hpp" #include "fiatconverter.hpp" -#include "file.hpp" #include "httprequesttype.hpp" #include "invariant-request-retry.hpp" #include "market.hpp" @@ -97,12 +94,6 @@ bool CheckCurrencyExchange(std::string_view krakenEntryCurrencyCode, std::string return true; } -File GetKrakenWithdrawInfoFile(std::string_view dataDir) { - return {dataDir, File::Type::kCache, "krakenwithdrawinfo.json", File::IfError::kNoThrow}; -} - -constexpr std::string_view kExchangeName = "kraken"; - } // namespace KrakenPublic::KrakenPublic(const CoincenterInfo& config, FiatConverter& fiatConverter, CommonAPI& commonAPI) @@ -118,9 +109,6 @@ KrakenPublic::KrakenPublic(const CoincenterInfo& config, FiatConverter& fiatConv _tradableCurrenciesCache( CachedResultOptions(exchangeConfig().getAPICallUpdateFrequency(kCurrencies), _cachedResultVault), config, commonAPI, _curlHandle, exchangeConfig()), - _withdrawalFeesCache( - CachedResultOptions(exchangeConfig().getAPICallUpdateFrequency(kWithdrawalFees), _cachedResultVault), config, - exchangeConfig().publicAPIRate()), _marketsCache(CachedResultOptions(exchangeConfig().getAPICallUpdateFrequency(kMarkets), _cachedResultVault), _tradableCurrenciesCache, config, _curlHandle, exchangeConfig()), _allOrderBooksCache( @@ -131,31 +119,7 @@ KrakenPublic::KrakenPublic(const CoincenterInfo& config, FiatConverter& fiatConv _tickerCache(CachedResultOptions(std::min(exchangeConfig().getAPICallUpdateFrequency(kTradedVolume), exchangeConfig().getAPICallUpdateFrequency(kLastPrice)), _cachedResultVault), - _tradableCurrenciesCache, _curlHandle) { - json data = GetKrakenWithdrawInfoFile(_coincenterInfo.dataDir()).readAllJson(); - if (!data.empty()) { - Duration withdrawDataRefreshTime = exchangeConfig().getAPICallUpdateFrequency(kWithdrawalFees); - TimePoint lastUpdatedTime(TimeInS(data["timeepoch"].get())); - if (Clock::now() < lastUpdatedTime + withdrawDataRefreshTime) { - // we can reuse file data - KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps withdrawalInfoMaps; - - for (const auto& [curCodeStr, val] : data["assets"].items()) { - CurrencyCode cur(curCodeStr); - MonetaryAmount withdrawMin(val["min"].get(), cur); - MonetaryAmount withdrawFee(val["fee"].get(), cur); - - log::trace("Updated {} withdrawal fee {} from cache", _name, withdrawFee); - log::trace("Updated {} min withdraw {} from cache", _name, withdrawMin); - - withdrawalInfoMaps.first.insert(withdrawFee); - withdrawalInfoMaps.second.insert_or_assign(cur, withdrawMin); - } - - _withdrawalFeesCache.set(std::move(withdrawalInfoMaps), lastUpdatedTime); - } - } -} + _tradableCurrenciesCache, _curlHandle) {} bool KrakenPublic::healthCheck() { json result = json::parse(_curlHandle.query("/public/SystemStatus", CurlOptions(HttpRequestType::kGet))); @@ -180,7 +144,7 @@ bool KrakenPublic::healthCheck() { } std::optional KrakenPublic::queryWithdrawalFee(CurrencyCode currencyCode) { - const MonetaryAmountByCurrencySet& withdrawalFees = _withdrawalFeesCache.get().first; + const MonetaryAmountByCurrencySet& withdrawalFees = _commonApi.queryWithdrawalFees(kExchangeName).first; auto foundIt = withdrawalFees.find(currencyCode); if (foundIt == withdrawalFees.end()) { return {}; @@ -188,177 +152,6 @@ std::optional KrakenPublic::queryWithdrawalFee(CurrencyCode curr return *foundIt; } -namespace { -constexpr std::string_view kUrlWithdrawFee1 = "https://withdrawalfees.com/exchanges/kraken"; -constexpr std::string_view kUrlWithdrawFee2 = "https://www.cryptofeesaver.com/exchanges/fees/kraken"; -} // namespace - -KrakenPublic::WithdrawalFeesFunc::WithdrawalFeesFunc(const CoincenterInfo& coincenterInfo, - Duration minDurationBetweenQueries) - : _curlHandle1(kUrlWithdrawFee1, coincenterInfo.metricGatewayPtr(), - PermanentCurlOptions::Builder() - .setMinDurationBetweenQueries(minDurationBetweenQueries) - .setAcceptedEncoding(coincenterInfo.exchangeConfig(kExchangeName).acceptEncoding()) - .build(), - coincenterInfo.getRunMode()), - _curlHandle2(kUrlWithdrawFee2, coincenterInfo.metricGatewayPtr(), - PermanentCurlOptions::Builder() - .setMinDurationBetweenQueries(minDurationBetweenQueries) - .setAcceptedEncoding(coincenterInfo.exchangeConfig(kExchangeName).acceptEncoding()) - .build(), - coincenterInfo.getRunMode()) {} - -KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps KrakenPublic::WithdrawalFeesFunc::updateFromSource1() { - std::string_view withdrawalFeesCsv = _curlHandle1.query("", CurlOptions(HttpRequestType::kGet)); - - static constexpr std::string_view kBeginWithdrawalFeeHtmlTag = ""; - static constexpr std::string_view kBeginMinWithdrawalHtmlTag = ""; - static constexpr std::string_view kParseError1Msg = - "Parse error from source 1 - either site information unavailable or code to be updated"; - - WithdrawalInfoMaps ret; - - std::size_t searchPos = 0; - while ((searchPos = withdrawalFeesCsv.find(kBeginWithdrawalFeeHtmlTag, searchPos)) != string::npos) { - auto parseNextFee = [&withdrawalFeesCsv](std::size_t& begPos) { - static constexpr std::string_view kBeginFeeHtmlTag = "
"; - static constexpr std::string_view kEndHtmlTag = "
"; - - MonetaryAmount ret; - - begPos = withdrawalFeesCsv.find(kBeginFeeHtmlTag, begPos); - if (begPos == string::npos) { - log::error(kParseError1Msg); - return ret; - } - begPos += kBeginFeeHtmlTag.size(); - // There are sometimes strange characters at beginning of the amount - while (!isdigit(withdrawalFeesCsv[begPos])) { - ++begPos; - } - std::size_t endPos = withdrawalFeesCsv.find(kEndHtmlTag, begPos + 1); - if (endPos == string::npos) { - log::error(kParseError1Msg); - return ret; - } - ret = MonetaryAmount(std::string_view(withdrawalFeesCsv.begin() + begPos, withdrawalFeesCsv.begin() + endPos)); - begPos = endPos + kEndHtmlTag.size(); - return ret; - }; - - // Locate withdrawal fee - searchPos += kBeginWithdrawalFeeHtmlTag.size(); - MonetaryAmount withdrawalFee = parseNextFee(searchPos); - if (withdrawalFee.currencyCode().isNeutral()) { - ret.first.clear(); - break; - } - - log::trace("Updated Kraken withdrawal fee {} from first source", withdrawalFee); - ret.first.insert(withdrawalFee); - - // Locate min withdrawal - searchPos = withdrawalFeesCsv.find(kBeginMinWithdrawalHtmlTag, searchPos) + kBeginMinWithdrawalHtmlTag.size(); - if (searchPos == string::npos) { - log::error(kParseError1Msg); - ret.first.clear(); - break; - } - - MonetaryAmount minWithdrawal = parseNextFee(searchPos); - if (minWithdrawal.currencyCode().isNeutral()) { - ret.first.clear(); - break; - } - - log::trace("Updated Kraken min withdrawal {} from first source", minWithdrawal); - ret.second.insert_or_assign(minWithdrawal.currencyCode(), minWithdrawal); - } - if (ret.first.empty() || ret.second.empty()) { - log::error("Unable to parse Kraken withdrawal fees from first source"); - } else { - log::info("Updated Kraken withdraw infos for {} coins from first source", ret.first.size()); - } - return ret; -} - -KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps KrakenPublic::WithdrawalFeesFunc::updateFromSource2() { - std::string_view withdrawalFeesCsv = _curlHandle2.query("", CurlOptions(HttpRequestType::kGet)); - - static constexpr std::string_view kBeginTableTitle = "Kraken Deposit & Withdrawal fees"; - - std::size_t begPos = withdrawalFeesCsv.find(kBeginTableTitle); - WithdrawalInfoMaps ret; - if (begPos != string::npos) { - static constexpr std::string_view kBeginTable = " MonetaryAmount { - static constexpr std::string_view kBeginFeeHtmlTag = ""; - - // Skip one column - for (int colPos = 0; colPos < 2; ++colPos) { - begPos = withdrawalFeesCsv.find(kBeginFeeHtmlTag, begPos); - if (begPos == string::npos) { - throw exception("Unable to parse Kraken withdrawal fees from source 2: expecting begin HTML tag"); - } - begPos += kBeginFeeHtmlTag.size(); - } - // Scan until next non space char - while (begPos < withdrawalFeesCsv.size() && isspace(withdrawalFeesCsv[begPos])) { - ++begPos; - } - std::size_t endPos = withdrawalFeesCsv.find(kEndHtmlTag, begPos + 1); - if (endPos == string::npos) { - throw exception("Unable to parse Kraken withdrawal fees from source 2: expecting end HTML tag"); - } - std::size_t endHtmlTagPos = endPos; - while (endPos > begPos && isspace(withdrawalFeesCsv[endPos - 1])) { - --endPos; - } - MonetaryAmount ret(std::string_view(withdrawalFeesCsv.begin() + begPos, withdrawalFeesCsv.begin() + endPos)); - begPos = endHtmlTagPos + kEndHtmlTag.size(); - return ret; - }; - - // Locate withdrawal fee - searchPos += kBeginWithdrawalFeeHtmlTag.size(); - MonetaryAmount withdrawalFee = parseNextFee(searchPos); - - log::trace("Updated Kraken withdrawal fee {} from source 2, simulate min withdrawal amount", withdrawalFee); - ret.first.insert(withdrawalFee); - - ret.second.insert_or_assign(withdrawalFee.currencyCode(), 3 * withdrawalFee); - } - } - } - - if (ret.first.empty() || ret.second.empty()) { - log::error("Unable to parse Kraken withdrawal fees from second source"); - } else { - log::info("Updated Kraken withdraw infos for {} coins from second source", ret.first.size()); - } - return ret; -} - -KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps KrakenPublic::WithdrawalFeesFunc::operator()() { - auto [withdrawFees1, withdrawMinMap1] = updateFromSource1(); - auto [withdrawFees2, withdrawMinMap2] = updateFromSource2(); - - withdrawFees1.insert(withdrawFees2.begin(), withdrawFees2.end()); - withdrawMinMap1.merge(std::move(withdrawMinMap2)); - - if (withdrawFees1.empty() || withdrawMinMap1.empty()) { - throw exception("Unable to parse Kraken withdrawal fees"); - } - return std::make_pair(std::move(withdrawFees1), std::move(withdrawMinMap1)); -} - CurrencyExchangeFlatSet KrakenPublic::TradableCurrenciesFunc::operator()() { json result = PublicQuery(_curlHandle, "/public/Assets"); CurrencyExchangeVector currencies; @@ -565,23 +358,4 @@ LastTradesVector KrakenPublic::queryLastTrades(Market mk, int nbLastTrades) { return ret; } -void KrakenPublic::updateCacheFile() const { - const auto [withdrawalInfoMapsPtr, latestUpdate] = _withdrawalFeesCache.retrieve(); - if (withdrawalInfoMapsPtr != nullptr) { - using WithdrawalInfoMaps = KrakenPublic::WithdrawalFeesFunc::WithdrawalInfoMaps; - - const WithdrawalInfoMaps& withdrawalInfoMaps = *withdrawalInfoMapsPtr; - - json data; - data["timeepoch"] = TimestampToS(latestUpdate); - for (const auto withdrawFee : withdrawalInfoMaps.first) { - string curCodeStr = withdrawFee.currencyCode().str(); - data["assets"][curCodeStr]["min"] = - withdrawalInfoMaps.second.find(withdrawFee.currencyCode())->second.amountStr(); - data["assets"][curCodeStr]["fee"] = withdrawFee.amountStr(); - } - GetKrakenWithdrawInfoFile(_coincenterInfo.dataDir()).write(data); - } -} - } // namespace cct::api diff --git a/src/api/exchanges/test/binanceapi_test.cpp b/src/api/exchanges/test/binanceapi_test.cpp index 7a5cdecc..3c590ec5 100644 --- a/src/api/exchanges/test/binanceapi_test.cpp +++ b/src/api/exchanges/test/binanceapi_test.cpp @@ -1,6 +1,6 @@ #include "binanceprivateapi.hpp" #include "binancepublicapi.hpp" -#include "commonapi_test.hpp" +#include "exchangecommonapi_test.hpp" namespace cct::api { diff --git a/src/api/exchanges/test/bithumbapi_test.cpp b/src/api/exchanges/test/bithumbapi_test.cpp index 352097ee..de55601a 100644 --- a/src/api/exchanges/test/bithumbapi_test.cpp +++ b/src/api/exchanges/test/bithumbapi_test.cpp @@ -1,6 +1,6 @@ #include "bithumbprivateapi.hpp" #include "bithumbpublicapi.hpp" -#include "commonapi_test.hpp" +#include "exchangecommonapi_test.hpp" namespace cct::api { diff --git a/src/api/exchanges/test/commonapi_test.hpp b/src/api/exchanges/test/exchangecommonapi_test.hpp similarity index 100% rename from src/api/exchanges/test/commonapi_test.hpp rename to src/api/exchanges/test/exchangecommonapi_test.hpp diff --git a/src/api/exchanges/test/huobiapi_test.cpp b/src/api/exchanges/test/huobiapi_test.cpp index 10fe206d..9762061c 100644 --- a/src/api/exchanges/test/huobiapi_test.cpp +++ b/src/api/exchanges/test/huobiapi_test.cpp @@ -1,4 +1,4 @@ -#include "commonapi_test.hpp" +#include "exchangecommonapi_test.hpp" #include "huobiprivateapi.hpp" #include "huobipublicapi.hpp" diff --git a/src/api/exchanges/test/krakenapi_test.cpp b/src/api/exchanges/test/krakenapi_test.cpp index da120383..66da9544 100644 --- a/src/api/exchanges/test/krakenapi_test.cpp +++ b/src/api/exchanges/test/krakenapi_test.cpp @@ -1,4 +1,4 @@ -#include "commonapi_test.hpp" +#include "exchangecommonapi_test.hpp" #include "krakenprivateapi.hpp" #include "krakenpublicapi.hpp" diff --git a/src/api/exchanges/test/kucoinapi_test.cpp b/src/api/exchanges/test/kucoinapi_test.cpp index 754f82a1..d5bac608 100644 --- a/src/api/exchanges/test/kucoinapi_test.cpp +++ b/src/api/exchanges/test/kucoinapi_test.cpp @@ -1,4 +1,4 @@ -#include "commonapi_test.hpp" +#include "exchangecommonapi_test.hpp" #include "kucoinprivateapi.hpp" #include "kucoinpublicapi.hpp" diff --git a/src/api/exchanges/test/upbitapi_test.cpp b/src/api/exchanges/test/upbitapi_test.cpp index b309215b..b3eef562 100644 --- a/src/api/exchanges/test/upbitapi_test.cpp +++ b/src/api/exchanges/test/upbitapi_test.cpp @@ -1,4 +1,4 @@ -#include "commonapi_test.hpp" +#include "exchangecommonapi_test.hpp" #include "upbitprivateapi.hpp" #include "upbitpublicapi.hpp"
"; - static constexpr std::string_view kEndHtmlTag = "