From a7136ad915e8b324f07553815865428cb64141ae Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Sun, 17 Mar 2024 23:45:49 +0100 Subject: [PATCH] Do not select Exchange without account with private requests on empty list of exchanges --- cmake/AddUnitTest.cmake | 5 +- src/api/common/CMakeLists.txt | 2 + .../include/exchangeprivateapi_mock.hpp | 0 .../include/exchangepublicapi_mock.hpp | 0 src/api/interface/CMakeLists.txt | 11 +- src/api/interface/include/exchange.hpp | 20 +- .../interface/include/exchangeretriever.hpp | 166 +++++++++++++- .../include/exchangeretrieverbase.hpp | 153 ------------- .../interface/test/exchangeretriever_test.cpp | 215 ++++++++++++++++++ .../test/exchangeretrieverbase_test.cpp | 117 ---------- src/engine/CMakeLists.txt | 10 + src/engine/src/exchangesorchestrator.cpp | 33 ++- 12 files changed, 426 insertions(+), 306 deletions(-) rename src/api/common/{ => test}/include/exchangeprivateapi_mock.hpp (100%) rename src/api/common/{ => test}/include/exchangepublicapi_mock.hpp (100%) delete mode 100644 src/api/interface/include/exchangeretrieverbase.hpp create mode 100644 src/api/interface/test/exchangeretriever_test.cpp delete mode 100644 src/api/interface/test/exchangeretrieverbase_test.cpp diff --git a/cmake/AddUnitTest.cmake b/cmake/AddUnitTest.cmake index cbfaa5e8..339f651c 100644 --- a/cmake/AddUnitTest.cmake +++ b/cmake/AddUnitTest.cmake @@ -51,6 +51,7 @@ function(add_exe name) set(exe_sources "") set(exe_libraries "") set(exe_definitions "") + set(exe_directories "") set(exe_include_dirs "") foreach(arg IN LISTS ARGN) @@ -58,6 +59,8 @@ function(add_exe name) set(cur_var "libraries") elseif(arg STREQUAL "DEFINITIONS") set(cur_var "definitions") + elseif(arg STREQUAL "DIRECTORIES") + set(cur_var "directories") else() list(APPEND exe_${cur_var} ${arg}) @@ -74,5 +77,5 @@ function(add_exe name) BUILD_RPATH "${runtime_path}") target_link_libraries(${name} PRIVATE ${exe_libraries}) list(REMOVE_DUPLICATES exe_include_dirs) - target_include_directories(${name} PRIVATE ${exe_include_dirs} ${all_includes}) + target_include_directories(${name} PRIVATE ${exe_include_dirs} ${exe_directories}) endfunction() \ No newline at end of file diff --git a/src/api/common/CMakeLists.txt b/src/api/common/CMakeLists.txt index 637ca353..9464b950 100644 --- a/src/api/common/CMakeLists.txt +++ b/src/api/common/CMakeLists.txt @@ -21,6 +21,8 @@ function(add_common_test name) ${MY_UNPARSED_ARGUMENTS} LIBRARIES coincenter_api-common + DIRECTORIES + test/include ) endfunction() diff --git a/src/api/common/include/exchangeprivateapi_mock.hpp b/src/api/common/test/include/exchangeprivateapi_mock.hpp similarity index 100% rename from src/api/common/include/exchangeprivateapi_mock.hpp rename to src/api/common/test/include/exchangeprivateapi_mock.hpp diff --git a/src/api/common/include/exchangepublicapi_mock.hpp b/src/api/common/test/include/exchangepublicapi_mock.hpp similarity index 100% rename from src/api/common/include/exchangepublicapi_mock.hpp rename to src/api/common/test/include/exchangepublicapi_mock.hpp diff --git a/src/api/interface/CMakeLists.txt b/src/api/interface/CMakeLists.txt index a17d1968..fd5fdac0 100644 --- a/src/api/interface/CMakeLists.txt +++ b/src/api/interface/CMakeLists.txt @@ -1,4 +1,3 @@ - aux_source_directory(src API_INTERFACE_SRC) add_library(coincenter_api-interface STATIC ${API_INTERFACE_SRC}) @@ -8,10 +7,10 @@ target_link_libraries(coincenter_api-interface PRIVATE coincenter_monitoring) target_include_directories(coincenter_api-interface PUBLIC include) add_unit_test( - exchangeretrieverbase_test - test/exchangeretrieverbase_test.cpp - DEFINITIONS - CCT_DISABLE_SPDLOG + exchangeretriever_test + test/exchangeretriever_test.cpp LIBRARIES - coincenter_objects + coincenter_api-interface + DIRECTORIES + ../common/test/include ) \ No newline at end of file diff --git a/src/api/interface/include/exchange.hpp b/src/api/interface/include/exchange.hpp index c150a9b4..51306bbe 100644 --- a/src/api/interface/include/exchange.hpp +++ b/src/api/interface/include/exchange.hpp @@ -19,16 +19,17 @@ #include "public-trade-vector.hpp" namespace cct { + class Exchange { public: using ExchangePublic = api::ExchangePublic; + using ExchangePrivate = api::ExchangePrivate; /// Builds a Exchange without private exchange. All private requests will be forbidden. - Exchange(const ExchangeConfig &exchangeConfig, api::ExchangePublic &exchangePublic); + Exchange(const ExchangeConfig &exchangeConfig, ExchangePublic &exchangePublic); /// Build a Exchange with both private and public exchanges - Exchange(const ExchangeConfig &exchangeConfig, api::ExchangePublic &exchangePublic, - api::ExchangePrivate &exchangePrivate); + Exchange(const ExchangeConfig &exchangeConfig, ExchangePublic &exchangePublic, ExchangePrivate &exchangePrivate); std::string_view name() const { return _exchangePublic.name(); } std::string_view keyName() const { return apiPrivate().keyName(); } @@ -40,17 +41,17 @@ class Exchange { return ExchangeName(name()); } - api::ExchangePublic &apiPublic() { return _exchangePublic; } - const api::ExchangePublic &apiPublic() const { return _exchangePublic; } + ExchangePublic &apiPublic() { return _exchangePublic; } + const ExchangePublic &apiPublic() const { return _exchangePublic; } - api::ExchangePrivate &apiPrivate() { + ExchangePrivate &apiPrivate() { if (hasPrivateAPI()) { return *_pExchangePrivate; } throw exception("No private key associated to exchange {}", name()); } - const api::ExchangePrivate &apiPrivate() const { + const ExchangePrivate &apiPrivate() const { if (hasPrivateAPI()) { return *_pExchangePrivate; } @@ -109,8 +110,9 @@ class Exchange { void updateCacheFile() const; private: - api::ExchangePublic &_exchangePublic; - api::ExchangePrivate *_pExchangePrivate = nullptr; + ExchangePublic &_exchangePublic; + ExchangePrivate *_pExchangePrivate = nullptr; const ExchangeConfig &_exchangeConfig; }; + } // namespace cct diff --git a/src/api/interface/include/exchangeretriever.hpp b/src/api/interface/include/exchangeretriever.hpp index c3ee5dc3..fc5e850a 100644 --- a/src/api/interface/include/exchangeretriever.hpp +++ b/src/api/interface/include/exchangeretriever.hpp @@ -1,9 +1,169 @@ #pragma once +#include +#include +#include +#include +#include +#include + +#include "cct_const.hpp" +#include "cct_exception.hpp" +#include "cct_fixedcapacityvector.hpp" +#include "cct_smallvector.hpp" #include "exchange.hpp" -#include "exchangeretrieverbase.hpp" +#include "exchangename.hpp" +#include "exchangepublicapi.hpp" namespace cct { -using ExchangeRetriever = ExchangeRetrieverBase; -using ConstExchangeRetriever = ExchangeRetrieverBase; + +class ExchangeRetriever { + public: + using SelectedExchanges = SmallVector; + using UniquePublicSelectedExchanges = FixedCapacityVector; + using PublicExchangesVec = FixedCapacityVector; + + enum class Order : int8_t { kInitial, kSelection }; + enum class Filter : int8_t { kNone, kWithAccountWhenEmpty }; + + ExchangeRetriever() noexcept = default; + + explicit ExchangeRetriever(std::span exchanges) : _exchanges(exchanges) {} + + std::span exchanges() const { return _exchanges; } + + /// Retrieve the unique Exchange corresponding to given exchange name. + /// Raise exception in case of ambiguity + Exchange &retrieveUniqueCandidate(const ExchangeName &exchangeName) const { + Exchange *pExchange = nullptr; + for (Exchange &exchange : _exchanges) { + if (exchange.matches(exchangeName)) { + if (pExchange != nullptr) { + throw exception("Several private exchanges found for {} - remove ambiguity by specifying key name", + exchangeName.str()); + } + pExchange = std::addressof(exchange); + } + } + if (pExchange == nullptr) { + throw exception("Cannot find exchange {:ek}", exchangeName); + } + return *pExchange; + } + + private: + template + SelectedExchanges select(Order order, const Names &names, Matcher matcher, Filter filter) const { + SelectedExchanges ret; + if (names.empty()) { + auto exchangeAddress = [](Exchange &exchange) { return std::addressof(exchange); }; + switch (filter) { + case Filter::kNone: + ret.resize(static_cast(_exchanges.size())); + std::ranges::transform(_exchanges, ret.begin(), exchangeAddress); + break; + case Filter::kWithAccountWhenEmpty: + std::ranges::transform( + _exchanges | std::views::filter([](Exchange &exchange) { return exchange.hasPrivateAPI(); }), + std::back_inserter(ret), exchangeAddress); + break; + default: + throw exception("Unknown filter"); + } + } else { + switch (order) { + case Order::kInitial: + for (Exchange &exchange : _exchanges) { + if (std::ranges::any_of(names, [&exchange, &matcher](const auto &n) { return matcher(exchange, n); })) { + ret.push_back(std::addressof(exchange)); + } + } + break; + case Order::kSelection: + for (const auto &name : names) { + auto nameMatch = [&name, &matcher](Exchange &exchange) { return matcher(exchange, name); }; + auto endIt = _exchanges.end(); + auto oldSize = ret.size(); + for (auto foundIt = std::ranges::find_if(_exchanges, nameMatch); foundIt != _exchanges.end(); + foundIt = std::find_if(std::next(foundIt), endIt, nameMatch)) { + ret.push_back(std::addressof(*foundIt)); + } + if (ret.size() == oldSize) { + throw exception("Unable to find {} in the exchange list", name); + } + } + break; + default: + throw exception("Unknown Order"); + } + } + + return ret; + } + + template + struct Matcher { + static_assert(std::is_same_v || std::is_same_v); + + bool operator()(const Exchange &e, const NameType &n) const { + if constexpr (std::is_same_v) { + return e.name() == n; + } else { + return e.matches(n); + } + } + }; + + template + using NameType = std::remove_cvref_t().begin())>; + + public: + /// Retrieve all selected exchange addresses matching given public names, or all if empty span is given. + /// Returned exchanges order can be controlled thanks to 'order' parameter: + /// - 'kInitial' : matching 'Exchanges' are returned according to their initial order (at creation of + /// 'this' object) + /// - 'kSelection' : matching 'Exchanges' are returned according to given 'exchangeNames' order, + /// or initial order if empty + /// filter can be set to kWithAccountWhenEmpty so that when exchange names is empty, only Exchange with accounts are + /// returned + template + SelectedExchanges select(Order order, const Names &exchangeNames, Filter filter = Filter::kNone) const { + return select(order, exchangeNames, Matcher>(), filter); + } + + /// Among all 'Exchange's, retrieve at most one 'Exchange' per public exchange matching public exchange names. + /// Order of 'Exchange's will respect the same order as the 'exchangeNames' given in input. + /// Examples + /// {"kraken_user1", "kucoin_user1"} -> {"kraken_user1", "kucoin_user1"} + /// {"kraken_user1", "kraken_user2", "kucoin_user1"} -> {"kraken_user1", "kucoin_user1"} + /// {"huobi", "kucoin_user1"} -> {"huobi_user1", "kucoin_user1"} + template + UniquePublicSelectedExchanges selectOneAccount(const Names &exchangeNames, Filter filter = Filter::kNone) const { + SelectedExchanges selectedExchanges = select(Order::kSelection, exchangeNames, Matcher>(), filter); + UniquePublicSelectedExchanges ret; + std::ranges::copy_if(selectedExchanges, std::back_inserter(ret), [&ret](Exchange *lhs) { + return std::ranges::none_of(ret, [lhs](Exchange *rhs) { return lhs->name() == rhs->name(); }); + }); + return ret; + } + + /// Extract the 'ExchangePublic' from the 'Exchange' corresponding to given 'ExchangeNames'. + /// Order of public exchanges will respect the same order as the 'exchangeNames' given in input. + /// Examples + /// {"kraken_user1", "kucoin_user1"} -> {"kraken", "kucoin"} + /// {"kraken_user1", "kraken_user2", "kucoin_user1"} -> {"kraken", "kucoin"} + /// {"huobi", "kucoin_user1"} -> {"huobi", "kucoin"} + template + PublicExchangesVec selectPublicExchanges(const Names &exchangeNames) const { + auto selectedExchanges = selectOneAccount(exchangeNames); + PublicExchangesVec selectedPublicExchanges(selectedExchanges.size()); + std::ranges::transform(selectedExchanges, selectedPublicExchanges.begin(), + [](Exchange *exchange) { return std::addressof(exchange->apiPublic()); }); + return selectedPublicExchanges; + } + + private: + std::span _exchanges; +}; + } // namespace cct \ No newline at end of file diff --git a/src/api/interface/include/exchangeretrieverbase.hpp b/src/api/interface/include/exchangeretrieverbase.hpp deleted file mode 100644 index 35d16515..00000000 --- a/src/api/interface/include/exchangeretrieverbase.hpp +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "cct_const.hpp" -#include "cct_exception.hpp" -#include "cct_fixedcapacityvector.hpp" -#include "cct_smallvector.hpp" -#include "exchangename.hpp" - -namespace cct { -template -class ExchangeRetrieverBase { - public: - using SelectedExchanges = SmallVector; - using UniquePublicSelectedExchanges = FixedCapacityVector; - using ExchangePublicT = - std::conditional_t, std::add_const_t, - typename ExchangeT::ExchangePublic>; - using PublicExchangesVec = FixedCapacityVector; - - ExchangeRetrieverBase() noexcept = default; - - explicit ExchangeRetrieverBase(std::span exchanges) : _exchanges(exchanges) {} - - std::span exchanges() const { return _exchanges; } - - /// Retrieve the unique Exchange corresponding to given exchange name. - /// Raise exception in case of ambiguity - ExchangeT &retrieveUniqueCandidate(const ExchangeName &exchangeName) const { - ExchangeT *pExchange = nullptr; - for (ExchangeT &exchange : _exchanges) { - if (exchange.matches(exchangeName)) { - if (pExchange) { - throw exception("Several private exchanges found for {} - remove ambiguity by specifying key name", - exchangeName.str()); - } - pExchange = std::addressof(exchange); - } - } - if (!pExchange) { - throw exception("Cannot find exchange {:ek}", exchangeName); - } - return *pExchange; - } - - enum class Order { kInitial, kSelection }; - - private: - template - SelectedExchanges select(Order order, const Names &names, Matcher matcher) const { - SelectedExchanges ret; - if (names.empty()) { - ret.resize(static_cast(_exchanges.size())); - std::ranges::transform(_exchanges, ret.begin(), [](ExchangeT &e) { return &e; }); - } else { - switch (order) { - case Order::kInitial: - for (ExchangeT &e : _exchanges) { - if (std::ranges::any_of(names, [&e, &matcher](const auto &n) { return matcher(e, n); })) { - ret.push_back(std::addressof(e)); - } - } - break; - case Order::kSelection: - for (const auto &n : names) { - auto nameMatch = [&n, &matcher](ExchangeT &e) { return matcher(e, n); }; - auto endIt = _exchanges.end(); - auto oldSize = ret.size(); - for (auto foundIt = std::ranges::find_if(_exchanges, nameMatch); foundIt != _exchanges.end(); - foundIt = std::find_if(std::next(foundIt), endIt, nameMatch)) { - ret.push_back(std::addressof(*foundIt)); - } - if (ret.size() == oldSize) { - throw exception("Unable to find {} in the exchange list", n); - } - } - break; - default: - throw exception("Unknown Order"); - } - } - - return ret; - } - - template - struct Matcher { - static_assert(std::is_same_v || std::is_same_v); - - bool operator()(const ExchangeT &e, const NameType &n) const { - if constexpr (std::is_same_v) { - return e.name() == n; - } else { - return e.matches(n); - } - } - }; - - template - using NameType = std::remove_cvref_t().begin())>; - - public: - /// Retrieve all selected exchange addresses matching given public names, or all if empty span is given. - /// Returned exchanges order can be controlled thanks to 'order' parameter: - /// - 'kInitial' : matching 'Exchanges' are returned according to their initial order (at creation of - /// 'this' object) - /// - 'kSelection' : matching 'Exchanges' are returned according to given 'exchangeNames' order, - /// or initial order if empty - template - SelectedExchanges select(Order order, const Names &exchangeNames) const { - return select(order, exchangeNames, Matcher>()); - } - - /// Among all 'Exchange's, retrieve at most one 'Exchange' per public exchange matching public exchange names. - /// Order of 'Exchange's will respect the same order as the 'exchangeNames' given in input. - /// Examples - /// {"kraken_user1", "kucoin_user1"} -> {"kraken_user1", "kucoin_user1"} - /// {"kraken_user1", "kraken_user2", "kucoin_user1"} -> {"kraken_user1", "kucoin_user1"} - /// {"huobi", "kucoin_user1"} -> {"huobi_user1", "kucoin_user1"} - template - UniquePublicSelectedExchanges selectOneAccount(const Names &exchangeNames) const { - SelectedExchanges selectedExchanges = select(Order::kSelection, exchangeNames, Matcher>()); - UniquePublicSelectedExchanges ret; - std::ranges::copy_if(selectedExchanges, std::back_inserter(ret), [&ret](ExchangeT *e) { - return std::ranges::none_of(ret, [e](ExchangeT *o) { return o->name() == e->name(); }); - }); - return ret; - } - - /// Extract the 'ExchangePublic' from the 'Exchange' corresponding to given 'ExchangeNames'. - /// Order of public exchanges will respect the same order as the 'exchangeNames' given in input. - /// Examples - /// {"kraken_user1", "kucoin_user1"} -> {"kraken", "kucoin"} - /// {"kraken_user1", "kraken_user2", "kucoin_user1"} -> {"kraken", "kucoin"} - /// {"huobi", "kucoin_user1"} -> {"huobi", "kucoin"} - template - PublicExchangesVec selectPublicExchanges(const Names &exchangeNames) const { - auto selectedExchanges = selectOneAccount(exchangeNames); - PublicExchangesVec selectedPublicExchanges(selectedExchanges.size()); - std::ranges::transform(selectedExchanges, selectedPublicExchanges.begin(), - [](ExchangeT *e) { return std::addressof(e->apiPublic()); }); - return selectedPublicExchanges; - } - - private: - std::span _exchanges; -}; -} // namespace cct \ No newline at end of file diff --git a/src/api/interface/test/exchangeretriever_test.cpp b/src/api/interface/test/exchangeretriever_test.cpp new file mode 100644 index 00000000..3411f956 --- /dev/null +++ b/src/api/interface/test/exchangeretriever_test.cpp @@ -0,0 +1,215 @@ +#include "exchangeretriever.hpp" + +#include + +#include +#include +#include + +#include "cct_exception.hpp" +#include "cct_string.hpp" +#include "coincenterinfo.hpp" +#include "commonapi.hpp" +#include "exchange.hpp" +#include "exchangeconfigmap.hpp" +#include "exchangeconfigparser.hpp" +#include "exchangename.hpp" +#include "exchangeprivateapi_mock.hpp" +#include "exchangepublicapi_mock.hpp" +#include "fiatconverter.hpp" +#include "loadconfiguration.hpp" +#include "reader.hpp" +#include "timedef.hpp" + +namespace cct { + +class ExchangeRetrieverTest : public ::testing::Test { + protected: + LoadConfiguration loadConfiguration{kDefaultDataDir, LoadConfiguration::ExchangeConfigFileType::kTest}; + ExchangeConfigMap exchangeConfigMap{ + ComputeExchangeConfigMap(loadConfiguration.exchangeConfigFileName(), LoadExchangeConfigData(loadConfiguration))}; + + CoincenterInfo coincenterInfo{settings::RunMode::kTestKeys, loadConfiguration}; + api::CommonAPI commonAPI{coincenterInfo, Duration::max()}; + FiatConverter fiatConverter{coincenterInfo, Duration::max(), Reader()}; // max to avoid real Fiat converter queries + + api::MockExchangePublic exchangePublic1{"bithumb", fiatConverter, commonAPI, coincenterInfo}; + api::MockExchangePublic exchangePublic2{"kraken", fiatConverter, commonAPI, coincenterInfo}; + api::MockExchangePublic exchangePublic3{"kucoin", fiatConverter, commonAPI, coincenterInfo}; + api::APIKey key1{"test1", "user1", "", "", ""}; + api::APIKey key2{"test2", "user2", "", "", ""}; + api::APIKey key3{"test3", "user3", "", "", ""}; + api::APIKey key4{"test4", "user4", "", "", ""}; + api::APIKey key5{"test5", "user5", "", "", ""}; + api::MockExchangePrivate exchangePrivate1{exchangePublic1, coincenterInfo, key1}; + api::MockExchangePrivate exchangePrivate2{exchangePublic2, coincenterInfo, key1}; + api::MockExchangePrivate exchangePrivate3{exchangePublic3, coincenterInfo, key1}; + api::MockExchangePrivate exchangePrivate4{exchangePublic3, coincenterInfo, key2}; + api::MockExchangePrivate exchangePrivate5{exchangePublic3, coincenterInfo, key3}; + api::MockExchangePrivate exchangePrivate6{exchangePublic3, coincenterInfo, key4}; + api::MockExchangePrivate exchangePrivate7{exchangePublic3, coincenterInfo, key5}; + api::MockExchangePrivate exchangePrivate8{exchangePublic1, coincenterInfo, key2}; + Exchange exchange1{coincenterInfo.exchangeConfig(exchangePublic1.name()), exchangePublic1, exchangePrivate1}; + Exchange exchange2{coincenterInfo.exchangeConfig(exchangePublic2.name()), exchangePublic2, exchangePrivate2}; + Exchange exchange3{coincenterInfo.exchangeConfig(exchangePublic3.name()), exchangePublic3, exchangePrivate3}; + Exchange exchange4{coincenterInfo.exchangeConfig(exchangePublic3.name()), exchangePublic3, exchangePrivate4}; + Exchange exchange5{coincenterInfo.exchangeConfig(exchangePublic3.name()), exchangePublic3, exchangePrivate5}; + Exchange exchange6{coincenterInfo.exchangeConfig(exchangePublic3.name()), exchangePublic3, exchangePrivate6}; + Exchange exchange7{coincenterInfo.exchangeConfig(exchangePublic3.name()), exchangePublic3}; + Exchange exchange8{coincenterInfo.exchangeConfig(exchangePublic1.name()), exchangePublic1, exchangePrivate8}; +}; + +TEST_F(ExchangeRetrieverTest, Empty) { + EXPECT_TRUE(ExchangeRetriever().exchanges().empty()); + EXPECT_TRUE(ExchangeRetriever().select(ExchangeRetriever::Order::kInitial, ExchangeNames{}).empty()); +} + +TEST_F(ExchangeRetrieverTest, EmptySelection) { + Exchange kAllExchanges[] = {exchange1, exchange2, exchange7, exchange8}; + + ExchangeRetriever exchangeRetriever(kAllExchanges); + + EXPECT_FALSE(exchangeRetriever.exchanges().empty()); + + ExchangeRetriever::SelectedExchanges expected; + std::ranges::transform(kAllExchanges, std::back_inserter(expected), + [](auto &exchange) { return std::addressof(exchange); }); + + EXPECT_EQ(exchangeRetriever.select(ExchangeRetriever::Order::kInitial, ExchangeNames{}), expected); + EXPECT_EQ(exchangeRetriever.select(ExchangeRetriever::Order::kSelection, ExchangeNames{}), expected); +} + +TEST_F(ExchangeRetrieverTest, RetrieveUniqueCandidate) { + Exchange kAllExchanges[] = {exchange1, exchange2, exchange7, exchange8}; + + ExchangeRetriever exchangeRetriever(kAllExchanges); + + EXPECT_THROW(exchangeRetriever.retrieveUniqueCandidate(ExchangeName("bithumb")), exception); + + auto &bithumbUser1 = exchangeRetriever.retrieveUniqueCandidate(ExchangeName("bithumb_user1")); + + EXPECT_EQ(bithumbUser1.name(), "bithumb"); + EXPECT_EQ(bithumbUser1.keyName(), "user1"); + + auto &krakenUser1 = exchangeRetriever.retrieveUniqueCandidate(ExchangeName("kraken")); + + EXPECT_EQ(krakenUser1.name(), "kraken"); + EXPECT_EQ(krakenUser1.keyName(), "user1"); +} + +TEST_F(ExchangeRetrieverTest, RetrieveSelectedExchangesInitialOrder) { + Exchange kAllExchanges[] = {exchange1, exchange2, exchange7, exchange8}; + + ExchangeRetriever exchangeRetriever(kAllExchanges); + + EXPECT_FALSE(exchangeRetriever.exchanges().empty()); + + ExchangeName exchangeName("bithumb"); + ExchangeNames names{exchangeName}; + ExchangeRetriever::SelectedExchanges selectedExchanges = + exchangeRetriever.select(ExchangeRetriever::Order::kInitial, names); + + ASSERT_EQ(selectedExchanges.size(), 2U); + EXPECT_EQ(selectedExchanges.front()->name(), "bithumb"); + EXPECT_EQ(selectedExchanges.back()->name(), "bithumb"); + + selectedExchanges = exchangeRetriever.select(ExchangeRetriever::Order::kInitial, ExchangeNames{}); + + ASSERT_EQ(selectedExchanges.size(), 4U); + EXPECT_EQ(selectedExchanges[0]->name(), "bithumb"); + EXPECT_EQ(selectedExchanges[1]->name(), "kraken"); + EXPECT_EQ(selectedExchanges[2]->name(), "kucoin"); + EXPECT_EQ(selectedExchanges[3]->name(), "bithumb"); +} + +TEST_F(ExchangeRetrieverTest, RetrieveSelectedExchangesFilterWhenAccountNotPresent) { + Exchange kAllExchanges[] = {exchange1, exchange2, exchange7, exchange8}; + + ExchangeRetriever exchangeRetriever(kAllExchanges); + + ExchangeRetriever::SelectedExchanges selectedExchanges = exchangeRetriever.select( + ExchangeRetriever::Order::kInitial, ExchangeNames{}, ExchangeRetriever::Filter::kWithAccountWhenEmpty); + + ASSERT_EQ(selectedExchanges.size(), 3U); + EXPECT_EQ(selectedExchanges[0]->name(), "bithumb"); + EXPECT_EQ(selectedExchanges[1]->name(), "kraken"); + EXPECT_EQ(selectedExchanges[2]->name(), "bithumb"); + + selectedExchanges = + exchangeRetriever.select(ExchangeRetriever::Order::kInitial, ExchangeNames{ExchangeName{"kraken"}}, + ExchangeRetriever::Filter::kWithAccountWhenEmpty); + + ASSERT_EQ(selectedExchanges.size(), 1U); + EXPECT_EQ(selectedExchanges[0]->name(), "kraken"); + + // should be returned anyway when asked explicitly + selectedExchanges = + exchangeRetriever.select(ExchangeRetriever::Order::kInitial, ExchangeNames{ExchangeName{"kucoin"}}, + ExchangeRetriever::Filter::kWithAccountWhenEmpty); + + ASSERT_EQ(selectedExchanges.size(), 1U); + EXPECT_EQ(selectedExchanges[0]->name(), "kucoin"); +} + +TEST_F(ExchangeRetrieverTest, RetrieveSelectedExchangesSelectedOrder) { + Exchange kAllExchanges1[] = {exchange1, exchange2, exchange8}; + Exchange kAllExchanges2[] = {exchange8, exchange1, exchange2}; + + std::span exchangesSpan1 = kAllExchanges1; + std::span exchangesSpan2 = kAllExchanges2; + + for (auto exchangesSpan : {exchangesSpan1, exchangesSpan2}) { + ExchangeRetriever exchangeRetriever(exchangesSpan); + ExchangeNames names{ExchangeName("kraken"), ExchangeName("bithumb")}; + ExchangeRetriever::SelectedExchanges selectedExchanges = + exchangeRetriever.select(ExchangeRetriever::Order::kSelection, names); + ASSERT_EQ(selectedExchanges.size(), 3U); + EXPECT_EQ(selectedExchanges[0]->name(), "kraken"); + EXPECT_EQ(selectedExchanges[1]->name(), "bithumb"); + EXPECT_EQ(selectedExchanges[2]->name(), "bithumb"); + } +} + +TEST_F(ExchangeRetrieverTest, RetrieveAtMostOneAccountSelectedExchanges) { + Exchange kAllExchanges1[] = {exchange1, exchange2, exchange8}; + Exchange kAllExchanges2[] = {exchange8, exchange1, exchange2}; + + std::span exchangesSpan1 = kAllExchanges1; + std::span exchangesSpan2 = kAllExchanges2; + + for (auto exchangesSpan : {exchangesSpan1, exchangesSpan2}) { + ExchangeRetriever exchangeRetriever(exchangesSpan); + + ExchangeNames names{ExchangeName("kraken"), ExchangeName("bithumb")}; + ExchangeRetriever::UniquePublicSelectedExchanges selectedExchanges = exchangeRetriever.selectOneAccount(names); + ExchangeRetriever::UniquePublicSelectedExchanges exchangesInitialOrder = + exchangeRetriever.selectOneAccount(ExchangeNames{}); + + ASSERT_EQ(selectedExchanges.size(), 2U); + EXPECT_EQ(selectedExchanges.front()->name(), "kraken"); + EXPECT_EQ(selectedExchanges.back()->name(), "bithumb"); + + ASSERT_EQ(exchangesInitialOrder.size(), 2U); + EXPECT_EQ(exchangesInitialOrder.front()->name(), "bithumb"); + EXPECT_EQ(exchangesInitialOrder.back()->name(), "kraken"); + } +} + +TEST_F(ExchangeRetrieverTest, RetrieveUniquePublicExchange) { + Exchange kAllExchanges1[] = {exchange1, exchange2, exchange8}; + Exchange kAllExchanges2[] = {exchange8, exchange1, exchange2}; + + std::span exchangesSpan1 = kAllExchanges1; + std::span exchangesSpan2 = kAllExchanges2; + + for (auto exchangesSpan : {exchangesSpan1, exchangesSpan2}) { + ExchangeRetriever exchangeRetriever(exchangesSpan); + ExchangeNames names{ExchangeName("kraken"), ExchangeName("bithumb")}; + ExchangeRetriever::PublicExchangesVec selectedExchanges = exchangeRetriever.selectPublicExchanges(names); + + ASSERT_EQ(selectedExchanges.size(), 2U); + EXPECT_EQ(selectedExchanges.front()->name(), "kraken"); + EXPECT_EQ(selectedExchanges.back()->name(), "bithumb"); + } +} +} // namespace cct \ No newline at end of file diff --git a/src/api/interface/test/exchangeretrieverbase_test.cpp b/src/api/interface/test/exchangeretrieverbase_test.cpp deleted file mode 100644 index ed784518..00000000 --- a/src/api/interface/test/exchangeretrieverbase_test.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "exchangeretrieverbase.hpp" - -#include - -#include -#include - -#include "cct_exception.hpp" -#include "cct_string.hpp" -#include "exchangename.hpp" - -namespace cct { - -class ExchangeTest { - public: - using ExchangePublic = ExchangeTest; - - ExchangeTest(std::string_view name, std::string_view keyName) : _name(name), _keyName(keyName) {} - - [[nodiscard]] std::string_view name() const { return _name; } - [[nodiscard]] std::string_view keyName() const { return _keyName; } - - ExchangePublic &apiPublic() { return *this; } - [[nodiscard]] const ExchangePublic &apiPublic() const { return *this; } - - [[nodiscard]] bool matches(const ExchangeName &exchangeName) const { - return exchangeName.name() == _name && (!exchangeName.isKeyNameDefined() || exchangeName.keyName() == _keyName); - } - - private: - std::string_view _name; - std::string_view _keyName; -}; - -using ExchangeRetriever = ExchangeRetrieverBase; -using Order = ExchangeRetriever::Order; - -TEST(ExchangeRetriever, Empty) { - EXPECT_TRUE(ExchangeRetriever().exchanges().empty()); - ExchangeNames names; - EXPECT_TRUE(ExchangeRetriever().select(Order::kInitial, names).empty()); -} - -TEST(ExchangeRetriever, RetrieveUniqueCandidate) { - const ExchangeTest kAllExchanges[] = {ExchangeTest("bithumb", "user1"), ExchangeTest("kraken", "user3"), - ExchangeTest("bithumb", "user2")}; - ExchangeRetriever exchangeRetriever(kAllExchanges); - // ambiguity, should throw exception - EXPECT_THROW(exchangeRetriever.retrieveUniqueCandidate(ExchangeName("bithumb")), exception); - const ExchangeTest &bithumbUser1 = exchangeRetriever.retrieveUniqueCandidate(ExchangeName("bithumb_user1")); - EXPECT_EQ(bithumbUser1.name(), "bithumb"); - EXPECT_EQ(bithumbUser1.keyName(), "user1"); - const ExchangeTest &krakenUser1 = exchangeRetriever.retrieveUniqueCandidate(ExchangeName("kraken")); - EXPECT_EQ(krakenUser1.name(), "kraken"); - EXPECT_EQ(krakenUser1.keyName(), "user3"); -} - -TEST(ExchangeRetriever, RetrieveSelectedExchangesInitialOrder) { - const ExchangeTest kAllExchanges[] = {ExchangeTest("kraken", "user1"), ExchangeTest("bithumb", "user1"), - ExchangeTest("kraken", "user2")}; - ExchangeRetriever exchangeRetriever(kAllExchanges); - EXPECT_FALSE(exchangeRetriever.exchanges().empty()); - ExchangeName krakenExchangeName("kraken"); - ExchangeNames names{krakenExchangeName}; - ExchangeRetriever::SelectedExchanges selectedExchanges = exchangeRetriever.select(Order::kInitial, names); - ASSERT_EQ(selectedExchanges.size(), 2U); - EXPECT_EQ(selectedExchanges.front()->name(), "kraken"); - EXPECT_EQ(selectedExchanges.back()->name(), "kraken"); - selectedExchanges = exchangeRetriever.select(Order::kInitial, ExchangeNames()); - ASSERT_EQ(selectedExchanges.size(), 3U); - EXPECT_EQ(selectedExchanges[0]->name(), "kraken"); - EXPECT_EQ(selectedExchanges[1]->name(), "bithumb"); - EXPECT_EQ(selectedExchanges[2]->name(), "kraken"); -} - -TEST(ExchangeRetriever, RetrieveSelectedExchangesSelectedOrder) { - const std::pair kExchangePairs[] = {{"kraken", "bithumb"}, {"bithumb", "kraken"}}; - for (const auto &[first, second] : kExchangePairs) { - const ExchangeTest kAllExchanges[] = {ExchangeTest(first, "user1"), ExchangeTest(second, "user1"), - ExchangeTest(first, "user2")}; - ExchangeRetriever exchangeRetriever(kAllExchanges); - ExchangeNames names{ExchangeName(second), ExchangeName(first)}; - ExchangeRetriever::SelectedExchanges selectedExchanges = exchangeRetriever.select(Order::kSelection, names); - ASSERT_EQ(selectedExchanges.size(), 3U); - EXPECT_EQ(selectedExchanges[0]->name(), second); - EXPECT_EQ(selectedExchanges[1]->name(), first); - EXPECT_EQ(selectedExchanges[2]->name(), first); - } -} - -TEST(ExchangeRetriever, RetrieveAtMostOneAccountSelectedExchanges) { - const std::pair kExchangePairs[] = {{"kraken", "bithumb"}, {"bithumb", "kraken"}}; - for (const auto &[first, second] : kExchangePairs) { - const ExchangeTest kAllExchanges[] = {ExchangeTest(first, "user1"), ExchangeTest(second, "user1"), - ExchangeTest(first, "user2")}; - ExchangeRetriever exchangeRetriever(kAllExchanges); - ExchangeNames names{ExchangeName(second), ExchangeName(first)}; - ExchangeRetriever::UniquePublicSelectedExchanges selectedExchanges = exchangeRetriever.selectOneAccount(names); - ASSERT_EQ(selectedExchanges.size(), 2U); - EXPECT_EQ(selectedExchanges.front()->name(), second); - EXPECT_EQ(selectedExchanges.back()->name(), first); - } -} - -TEST(ExchangeRetriever, RetrieveUniquePublicExchange) { - const std::pair kExchangePairs[] = {{"kraken", "bithumb"}, {"bithumb", "kraken"}}; - for (const auto &[first, second] : kExchangePairs) { - const ExchangeTest kAllExchanges[] = {ExchangeTest(first, "user1"), ExchangeTest(second, "user1")}; - ExchangeRetriever exchangeRetriever(kAllExchanges); - ExchangeNames names{ExchangeName(second), ExchangeName(first)}; - ExchangeRetriever::PublicExchangesVec selectedExchanges = exchangeRetriever.selectPublicExchanges(names); - ASSERT_EQ(selectedExchanges.size(), 2U); - EXPECT_EQ(selectedExchanges.front()->name(), second); - EXPECT_EQ(selectedExchanges.back()->name(), first); - } -} -} // namespace cct \ No newline at end of file diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 4871e58b..f6191775 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -41,6 +41,8 @@ add_unit_test( test/exchangesorchestrator_private_test.cpp LIBRARIES coincenter_engine + DIRECTORIES + ../api/common/test/include ) add_unit_test( @@ -48,6 +50,8 @@ add_unit_test( test/exchangesorchestrator_public_test.cpp LIBRARIES coincenter_engine + DIRECTORIES + ../api/common/test/include ) add_unit_test( @@ -55,6 +59,8 @@ add_unit_test( test/exchangesorchestrator_trade_test.cpp LIBRARIES coincenter_engine + DIRECTORIES + ../api/common/test/include ) add_unit_test( @@ -62,6 +68,8 @@ add_unit_test( test/queryresultprinter_public_test.cpp LIBRARIES coincenter_engine + DIRECTORIES + ../api/common/test/include ) add_unit_test( @@ -69,6 +77,8 @@ add_unit_test( test/queryresultprinter_private_test.cpp LIBRARIES coincenter_engine + DIRECTORIES + ../api/common/test/include ) add_unit_test( diff --git a/src/engine/src/exchangesorchestrator.cpp b/src/engine/src/exchangesorchestrator.cpp index cd10c242..a2206d99 100644 --- a/src/engine/src/exchangesorchestrator.cpp +++ b/src/engine/src/exchangesorchestrator.cpp @@ -30,7 +30,6 @@ #include "exchangepublicapi.hpp" #include "exchangepublicapitypes.hpp" #include "exchangeretriever.hpp" -#include "exchangeretrieverbase.hpp" #include "market.hpp" #include "monetaryamount.hpp" #include "monetaryamountbycurrencyset.hpp" @@ -184,8 +183,8 @@ BalancePerExchange ExchangesOrchestrator::getBalance(std::span balancePortfolios(selectedExchanges.size()); @@ -207,8 +206,8 @@ WalletPerExchange ExchangesOrchestrator::getDepositInfo(std::span canDepositCurrency(depositInfoExchanges.size()); @@ -249,8 +248,8 @@ ClosedOrdersPerExchange ExchangesOrchestrator::getClosedOrders(std::span privateExchangeNames, CurrencyCode currencyCode) { log::info("Query {} dust sweeper from {}", currencyCode, ConstructAccumulatedExchangeNames(privateExchangeNames)); - ExchangeRetriever::SelectedExchanges selExchanges = - _exchangeRetriever.select(ExchangeRetriever::Order::kInitial, privateExchangeNames); + ExchangeRetriever::SelectedExchanges selExchanges = _exchangeRetriever.select( + ExchangeRetriever::Order::kInitial, privateExchangeNames, ExchangeRetriever::Filter::kWithAccountWhenEmpty); TradedAmountsVectorWithFinalAmountPerExchange ret(selExchanges.size()); _threadPool.parallelTransform(selExchanges.begin(), selExchanges.end(), ret.begin(),