Skip to content

Commit

Permalink
Use glaze::json for Binance private API
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Nov 19, 2024
1 parent 12b809d commit 88b67c9
Show file tree
Hide file tree
Showing 9 changed files with 581 additions and 314 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ if(NOT glaze)

FetchContent_Declare(
glaze
URL https://github.com/stephenberry/glaze/archive/refs/tags/v4.0.0.tar.gz
URL_HASH SHA256=6114cd6fc2eb39e396e229c7971b2ca5aeb8a670f0dfcd37d6223d766f4afecf
URL https://github.com/stephenberry/glaze/archive/refs/tags/v4.0.1.tar.gz
URL_HASH SHA256=0026aca33201ee6d3a820fb5926f36ba8c838bfd3120e2e179b0eee62b5bd231
)

list(APPEND fetchContentPackagesToMakeAvailable glaze)
Expand Down
29 changes: 14 additions & 15 deletions src/api/common/include/binance-common-api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

#include <mutex>

#include "binance-common-schema.hpp"
#include "cachedresult.hpp"
#include "cct_json-container.hpp"
#include "curlhandle.hpp"
#include "currencycode.hpp"
#include "currencycodeset.hpp"
#include "currencyexchangeflatset.hpp"
#include "monetaryamount.hpp"
#include "monetaryamountbycurrencyset.hpp"
#include "runmodes.hpp"
#include "timedef.hpp"

