Skip to content

Commit

Permalink
Refactor BalancePortfolio
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Mar 29, 2024
1 parent 12a4eb4 commit 8714b3d
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 152 deletions.
10 changes: 4 additions & 6 deletions src/api/common/include/exchangeprivateapi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,8 @@ class ExchangePrivate : public ExchangeBase {

virtual BalancePortfolio queryAccountBalance(const BalanceOptions &balanceOptions = BalanceOptions()) = 0;

/// Adds an amount to given BalancePortfolio.
/// @param equiCurrency Asks conversion of given amount into this currency as well
void addBalance(BalancePortfolio &balancePortfolio, MonetaryAmount amount, CurrencyCode equiCurrency);

/// Return true if exchange supports simulated order (some exchanges such as Kraken or Binance for instance support
/// this query parameter)
/// Return true if exchange supports simulated order (some exchanges such as Kraken or Binance for instance
/// support this query parameter)
virtual bool isSimulatedOrderSupported() const = 0;

/// Place an order in mode fire and forget.
Expand Down Expand Up @@ -190,6 +186,8 @@ class ExchangePrivate : public ExchangeBase {

/// Check if withdraw has been confirmed and successful from 'this' exchange
SentWithdrawInfo isWithdrawSuccessfullySent(const InitiatedWithdrawInfo &initiatedWithdrawInfo);

void computeEquiCurrencyAmounts(BalancePortfolio &balancePortfolio, CurrencyCode equiCurrency);
};
} // namespace api
} // namespace cct
52 changes: 21 additions & 31 deletions src/api/common/src/exchangeprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,40 +55,31 @@ ExchangePrivate::ExchangePrivate(const CoincenterInfo &coincenterInfo, ExchangeP
: _exchangePublic(exchangePublic), _coincenterInfo(coincenterInfo), _apiKey(apiKey) {}

BalancePortfolio ExchangePrivate::getAccountBalance(const BalanceOptions &balanceOptions) {
CacheFreezerRAII uniqueQueryHandle;
if (balanceOptions.equiCurrency().isDefined()) {
// In case of balance with equivalent currencies, for each exchange these requests will be made:
// - Markets
// - Ticker
// To avoid making several ticker queries (especially) for each exchange during the whole balance process,
// we "allow" only one query per argument in the cache of results of all exchanges.
// The freeze will be lifted automatically (as the object is RAII) at the end of this function.
uniqueQueryHandle = CacheFreezerRAII(_cachedResultVault);
}
BalancePortfolio balancePortfolio = queryAccountBalance(balanceOptions);

const auto equiCurrency = balanceOptions.equiCurrency();
if (equiCurrency.isDefined()) {
computeEquiCurrencyAmounts(balancePortfolio, equiCurrency);
}

log::info("Retrieved {} balance for {} assets", exchangeName(), balancePortfolio.size());

return balancePortfolio;
}

void ExchangePrivate::addBalance(BalancePortfolio &balancePortfolio, MonetaryAmount amount, CurrencyCode equiCurrency) {
if (amount != 0) {
ExchangeName exchangeName = this->exchangeName();
if (equiCurrency.isNeutral()) {
log::trace("{} balance {}", exchangeName, amount);
balancePortfolio.add(amount);
} else {
std::optional<MonetaryAmount> optConvertedAmountEquiCurrency =
_exchangePublic.estimatedConvert(amount, equiCurrency);
MonetaryAmount equivalentInMainCurrency;
if (optConvertedAmountEquiCurrency) {
equivalentInMainCurrency = *optConvertedAmountEquiCurrency;
} else {
log::debug("No {} -> {} conversion possible on {}", amount.currencyStr(), equiCurrency, exchangeName);
equivalentInMainCurrency = MonetaryAmount(0, equiCurrency);
}
log::trace("{} Balance {} (eq. {})", exchangeName, amount, equivalentInMainCurrency);
balancePortfolio.add(amount, equivalentInMainCurrency);
}
void ExchangePrivate::computeEquiCurrencyAmounts(BalancePortfolio &balancePortfolio, CurrencyCode equiCurrency) {
MarketOrderBookMap marketOrderBookMap;
auto fiats = _exchangePublic.queryFiats();
MarketSet markets;
ExchangeName exchangeName = this->exchangeName();

for (auto &[amount, equi] : balancePortfolio) {
MarketsPath conversionPath =
_exchangePublic.findMarketsPath(amount.currencyCode(), equiCurrency, markets, fiats,
ExchangePublic::MarketPathMode::kWithPossibleFiatConversionAtExtremity);
equi = _exchangePublic.convert(amount, equiCurrency, conversionPath, fiats, marketOrderBookMap)
.value_or(MonetaryAmount(0, equiCurrency));
log::trace("{} Balance {} (eq. {})", exchangeName, amount, equi);
}
}

Expand Down Expand Up @@ -352,8 +343,7 @@ MarketVector GetPossibleMarketsForDustThresholds(const BalancePortfolio &balance
CurrencyCode currencyCode, const MarketSet &markets,
const PenaltyPerMarketMap &penaltyPerMarketMap) {
MarketVector possibleMarkets;
for (const BalancePortfolio::MonetaryAmountWithEquivalent &avAmountEq : balance) {
MonetaryAmount avAmount = avAmountEq.amount;
for (const auto [avAmount, _] : balance) {
CurrencyCode avCur = avAmount.currencyCode();
const auto lbAvAmount = dustThresholds.find(MonetaryAmount(0, avCur));
if (lbAvAmount == dustThresholds.end() || *lbAvAmount < avAmount) {
Expand Down
4 changes: 2 additions & 2 deletions src/api/common/test/exchangeprivateapi_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ inline bool operator==(const DeliveredWithdrawInfo &lhs, const DeliveredWithdraw

inline BalancePortfolio operator+(const BalancePortfolio &balancePortfolio, const TradedAmounts &tradedAmounts) {
BalancePortfolio ret = balancePortfolio;
ret.add(tradedAmounts.to);
ret.add(-tradedAmounts.from);
ret += tradedAmounts.to;
ret += -tradedAmounts.from;
return ret;
}

Expand Down
19 changes: 14 additions & 5 deletions src/api/exchanges/src/binanceprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,21 @@ bool BinancePrivate::validateApiKey() {
}

BalancePortfolio BinancePrivate::queryAccountBalance(const BalanceOptions& balanceOptions) {
json result = PrivateQuery(_curlHandle, _apiKey, HttpRequestType::kGet, "/api/v3/account", _queryDelay);
bool withBalanceInUse =
const json result = PrivateQuery(_curlHandle, _apiKey, HttpRequestType::kGet, "/api/v3/account", _queryDelay);
const bool withBalanceInUse =
balanceOptions.amountIncludePolicy() == BalanceOptions::AmountIncludePolicy::kWithBalanceInUse;
CurrencyCode equiCurrency = balanceOptions.equiCurrency();

BalancePortfolio balancePortfolio;
for (const json& balance : result["balances"]) {

auto dataIt = result.find("balances");
if (dataIt == result.end()) {
log::error("Unexpected get account balance reply from {}", exchangeName());
return balancePortfolio;
}

balancePortfolio.reserve(dataIt->size());

for (const json& balance : *dataIt) {
CurrencyCode currencyCode(balance["asset"].get<std::string_view>());
MonetaryAmount amount(balance["free"].get<std::string_view>(), currencyCode);

Expand All @@ -272,7 +281,7 @@ BalancePortfolio BinancePrivate::queryAccountBalance(const BalanceOptions& balan
amount += usedAmount;
}

addBalance(balancePortfolio, amount, equiCurrency);
balancePortfolio += amount;
}
return balancePortfolio;
}
Expand Down
5 changes: 2 additions & 3 deletions src/api/exchanges/src/bithumbprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,6 @@ BalancePortfolio BithumbPrivate::queryAccountBalance(const BalanceOptions& balan
return balancePortfolio;
}

CurrencyCode equiCurrency = balanceOptions.equiCurrency();

static constexpr std::array<std::string_view, 2> kKnownPrefixes = {"available_", "in_use_"};
auto endPrefixIt = kKnownPrefixes.end();
if (balanceOptions.amountIncludePolicy() != BalanceOptions::AmountIncludePolicy::kWithBalanceInUse) {
Expand All @@ -377,7 +375,8 @@ BalancePortfolio BithumbPrivate::queryAccountBalance(const BalanceOptions& balan
if (key.starts_with(*prefixIt)) {
std::string_view curStr(key.begin() + prefixIt->size(), key.end());
MonetaryAmount amount(value.get<std::string_view>(), _coincenterInfo.standardizeCurrencyCode(curStr));
this->addBalance(balancePortfolio, amount, equiCurrency);

balancePortfolio += amount;
break;
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/api/exchanges/src/huobiprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,12 @@ BalancePortfolio HuobiPrivate::queryAccountBalance(const BalanceOptions& balance

bool withBalanceInUse =
balanceOptions.amountIncludePolicy() == BalanceOptions::AmountIncludePolicy::kWithBalanceInUse;
CurrencyCode equiCurrency = balanceOptions.equiCurrency();
for (const json& balanceDetail : result["data"]["list"]) {
std::string_view typeStr = balanceDetail["type"].get<std::string_view>();
CurrencyCode currencyCode(balanceDetail["currency"].get<std::string_view>());
MonetaryAmount amount(balanceDetail["balance"].get<std::string_view>(), currencyCode);
if (typeStr == "trade" || (withBalanceInUse && typeStr == "frozen")) {
this->addBalance(balancePortfolio, amount, equiCurrency);
balancePortfolio += amount;
} else {
log::trace("Do not consider {} as it is {} on {}", amount, typeStr, _exchangePublic.name());
}
Expand Down
5 changes: 1 addition & 4 deletions src/api/exchanges/src/krakenprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ BalancePortfolio KrakenPrivate::queryAccountBalance(const BalanceOptions& balanc

bool withBalanceInUse =
balanceOptions.amountIncludePolicy() == BalanceOptions::AmountIncludePolicy::kWithBalanceInUse;
CurrencyCode equiCurrency = balanceOptions.equiCurrency();

// Kraken returns total balance, including the amounts in use
if (!withBalanceInUse) {
Expand Down Expand Up @@ -203,9 +202,7 @@ BalancePortfolio KrakenPrivate::queryAccountBalance(const BalanceOptions& balanc
}
}
}
for (MonetaryAmount amount : balanceAmounts) {
addBalance(balancePortfolio, amount, equiCurrency);
}
std::ranges::for_each(balanceAmounts, [&balancePortfolio](const auto amt) { balancePortfolio += amt; });
return balancePortfolio;
}

Expand Down
7 changes: 5 additions & 2 deletions src/api/exchanges/src/kucoinprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,18 @@ BalancePortfolio KucoinPrivate::queryAccountBalance(const BalanceOptions& balanc
BalancePortfolio balancePortfolio;
bool withBalanceInUse =
balanceOptions.amountIncludePolicy() == BalanceOptions::AmountIncludePolicy::kWithBalanceInUse;
CurrencyCode equiCurrency = balanceOptions.equiCurrency();
const std::string_view amountKey = withBalanceInUse ? "balance" : "available";

balancePortfolio.reserve(result.size());

for (const json& balanceDetail : result) {
std::string_view typeStr = balanceDetail["type"].get<std::string_view>();
CurrencyCode currencyCode(
_coincenterInfo.standardizeCurrencyCode(balanceDetail["currency"].get<std::string_view>()));
MonetaryAmount amount(balanceDetail[amountKey].get<std::string_view>(), currencyCode);
log::debug("{} in account '{}' on {}", amount, typeStr, exchangeName());
this->addBalance(balancePortfolio, amount, equiCurrency);

balancePortfolio += amount;
}
return balancePortfolio;
}
Expand Down
22 changes: 12 additions & 10 deletions src/api/exchanges/src/upbitprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,24 @@ CurrencyExchangeFlatSet UpbitPrivate::TradableCurrenciesFunc::operator()() {
BalancePortfolio UpbitPrivate::queryAccountBalance(const BalanceOptions& balanceOptions) {
const bool withBalanceInUse =
balanceOptions.amountIncludePolicy() == BalanceOptions::AmountIncludePolicy::kWithBalanceInUse;
const CurrencyCode equiCurrency = balanceOptions.equiCurrency();

BalancePortfolio ret;
for (const json& accountDetail : PrivateQuery(_curlHandle, _apiKey, HttpRequestType::kGet, "/v1/accounts")) {
const CurrencyCode currencyCode(accountDetail["currency"].get<std::string_view>());
const MonetaryAmount availableAmount(accountDetail["balance"].get<std::string_view>(), currencyCode);
BalancePortfolio balancePortfolio;

this->addBalance(ret, availableAmount, equiCurrency);
json ret = PrivateQuery(_curlHandle, _apiKey, HttpRequestType::kGet, "/v1/accounts");

if (withBalanceInUse) {
const MonetaryAmount amountInUse(accountDetail["locked"].get<std::string_view>(), currencyCode);
balancePortfolio.reserve(ret.size());

for (const json& accountDetail : ret) {
const CurrencyCode currencyCode(accountDetail["currency"].get<std::string_view>());
MonetaryAmount availableAmount(accountDetail["balance"].get<std::string_view>(), currencyCode);

this->addBalance(ret, amountInUse, equiCurrency);
if (withBalanceInUse) {
availableAmount += MonetaryAmount(accountDetail["locked"].get<std::string_view>(), currencyCode);
}

balancePortfolio += availableAmount;
}
return ret;
return balancePortfolio;
}

Wallet UpbitPrivate::DepositWalletFunc::operator()(CurrencyCode currencyCode) {
Expand Down
4 changes: 2 additions & 2 deletions src/engine/src/balanceperexchangeportfolio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace cct {

namespace {
MonetaryAmount ComputeTotalSum(const BalancePortfolio &total) {
CurrencyCode balanceCurrencyCode = total.empty() ? CurrencyCode() : total.front().equi.currencyCode();
CurrencyCode balanceCurrencyCode = total.equiCurrency();
MonetaryAmount totalSum(0, balanceCurrencyCode);
for (const auto &[amount, equi] : total) {
totalSum += equi;
Expand All @@ -25,7 +25,7 @@ MonetaryAmount ComputeTotalSum(const BalancePortfolio &total) {

SimpleTable BalancePerExchangePortfolio::getTable(bool wide) const {
BalancePortfolio total = computeTotal();
CurrencyCode balanceCurrencyCode = total.empty() ? CurrencyCode() : total.front().equi.currencyCode();
CurrencyCode balanceCurrencyCode = total.equiCurrency();
const bool countEqui = !balanceCurrencyCode.isNeutral();
table::Row header("Currency", "Total amount on selected");

Expand Down
10 changes: 5 additions & 5 deletions src/engine/src/metricsexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ void MetricsExporter::exportBalanceMetrics(const BalancePerExchange &balancePerE
key.set("account", exchange.keyName());
key.set("total", "no");
MonetaryAmount totalEquiAmount(0, equiCurrency);
for (BalancePortfolio::MonetaryAmountWithEquivalent amountWithEqui : balancePortfolio) {
key.set("currency", amountWithEqui.amount.currencyStr());
_pMetricsGateway->add(MetricType::kGauge, MetricOperation::kSet, key, amountWithEqui.amount.toDouble());
if (!equiCurrency.isNeutral()) {
totalEquiAmount += amountWithEqui.equi;
for (auto [amount, equi] : balancePortfolio) {
key.set("currency", amount.currencyStr());
_pMetricsGateway->add(MetricType::kGauge, MetricOperation::kSet, key, amount.toDouble());
if (equiCurrency.isDefined()) {
totalEquiAmount += equi;
}
}
if (!equiCurrency.isNeutral()) {
Expand Down
42 changes: 36 additions & 6 deletions src/engine/test/queryresultprinter_private_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "accountowner.hpp"
#include "apioutputtype.hpp"
#include "balanceportfolio.hpp"
#include "cct_exception.hpp"
#include "closed-order.hpp"
#include "currencycode.hpp"
#include "deposit.hpp"
Expand Down Expand Up @@ -238,14 +239,43 @@ TEST_F(QueryResultPrinterBalanceNoEquiCurTest, NoPrint) {

class QueryResultPrinterBalanceEquiCurTest : public QueryResultPrinterTest {
protected:
void SetUp() override {
for (auto &[amount, equi] : bp1) {
if (amount == ma1) {
equi = MonetaryAmount{10000, equiCur};
} else if (amount == ma2) {
equi = MonetaryAmount{90677, equiCur, 1};
} else {
throw exception("Should not happen");
}
}

for (auto &[amount, equi] : bp2) {
if (amount == ma3) {
equi = MonetaryAmount{4508, equiCur, 2};
} else if (amount == ma4) {
equi = MonetaryAmount{25000, equiCur};
} else if (amount == ma5) {
equi = MonetaryAmount{675, equiCur, 1};
} else {
throw exception("Should not happen");
}
}

balancePerExchange = BalancePerExchange{{&exchange1, bp1}, {&exchange4, bp2}, {&exchange2, bp3}, {&exchange3, bp3}};
}

MonetaryAmount ma1{15000, "ADA"};
MonetaryAmount ma2{56, "BTC", 2};
MonetaryAmount ma3{347, "XRP", 1};
MonetaryAmount ma4{15, "ETH"};
MonetaryAmount ma5{123, "XLM"};

CurrencyCode equiCur{"EUR"};
BalancePortfolio bp1{{MonetaryAmount("15000ADA"), MonetaryAmount("10000EUR")},
{MonetaryAmount("0.56BTC"), MonetaryAmount("9067.7EUR")}};
BalancePortfolio bp2{{MonetaryAmount("34.7XRP"), MonetaryAmount("45.08EUR")},
{MonetaryAmount("15ETH"), MonetaryAmount("25000EUR")},
{MonetaryAmount("123XLM"), MonetaryAmount("67.5EUR")}};
BalancePortfolio bp1{ma1, ma2};
BalancePortfolio bp2{ma3, ma4, ma5};
BalancePortfolio bp3;
BalancePerExchange balancePerExchange{{&exchange1, bp1}, {&exchange4, bp2}, {&exchange2, bp3}, {&exchange3, bp3}};
BalancePerExchange balancePerExchange;
};

TEST_F(QueryResultPrinterBalanceEquiCurTest, FormattedTable) {
Expand Down
Loading

0 comments on commit 8714b3d

Please sign in to comment.