Skip to content

Commit

Permalink
Do not select Exchange without account with private requests on empty…
Browse files Browse the repository at this point in the history
… list of exchanges
  • Loading branch information
sjanel committed Mar 18, 2024
1 parent abea1f9 commit a7136ad
Show file tree
Hide file tree
Showing 12 changed files with 426 additions and 306 deletions.
5 changes: 4 additions & 1 deletion cmake/AddUnitTest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ 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)
if(arg STREQUAL "LIBRARIES")
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})

Expand All @@ -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()
2 changes: 2 additions & 0 deletions src/api/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ function(add_common_test name)
${MY_UNPARSED_ARGUMENTS}
LIBRARIES
coincenter_api-common
DIRECTORIES
test/include
)
endfunction()

Expand Down
11 changes: 5 additions & 6 deletions src/api/interface/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

aux_source_directory(src API_INTERFACE_SRC)

add_library(coincenter_api-interface STATIC ${API_INTERFACE_SRC})
Expand All @@ -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
)
20 changes: 11 additions & 9 deletions src/api/interface/include/exchange.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
166 changes: 163 additions & 3 deletions src/api/interface/include/exchangeretriever.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,169 @@
#pragma once

#include <algorithm>
#include <cstdint>
#include <iterator>
#include <memory>
#include <span>
#include <type_traits>

#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<Exchange>;
using ConstExchangeRetriever = ExchangeRetrieverBase<const Exchange>;

class ExchangeRetriever {
public:
using SelectedExchanges = SmallVector<Exchange *, kTypicalNbPrivateAccounts>;
using UniquePublicSelectedExchanges = FixedCapacityVector<Exchange *, kNbSupportedExchanges>;
using PublicExchangesVec = FixedCapacityVector<api::ExchangePublic *, kNbSupportedExchanges>;

enum class Order : int8_t { kInitial, kSelection };
enum class Filter : int8_t { kNone, kWithAccountWhenEmpty };

ExchangeRetriever() noexcept = default;

explicit ExchangeRetriever(std::span<Exchange> exchanges) : _exchanges(exchanges) {}

std::span<Exchange> 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 <class Names, class Matcher>
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<SelectedExchanges::size_type>(_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 <class NameType>
struct Matcher {
static_assert(std::is_same_v<NameType, ExchangeName> || std::is_same_v<NameType, std::string_view>);

bool operator()(const Exchange &e, const NameType &n) const {
if constexpr (std::is_same_v<NameType, std::string_view>) {
return e.name() == n;
} else {
return e.matches(n);
}
}
};

template <class NamesContainerType>
using NameType = std::remove_cvref_t<decltype(*std::declval<NamesContainerType>().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 <class Names>
SelectedExchanges select(Order order, const Names &exchangeNames, Filter filter = Filter::kNone) const {
return select(order, exchangeNames, Matcher<NameType<Names>>(), 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 <class Names>
UniquePublicSelectedExchanges selectOneAccount(const Names &exchangeNames, Filter filter = Filter::kNone) const {
SelectedExchanges selectedExchanges = select(Order::kSelection, exchangeNames, Matcher<NameType<Names>>(), 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 <class Names>
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<Exchange> _exchanges;
};

} // namespace cct
Loading

0 comments on commit a7136ad

Please sign in to comment.