namespace cct {

Expand All @@ -20,17 +19,6 @@ class PermanentCurlOptions;

namespace api {

class BinanceGlobalInfosFunc {
public:
BinanceGlobalInfosFunc(AbstractMetricGateway* pMetricGateway, const PermanentCurlOptions& permanentCurlOptions,
settings::RunMode runMode);

json::container operator()();

private:
CurlHandle _curlHandle;
};

class BinanceGlobalInfos {
public:
BinanceGlobalInfos(CachedResultOptions&& cachedResultOptions, AbstractMetricGateway* pMetricGateway,
Expand All @@ -45,8 +33,19 @@ class BinanceGlobalInfos {
private:
friend class BinancePrivate;

static CurrencyExchangeFlatSet ExtractTradableCurrencies(const json::container& allCoins,
const CurrencyCodeSet& excludedCurrencies);
class BinanceGlobalInfosFunc {
public:
BinanceGlobalInfosFunc(AbstractMetricGateway* pMetricGateway, const PermanentCurlOptions& permanentCurlOptions,
settings::RunMode runMode);

schema::binance::NetworkCoinDataVector operator()();

private:
CurlHandle _curlHandle;
};

static CurrencyExchangeFlatSet ExtractTradableCurrencies(
const schema::binance::NetworkCoinDataVector& networkCoinDataVector, const CurrencyCodeSet& excludedCurrencies);

std::mutex _mutex;
CachedResult<BinanceGlobalInfosFunc> _globalInfosCache;
Expand Down
41 changes: 41 additions & 0 deletions src/api/common/include/binance-common-schema.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <type_traits>

#include "cct_smallvector.hpp"
#include "cct_string.hpp"
#include "cct_type_traits.hpp"
#include "cct_vector.hpp"
#include "monetaryamount.hpp"

namespace cct::schema::binance {

struct NetworkListElement {
bool isDefault{};
bool depositEnable{};
bool withdrawEnable{};
MonetaryAmount withdrawFee;

auto operator<=>(const NetworkListElement&) const = default;
};

struct NetworkCoinData {
string coin;
bool isLegalMoney{};
SmallVector<NetworkListElement, 4> networkList;

using trivially_relocatable =
std::bool_constant<is_trivially_relocatable_v<string> &&
is_trivially_relocatable_v<SmallVector<NetworkListElement, 4>>>::type;

auto operator<=>(const NetworkCoinData&) const = default;
};

using NetworkCoinDataVector = vector<NetworkCoinData>;

struct NetworkCoinAll {
string code;
NetworkCoinDataVector data;
};

} // namespace cct::schema::binance
123 changes: 49 additions & 74 deletions src/api/common/src/binance-common-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
#include <utility>

#include "abstractmetricgateway.hpp"
#include "binance-common-schema.hpp"
#include "cachedresult.hpp"
#include "cct_json-container.hpp"
#include "cct_log.hpp"
#include "cct_smallvector.hpp"
#include "curlhandle.hpp"
#include "currencycode.hpp"
#include "currencycodeset.hpp"
Expand All @@ -27,67 +28,48 @@ namespace cct::api {

namespace {

json::container PublicQuery(CurlHandle& curlHandle, std::string_view method) {
RequestRetry requestRetry(curlHandle, CurlOptions(HttpRequestType::kGet));

return requestRetry.queryJson(method, [](const json::container& jsonResponse) {
const auto foundErrorIt = jsonResponse.find("code");
const auto foundMsgIt = jsonResponse.find("msg");
if (foundErrorIt != jsonResponse.end() && foundMsgIt != jsonResponse.end()) {
const int statusCode = foundErrorIt->get<int>(); // "1100" for instance
log::warn("Binance error ({}), full: '{}'", statusCode, jsonResponse.dump());
return RequestRetry::Status::kResponseError;
}
return RequestRetry::Status::kResponseOK;
});
}

constexpr std::string_view kCryptoFeeBaseUrl = "https://www.binance.com";
} // namespace

BinanceGlobalInfosFunc::BinanceGlobalInfosFunc(AbstractMetricGateway* pMetricGateway,
const PermanentCurlOptions& permanentCurlOptions,
settings::RunMode runMode)
BinanceGlobalInfos::BinanceGlobalInfosFunc::BinanceGlobalInfosFunc(AbstractMetricGateway* pMetricGateway,
const PermanentCurlOptions& permanentCurlOptions,
settings::RunMode runMode)
: _curlHandle(kCryptoFeeBaseUrl, pMetricGateway, permanentCurlOptions, runMode) {}

json::container BinanceGlobalInfosFunc::operator()() {
json::container ret = PublicQuery(_curlHandle, "/bapi/capital/v1/public/capital/getNetworkCoinAll");
auto dataIt = ret.find("data");
json::container dataRet;
if (dataIt == ret.end() || !dataIt->is_array()) {
log::error("Unexpected reply from binance getNetworkCoinAll, no data array");
dataRet = json::container::array_t();
} else {
dataRet = std::move(*dataIt);
schema::binance::NetworkCoinDataVector BinanceGlobalInfos::BinanceGlobalInfosFunc::operator()() {
RequestRetry requestRetry(_curlHandle, CurlOptions(HttpRequestType::kGet));

schema::binance::NetworkCoinAll ret =
requestRetry.query<schema::binance::NetworkCoinAll,
json::opts{.error_on_unknown_keys = false, .minified = true, .raw_string = true}>(
"/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; });

if (endIt != ret.data.end()) {
log::debug("{} currencies discarded for binance as code too long", ret.data.end() - endIt);
ret.data.erase(endIt, ret.data.end());
}

const auto endIt = std::remove_if(dataRet.begin(), dataRet.end(), [](const json::container& el) {
return el["coin"].get<std::string_view>().size() > CurrencyCode::kMaxLen;
});

if (endIt != dataRet.end()) {
log::debug("{} currencies discarded for binance as code too long", dataRet.end() - endIt);
dataRet.erase(endIt, dataRet.end());
}
std::ranges::sort(ret.data);

std::sort(dataRet.begin(), dataRet.end(), [](const json::container& lhs, const json::container& rhs) {
return lhs["coin"].get<std::string_view>() < rhs["coin"].get<std::string_view>();
});
return dataRet;
return ret.data;
}

namespace {
MonetaryAmount ComputeWithdrawalFeesFromNetworkList(CurrencyCode cur, const json::container& coinElem) {
MonetaryAmount ComputeWithdrawalFeesFromNetworkList(CurrencyCode cur, const auto& coinElem) {
MonetaryAmount withdrawFee(0, cur);
auto networkListIt = coinElem.find("networkList");
if (networkListIt == coinElem.end()) {
log::error("Unexpected Binance public coin data format, returning 0 monetary amount");
return withdrawFee;
}
for (const json::container& networkListPart : *networkListIt) {
MonetaryAmount fee(networkListPart["withdrawFee"].get<std::string_view>(), cur);
auto isDefaultIt = networkListPart.find("isDefault");
if (isDefaultIt != networkListPart.end() && isDefaultIt->get<bool>()) {
for (const auto& networkListPart : coinElem.networkList) {
MonetaryAmount fee(networkListPart.withdrawFee, cur);
if (networkListPart.isDefault) {
withdrawFee = fee;
break;
}
Expand All @@ -105,13 +87,10 @@ MonetaryAmountByCurrencySet BinanceGlobalInfos::queryWithdrawalFees() {
std::lock_guard<std::mutex> guard(_mutex);
const auto& allCoins = _globalInfosCache.get();

MonetaryAmountVector fees;

fees.reserve(allCoins.size());
MonetaryAmountVector fees(allCoins.size());

std::transform(allCoins.begin(), allCoins.end(), std::back_inserter(fees), [](const json::container& el) {
CurrencyCode cur(el["coin"].get<std::string_view>());
return ComputeWithdrawalFeesFromNetworkList(cur, el);
std::ranges::transform(allCoins, fees.begin(), [](const auto& el) {
return ComputeWithdrawalFeesFromNetworkList(CurrencyCode{el.coin}, el);
});

log::info("Retrieved {} withdrawal fees for binance", fees.size());
Expand All @@ -123,10 +102,8 @@ MonetaryAmount BinanceGlobalInfos::queryWithdrawalFee(CurrencyCode currencyCode)
const auto& allCoins = _globalInfosCache.get();
const auto curStr = currencyCode.str();

const auto it = std::partition_point(allCoins.begin(), allCoins.end(), [&curStr](const json::container& el) {
return el["coin"].get<std::string_view>() < curStr;
});
if (it != allCoins.end() && (*it)["coin"].get<std::string_view>() == curStr) {
const auto it = std::ranges::partition_point(allCoins, [&curStr](const auto& el) { return el.coin < curStr; });
if (it != allCoins.end() && it->coin == curStr) {
return ComputeWithdrawalFeesFromNetworkList(currencyCode, *it);
}
return MonetaryAmount(0, currencyCode);
Expand All @@ -137,30 +114,28 @@ CurrencyExchangeFlatSet BinanceGlobalInfos::queryTradableCurrencies(const Curren
return ExtractTradableCurrencies(_globalInfosCache.get(), excludedCurrencies);
}

CurrencyExchangeFlatSet BinanceGlobalInfos::ExtractTradableCurrencies(const json::container& allCoins,
const CurrencyCodeSet& excludedCurrencies) {
CurrencyExchangeFlatSet BinanceGlobalInfos::ExtractTradableCurrencies(
const schema::binance::NetworkCoinDataVector& networkCoinDataVector, const CurrencyCodeSet& excludedCurrencies) {
CurrencyExchangeVector currencies;
for (const json::container& coinJson : allCoins) {
CurrencyCode cur(coinJson["coin"].get<std::string_view>());
for (const auto& coinJson : networkCoinDataVector) {
CurrencyCode cur{coinJson.coin};
if (excludedCurrencies.contains(cur)) {
log::trace("Discard {} excluded by config", cur.str());
continue;
}
const bool isFiat = coinJson["isLegalMoney"];
const auto& networkList = coinJson["networkList"];
if (networkList.size() > 1) {
const auto& networkList = coinJson.networkList;
if (coinJson.networkList.size() > 1) {
log::debug("Several networks found for {}, considering only default network", cur.str());
}
const auto it = std::find_if(networkList.begin(), networkList.end(),
[](const json::container& el) { return el["isDefault"].get<bool>(); });
const auto it = std::ranges::find_if(networkList, [](const auto& el) { return el.isDefault; });
if (it != networkList.end()) {
auto deposit = (*it)["depositEnable"].get<bool>() ? CurrencyExchange::Deposit::kAvailable
: CurrencyExchange::Deposit::kUnavailable;
auto withdraw = (*it)["withdrawEnable"].get<bool>() ? CurrencyExchange::Withdraw::kAvailable
: CurrencyExchange::Withdraw::kUnavailable;
auto deposit =
it->depositEnable ? CurrencyExchange::Deposit::kAvailable : CurrencyExchange::Deposit::kUnavailable;
auto withdraw =
it->withdrawEnable ? CurrencyExchange::Withdraw::kAvailable : CurrencyExchange::Withdraw::kUnavailable;

currencies.emplace_back(cur, cur, cur, deposit, withdraw,
isFiat ? CurrencyExchange::Type::kFiat : CurrencyExchange::Type::kCrypto);
coinJson.isLegalMoney ? CurrencyExchange::Type::kFiat : CurrencyExchange::Type::kCrypto);
}
}
CurrencyExchangeFlatSet ret(std::move(currencies));
Expand Down
Loading

0 comments on commit 88b67c9

Please sign in to comment.