From d184a7ee55fd929933b0ab7acaaa288d956e5831 Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Fri, 27 Oct 2023 07:44:11 +0200 Subject: [PATCH] Improve returned trade result information --- src/api-objects/include/tradedamounts.hpp | 27 ++- src/api-objects/src/tradedamounts.cpp | 7 +- src/api/common/src/exchangeprivateapi.cpp | 20 +- src/api/common/src/exchangepublicapi.cpp | 3 +- .../common/test/exchangeprivateapi_test.cpp | 19 +- src/api/exchanges/src/binanceprivateapi.cpp | 8 +- src/api/exchanges/src/bithumbprivateapi.cpp | 8 +- src/api/exchanges/src/krakenprivateapi.cpp | 8 +- src/api/exchanges/src/kucoinpublicapi.cpp | 11 +- src/api/exchanges/src/upbitprivateapi.cpp | 12 +- src/api/exchanges/test/commonapi_test.hpp | 4 +- src/api/interface/include/exchange.hpp | 6 +- src/engine/include/coincenter.hpp | 14 +- src/engine/include/exchangesorchestrator.hpp | 14 +- src/engine/include/iscompletecommand.hpp | 8 + src/engine/include/queryresultprinter.hpp | 17 +- src/engine/include/queryresulttypes.hpp | 4 +- src/engine/include/stringoptionparser.hpp | 7 +- src/engine/include/traderesult.hpp | 36 ++++ src/engine/src/coincenter.cpp | 18 +- src/engine/src/coincentercommands.cpp | 9 +- src/engine/src/exchangesorchestrator.cpp | 73 ++++---- src/engine/src/queryresultprinter.cpp | 29 +-- src/engine/src/stringoptionparser.cpp | 14 +- src/engine/src/traderesult.cpp | 31 ++++ .../test/exchangesorchestrator_trade_test.cpp | 174 +++++++++--------- .../test/queryresultprinter_private_test.cpp | 152 ++++++++------- src/engine/test/stringoptionparser_test.cpp | 35 ++-- src/objects/include/exchangename.hpp | 3 +- src/objects/include/monetaryamount.hpp | 49 ++--- src/objects/src/monetaryamount.cpp | 50 ++--- 31 files changed, 478 insertions(+), 392 deletions(-) create mode 100644 src/engine/include/iscompletecommand.hpp create mode 100644 src/engine/include/traderesult.hpp create mode 100644 src/engine/src/traderesult.cpp diff --git a/src/api-objects/include/tradedamounts.hpp b/src/api-objects/include/tradedamounts.hpp index 1ba1ec68..f87586c3 100644 --- a/src/api-objects/include/tradedamounts.hpp +++ b/src/api-objects/include/tradedamounts.hpp @@ -10,30 +10,24 @@ namespace cct { struct TradedAmounts { - constexpr TradedAmounts() noexcept(std::is_nothrow_default_constructible_v) = default; + constexpr TradedAmounts() noexcept = default; constexpr TradedAmounts(CurrencyCode fromCurrencyCode, CurrencyCode toCurrencyCode) - : tradedFrom(0, fromCurrencyCode), tradedTo(0, toCurrencyCode) {} + : from(0, fromCurrencyCode), to(0, toCurrencyCode) {} - constexpr TradedAmounts(MonetaryAmount fromAmount, MonetaryAmount toAmount) - : tradedFrom(fromAmount), tradedTo(toAmount) {} + constexpr TradedAmounts(MonetaryAmount fromAmount, MonetaryAmount toAmount) : from(fromAmount), to(toAmount) {} - TradedAmounts operator+(const TradedAmounts &o) const { - return TradedAmounts(tradedFrom + o.tradedFrom, tradedTo + o.tradedTo); - } - TradedAmounts &operator+=(const TradedAmounts &o) { - *this = *this + o; - return *this; - } + TradedAmounts operator+(const TradedAmounts &rhs) const { return {from + rhs.from, to + rhs.to}; } + TradedAmounts &operator+=(const TradedAmounts &rhs) { return (*this = *this + rhs); } - constexpr bool operator==(const TradedAmounts &) const = default; + constexpr bool operator==(const TradedAmounts &) const noexcept = default; friend std::ostream &operator<<(std::ostream &os, const TradedAmounts &tradedAmounts); string str() const; - MonetaryAmount tradedFrom; // In currency of 'from' amount - MonetaryAmount tradedTo; // In the opposite currency + MonetaryAmount from; // In currency of 'from' amount + MonetaryAmount to; // In the opposite currency }; } // namespace cct @@ -42,7 +36,8 @@ struct TradedAmounts { 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"); } @@ -51,7 +46,7 @@ struct fmt::formatter { template auto format(const cct::TradedAmounts &a, FormatContext &ctx) const -> decltype(ctx.out()) { - return fmt::format_to(ctx.out(), "{} -> {}", a.tradedFrom, a.tradedTo); + return fmt::format_to(ctx.out(), "{} -> {}", a.from, a.to); } }; #endif \ No newline at end of file diff --git a/src/api-objects/src/tradedamounts.cpp b/src/api-objects/src/tradedamounts.cpp index 7cb1b91b..4915b148 100644 --- a/src/api-objects/src/tradedamounts.cpp +++ b/src/api-objects/src/tradedamounts.cpp @@ -7,15 +7,14 @@ namespace cct { string TradedAmounts::str() const { - string ret = tradedFrom.str(); + string ret = from.str(); ret.append(" -> "); - ret.append(tradedTo.str()); + to.appendStrTo(ret); return ret; } std::ostream &operator<<(std::ostream &os, const TradedAmounts &tradedAmounts) { - os << tradedAmounts.tradedFrom << " -> " << tradedAmounts.tradedTo; - return os; + return os << tradedAmounts.from << " -> " << tradedAmounts.to; } } // namespace cct \ No newline at end of file diff --git a/src/api/common/src/exchangeprivateapi.cpp b/src/api/common/src/exchangeprivateapi.cpp index 32fc3fb3..b4a73b83 100644 --- a/src/api/common/src/exchangeprivateapi.cpp +++ b/src/api/common/src/exchangeprivateapi.cpp @@ -107,15 +107,15 @@ TradedAmounts ExchangePrivate::trade(MonetaryAmount from, CurrencyCode toCurrenc Market mk = conversionPath[tradePos]; log::info("Step {}/{} - trade {} into {}", tradePos + 1, nbTrades, avAmount, mk.opposite(avAmount.currencyCode())); TradedAmounts stepTradedAmounts = marketTrade(avAmount, options, mk); - avAmount = stepTradedAmounts.tradedTo; + avAmount = stepTradedAmounts.to; if (avAmount == 0) { break; } if (tradePos == 0) { - tradedAmounts.tradedFrom = stepTradedAmounts.tradedFrom; + tradedAmounts.from = stepTradedAmounts.from; } if (tradePos + 1 == nbTrades) { - tradedAmounts.tradedTo = stepTradedAmounts.tradedTo; + tradedAmounts.to = stepTradedAmounts.to; } } return tradedAmounts; @@ -220,7 +220,7 @@ TradedAmounts ExchangePrivate::marketTrade(MonetaryAmount from, const TradeOptio log::debug("Cancel order {}", orderId); OrderInfo cancelledOrderInfo = cancelOrder(orderId, tradeContext); totalTradedAmounts += cancelledOrderInfo.tradedAmounts; - from -= cancelledOrderInfo.tradedAmounts.tradedFrom; + from -= cancelledOrderInfo.tradedAmounts.from; if (from == 0) { log::debug("Order {} matched with last traded amounts {} while cancelling", orderId, cancelledOrderInfo.tradedAmounts); @@ -380,7 +380,7 @@ std::pair ExchangePrivate::isSellingPossibleOneShotDustSw for (Market mk : possibleMarkets) { log::info("Dust sweeper - attempt to sell in one shot on {}", mk); TradedAmounts tradedAmounts = marketTrade(amountBalance, tradeOptions, mk); - if (tradedAmounts.tradedTo != 0) { + if (tradedAmounts.to != 0) { return {tradedAmounts, mk}; } } @@ -432,7 +432,7 @@ TradedAmounts ExchangePrivate::buySomeAmountToMakeFutureSellPossible( log::info("Dust sweeper - attempt to buy some {} for future selling", currencyCode); TradedAmounts tradedAmounts = marketTrade(fromAmount, tradeOptions, mk); - if (tradedAmounts.tradedTo != 0) { + if (tradedAmounts.to != 0) { // Then we should have sufficient amount now on this market return tradedAmounts; } @@ -500,7 +500,7 @@ TradedAmountsVectorWithFinalAmount ExchangePrivate::queryDustSweeper(CurrencyCod TradedAmounts tradedAmounts; std::tie(tradedAmounts, tradedMarket) = isSellingPossibleOneShotDustSweeper(possibleMarkets, ret.finalAmount, tradeOptions); - if (tradedAmounts.tradedFrom != 0) { + if (tradedAmounts.from != 0) { IncrementPenalty(tradedMarket, penaltyPerMarketMap); ret.tradedAmountsVector.push_back(std::move(tradedAmounts)); continue; @@ -510,7 +510,7 @@ TradedAmountsVectorWithFinalAmount ExchangePrivate::queryDustSweeper(CurrencyCod // Selling has not worked - so we need to buy some amount on the requested currency first tradedAmounts = buySomeAmountToMakeFutureSellPossible(possibleMarkets, marketPriceMap, dustThreshold, balance, tradeOptions, dustThresholds); - if (tradedAmounts.tradedFrom == 0) { + if (tradedAmounts.from == 0) { break; } ret.tradedAmountsVector.push_back(std::move(tradedAmounts)); @@ -532,7 +532,7 @@ PlaceOrderInfo ExchangePrivate::placeOrderProcess(MonetaryAmount &from, Monetary price = isSell ? marketOrderbook.getHighestTheoreticalPrice() : marketOrderbook.getLowestTheoreticalPrice(); } else { PlaceOrderInfo placeOrderInfo = computeSimulatedMatchedPlacedOrderInfo(volume, price, tradeInfo); - from -= placeOrderInfo.tradedAmounts().tradedFrom; + from -= placeOrderInfo.tradedAmounts().from; return placeOrderInfo; } } @@ -544,7 +544,7 @@ PlaceOrderInfo ExchangePrivate::placeOrderProcess(MonetaryAmount &from, Monetary // (and remove the need to implement the matching amount computation with fees for each exchange) placeOrderInfo = computeSimulatedMatchedPlacedOrderInfo(volume, price, tradeInfo); } - from -= placeOrderInfo.tradedAmounts().tradedFrom; + from -= placeOrderInfo.tradedAmounts().from; return placeOrderInfo; } diff --git a/src/api/common/src/exchangepublicapi.cpp b/src/api/common/src/exchangepublicapi.cpp index f3bd8cfe..6ba54523 100644 --- a/src/api/common/src/exchangepublicapi.cpp +++ b/src/api/common/src/exchangepublicapi.cpp @@ -127,6 +127,7 @@ MarketsPath ExchangePublic::findMarketsPath(CurrencyCode fromCurrency, CurrencyC std::pop_heap(searchPaths.begin(), searchPaths.end(), comp); CurrencyDirPath path = std::move(searchPaths.back()); searchPaths.pop_back(); + CurrencyCode lastCurrencyCode = path.back().cur; if (visitedCurrencies.contains(lastCurrencyCode)) { continue; @@ -159,7 +160,7 @@ MarketsPath ExchangePublic::findMarketsPath(CurrencyCode fromCurrency, CurrencyC std::push_heap(searchPaths.begin(), searchPaths.end(), comp); } } - std::optional optLastFiat = + const std::optional optLastFiat = considerStableCoinsAsFiats ? _coincenterInfo.fiatCurrencyIfStableCoin(lastCurrencyCode) : std::nullopt; const bool isLastFiatLike = optLastFiat || fiats.contains(lastCurrencyCode); if (isToFiatLike && isLastFiatLike) { diff --git a/src/api/common/test/exchangeprivateapi_test.cpp b/src/api/common/test/exchangeprivateapi_test.cpp index e46811d8..29544bd7 100644 --- a/src/api/common/test/exchangeprivateapi_test.cpp +++ b/src/api/common/test/exchangeprivateapi_test.cpp @@ -50,8 +50,8 @@ inline bool operator==(const DeliveredWithdrawInfo &lhs, const DeliveredWithdraw inline BalancePortfolio operator+(const BalancePortfolio &balancePortfolio, const TradedAmounts &tradedAmounts) { BalancePortfolio ret = balancePortfolio; - ret.add(tradedAmounts.tradedTo); - ret.add(-tradedAmounts.tradedFrom); + ret.add(tradedAmounts.to); + ret.add(-tradedAmounts.from); return ret; } @@ -684,7 +684,7 @@ TEST_F(ExchangePrivateDustSweeperTest, DustSweeperDirectSellingPossible) { MonetaryAmount avBtcAmount{75, "BTC", 4}; EXPECT_CALL(exchangePrivate, queryAccountBalance(balanceOptions)) .WillOnce(testing::Return(BalancePortfolio{from, avBtcAmount})) - .WillOnce(testing::Return(BalancePortfolio{avBtcAmount + tradedAmounts.tradedTo})); + .WillOnce(testing::Return(BalancePortfolio{avBtcAmount + tradedAmounts.to})); TradedAmountsVector tradedAmountsVector{tradedAmounts}; TradedAmountsVectorWithFinalAmount res{tradedAmountsVector, MonetaryAmount{0, dustCur}}; @@ -710,14 +710,13 @@ TEST_F(ExchangePrivateDustSweeperTest, DustSweeper2StepsSameMarket) { MonetaryAmount xrpDustThreshold = dustThreshold(dustCur).value_or(MonetaryAmount{-1}); TradedAmounts tradedAmounts1 = expectTakerBuy(xrpDustThreshold, xrpbtcAskPri, xrpbtcBidPri, xrpbtcMarket); - TradedAmounts tradedAmounts2 = expectTakerSell(from + tradedAmounts1.tradedTo, pri); + TradedAmounts tradedAmounts2 = expectTakerSell(from + tradedAmounts1.to, pri); MonetaryAmount avBtcAmount{75, "BTC", 4}; EXPECT_CALL(exchangePrivate, queryAccountBalance(balanceOptions)) .WillOnce(testing::Return(BalancePortfolio{from, avBtcAmount})) - .WillOnce( - testing::Return(BalancePortfolio{from + tradedAmounts1.tradedTo, avBtcAmount - tradedAmounts1.tradedFrom})) - .WillOnce(testing::Return(BalancePortfolio{avBtcAmount - tradedAmounts1.tradedFrom})); + .WillOnce(testing::Return(BalancePortfolio{from + tradedAmounts1.to, avBtcAmount - tradedAmounts1.from})) + .WillOnce(testing::Return(BalancePortfolio{avBtcAmount - tradedAmounts1.from})); TradedAmountsVector tradedAmountsVector{tradedAmounts1, tradedAmounts2}; TradedAmountsVectorWithFinalAmount res{tradedAmountsVector, MonetaryAmount{0, dustCur}}; @@ -762,7 +761,7 @@ TEST_F(ExchangePrivateDustSweeperTest, DustSweeper5Steps) { MonetaryAmount xrpDustThreshold = dustThreshold(dustCur).value_or(MonetaryAmount{-1}); TradedAmounts tradedAmounts1 = expectTakerBuy(xrpDustThreshold, xrpbtcAskPri, xrpbtcBidPri, xrpbtcMarket); - from += tradedAmounts1.tradedTo; + from += tradedAmounts1.to; BalancePortfolio balance2 = balance1 + tradedAmounts1; @@ -770,7 +769,7 @@ TEST_F(ExchangePrivateDustSweeperTest, DustSweeper5Steps) { int percentXRPSoldSecondStep = 80; TradedAmounts tradedAmounts2 = expectTakerSell(from, priBtc, percentXRPSoldSecondStep); - from -= tradedAmounts2.tradedFrom; + from -= tradedAmounts2.from; BalancePortfolio balance3 = balance2 + tradedAmounts2; @@ -784,7 +783,7 @@ TEST_F(ExchangePrivateDustSweeperTest, DustSweeper5Steps) { expectTakerBuy(xrpDustThreshold, xrpbtcAskPri, xrpbtcBidPri, xrpbtcMarket, false); TradedAmounts tradedAmounts3 = expectTakerBuy((3 * xrpDustThreshold) / 2, xrpeurAskPri, xrpeurBidPri, xrpeurMarket); - from += tradedAmounts3.tradedTo; + from += tradedAmounts3.to; BalancePortfolio balance4 = balance3 + tradedAmounts3; EXPECT_CALL(exchangePrivate, queryAccountBalance(balanceOptions)).WillOnce(testing::Return(balance4)); diff --git a/src/api/exchanges/src/binanceprivateapi.cpp b/src/api/exchanges/src/binanceprivateapi.cpp index dce47bba..d31d21fb 100644 --- a/src/api/exchanges/src/binanceprivateapi.cpp +++ b/src/api/exchanges/src/binanceprivateapi.cpp @@ -576,10 +576,10 @@ TradedAmounts ParseTrades(Market mk, CurrencyCode fromCurrencyCode, const json& MonetaryAmount fee(fillDetail["commission"].get(), fillDetail["commissionAsset"].get()); log::debug("Gross {} has been matched at {} price, with a fee of {}", quantity, price, fee); - if (fee.currencyCode() == detailTradedInfo.tradedFrom.currencyCode()) { - detailTradedInfo.tradedFrom += fee; - } else if (fee.currencyCode() == detailTradedInfo.tradedTo.currencyCode()) { - detailTradedInfo.tradedTo -= fee; + if (fee.currencyCode() == detailTradedInfo.from.currencyCode()) { + detailTradedInfo.from += fee; + } else if (fee.currencyCode() == detailTradedInfo.to.currencyCode()) { + detailTradedInfo.to -= fee; } else { log::debug("Fee is deduced from {} which is outside {}, do not count it in this trade", fee.currencyStr(), mk); } diff --git a/src/api/exchanges/src/bithumbprivateapi.cpp b/src/api/exchanges/src/bithumbprivateapi.cpp index 93cc434c..6130681e 100644 --- a/src/api/exchanges/src/bithumbprivateapi.cpp +++ b/src/api/exchanges/src/bithumbprivateapi.cpp @@ -900,11 +900,11 @@ OrderInfo BithumbPrivate::queryOrderInfo(OrderIdView orderId, const TradeContext MonetaryAmount fee(contractDetail["fee"].get(), feeCurrency); if (fromCurrencyCode == mk.quote()) { - orderInfo.tradedAmounts.tradedFrom += tradedCost + fee; - orderInfo.tradedAmounts.tradedTo += tradedVol; + orderInfo.tradedAmounts.from += tradedCost + fee; + orderInfo.tradedAmounts.to += tradedVol; } else { - orderInfo.tradedAmounts.tradedFrom += tradedVol; - orderInfo.tradedAmounts.tradedTo += tradedCost - fee; + orderInfo.tradedAmounts.from += tradedVol; + orderInfo.tradedAmounts.to += tradedCost - fee; } } } diff --git a/src/api/exchanges/src/krakenprivateapi.cpp b/src/api/exchanges/src/krakenprivateapi.cpp index e8f68726..c425d003 100644 --- a/src/api/exchanges/src/krakenprivateapi.cpp +++ b/src/api/exchanges/src/krakenprivateapi.cpp @@ -566,11 +566,11 @@ OrderInfo KrakenPrivate::queryOrderInfo(OrderIdView orderId, const TradeContext& if (fromCurrencyCode == mk.quote()) { MonetaryAmount price(orderJson["price"].get(), mk.base()); - orderInfo.tradedAmounts.tradedFrom += tradedCost; - orderInfo.tradedAmounts.tradedTo += (tradedCost - fee).toNeutral() / price; + orderInfo.tradedAmounts.from += tradedCost; + orderInfo.tradedAmounts.to += (tradedCost - fee).toNeutral() / price; } else { - orderInfo.tradedAmounts.tradedFrom += tradedVol; - orderInfo.tradedAmounts.tradedTo += tradedCost - fee; + orderInfo.tradedAmounts.from += tradedVol; + orderInfo.tradedAmounts.to += tradedCost - fee; } } diff --git a/src/api/exchanges/src/kucoinpublicapi.cpp b/src/api/exchanges/src/kucoinpublicapi.cpp index a39a1e51..c8a69000 100644 --- a/src/api/exchanges/src/kucoinpublicapi.cpp +++ b/src/api/exchanges/src/kucoinpublicapi.cpp @@ -285,15 +285,8 @@ MarketOrderBook KucoinPublic::OrderBookFunc::operator()(Market mk, int depth) { using OrderBookVec = vector; OrderBookVec orderBookLines; orderBookLines.reserve(static_cast(depth) * 2); - for (auto asksOrBids : {std::addressof(bids), std::addressof(asks)}) { - const bool isAsk = asksOrBids == std::addressof(asks); - if (isAsk) { - FillOrderBook(mk, depth, isAsk, asksOrBids->begin(), asksOrBids->end(), orderBookLines); - } else { - // Reverse iterate as they are received in descending order - FillOrderBook(mk, depth, isAsk, asksOrBids->rbegin(), asksOrBids->rend(), orderBookLines); - } - } + FillOrderBook(mk, depth, false, bids.begin(), bids.end(), orderBookLines); + FillOrderBook(mk, depth, true, asks.begin(), asks.end(), orderBookLines); return MarketOrderBook(mk, orderBookLines); } diff --git a/src/api/exchanges/src/upbitprivateapi.cpp b/src/api/exchanges/src/upbitprivateapi.cpp index 8a975c94..9150fdf8 100644 --- a/src/api/exchanges/src/upbitprivateapi.cpp +++ b/src/api/exchanges/src/upbitprivateapi.cpp @@ -470,17 +470,17 @@ OrderInfo ParseOrderJson(const json& orderJson, CurrencyCode fromCurrencyCode, M MonetaryAmount tradedCost(orderDetails["funds"].get(), mk.quote()); if (fromCurrencyCode == mk.quote()) { - orderInfo.tradedAmounts.tradedFrom += tradedCost; - orderInfo.tradedAmounts.tradedTo += tradedVol; + orderInfo.tradedAmounts.from += tradedCost; + orderInfo.tradedAmounts.to += tradedVol; } else { - orderInfo.tradedAmounts.tradedFrom += tradedVol; - orderInfo.tradedAmounts.tradedTo += tradedCost; + orderInfo.tradedAmounts.from += tradedVol; + orderInfo.tradedAmounts.to += tradedCost; } } if (fromCurrencyCode == mk.quote()) { - orderInfo.tradedAmounts.tradedFrom += fee; + orderInfo.tradedAmounts.from += fee; } else { - orderInfo.tradedAmounts.tradedTo -= fee; + orderInfo.tradedAmounts.to -= fee; } } diff --git a/src/api/exchanges/test/commonapi_test.hpp b/src/api/exchanges/test/commonapi_test.hpp index 0e585c89..f6f1e169 100644 --- a/src/api/exchanges/test/commonapi_test.hpp +++ b/src/api/exchanges/test/commonapi_test.hpp @@ -280,8 +280,8 @@ class TestAPI { TradeOptions tradeOptions(TradeMode::kSimulation); MonetaryAmount smallFrom = smallAmountIt->amount() / 100; MonetaryAmount bigFrom = bigAmountIt->amount().toNeutral() * bigAmountIt->price() * 100; - EXPECT_GT(exchangePrivateOpt->trade(smallFrom, mk.quote(), tradeOptions).tradedTo, 0); - EXPECT_NE(exchangePrivateOpt->trade(bigFrom, mk.base(), tradeOptions).tradedFrom, 0); + EXPECT_GT(exchangePrivateOpt->trade(smallFrom, mk.quote(), tradeOptions).to, 0); + EXPECT_NE(exchangePrivateOpt->trade(bigFrom, mk.base(), tradeOptions).from, 0); } } } diff --git a/src/api/interface/include/exchange.hpp b/src/api/interface/include/exchange.hpp index 6b4afa5b..24c50d9a 100644 --- a/src/api/interface/include/exchange.hpp +++ b/src/api/interface/include/exchange.hpp @@ -27,14 +27,14 @@ class Exchange { const api::ExchangePublic &apiPublic() const { return _exchangePublic; } api::ExchangePrivate &apiPrivate() { - if (_pExchangePrivate) { + if (hasPrivateAPI()) { return *_pExchangePrivate; } throw exception("No private key associated to exchange {}", name()); } const api::ExchangePrivate &apiPrivate() const { - if (_pExchangePrivate) { + if (hasPrivateAPI()) { return *_pExchangePrivate; } throw exception("No private key associated to exchange {}", name()); @@ -42,7 +42,7 @@ class Exchange { const ExchangeInfo &exchangeInfo() const { return _exchangeInfo; } - bool hasPrivateAPI() const { return _pExchangePrivate; } + bool hasPrivateAPI() const { return _pExchangePrivate != nullptr; } bool healthCheck() { return _exchangePublic.healthCheck(); } diff --git a/src/engine/include/coincenter.hpp b/src/engine/include/coincenter.hpp index 6f1aa4bd..84e8d87d 100644 --- a/src/engine/include/coincenter.hpp +++ b/src/engine/include/coincenter.hpp @@ -101,15 +101,15 @@ class Coincenter { /// If no exchange name is given, it will attempt to trade given amount on all exchanges with the sufficient balance. /// If exactly one private exchange is given, balance will not be queried and trade will be launched without balance /// check. - TradedAmountsPerExchange trade(MonetaryAmount startAmount, bool isPercentageTrade, CurrencyCode toCurrency, - std::span privateExchangeNames, const TradeOptions &tradeOptions); + TradeResultPerExchange trade(MonetaryAmount startAmount, bool isPercentageTrade, CurrencyCode toCurrency, + std::span privateExchangeNames, const TradeOptions &tradeOptions); - TradedAmountsPerExchange smartBuy(MonetaryAmount endAmount, std::span privateExchangeNames, - const TradeOptions &tradeOptions); + TradeResultPerExchange smartBuy(MonetaryAmount endAmount, std::span privateExchangeNames, + const TradeOptions &tradeOptions); - TradedAmountsPerExchange smartSell(MonetaryAmount startAmount, bool isPercentageTrade, - std::span privateExchangeNames, - const TradeOptions &tradeOptions); + TradeResultPerExchange smartSell(MonetaryAmount startAmount, bool isPercentageTrade, + std::span privateExchangeNames, + const TradeOptions &tradeOptions); /// Single withdraw of 'grossAmount' from 'fromExchangeName' to 'toExchangeName' DeliveredWithdrawInfoWithExchanges withdraw(MonetaryAmount grossAmount, bool isPercentageWithdraw, diff --git a/src/engine/include/exchangesorchestrator.hpp b/src/engine/include/exchangesorchestrator.hpp index d0c499c6..7befd1b5 100644 --- a/src/engine/include/exchangesorchestrator.hpp +++ b/src/engine/include/exchangesorchestrator.hpp @@ -52,15 +52,15 @@ class ExchangesOrchestrator { UniquePublicSelectedExchanges getExchangesTradingMarket(Market mk, ExchangeNameSpan exchangeNames); - TradedAmountsPerExchange trade(MonetaryAmount startAmount, bool isPercentageTrade, CurrencyCode toCurrency, - std::span privateExchangeNames, const TradeOptions &tradeOptions); + TradeResultPerExchange trade(MonetaryAmount from, bool isPercentageTrade, CurrencyCode toCurrency, + std::span privateExchangeNames, const TradeOptions &tradeOptions); - TradedAmountsPerExchange smartBuy(MonetaryAmount endAmount, std::span privateExchangeNames, - const TradeOptions &tradeOptions); + TradeResultPerExchange smartBuy(MonetaryAmount endAmount, std::span privateExchangeNames, + const TradeOptions &tradeOptions); - TradedAmountsPerExchange smartSell(MonetaryAmount startAmount, bool isPercentageTrade, - std::span privateExchangeNames, - const TradeOptions &tradeOptions); + TradeResultPerExchange smartSell(MonetaryAmount startAmount, bool isPercentageTrade, + std::span privateExchangeNames, + const TradeOptions &tradeOptions); TradedAmountsVectorWithFinalAmountPerExchange dustSweeper(std::span privateExchangeNames, CurrencyCode currencyCode); diff --git a/src/engine/include/iscompletecommand.hpp b/src/engine/include/iscompletecommand.hpp new file mode 100644 index 00000000..9b29c993 --- /dev/null +++ b/src/engine/include/iscompletecommand.hpp @@ -0,0 +1,8 @@ +#pragma once + +class IsCompleteCommandInterface { + public: + virtual bool isComplete() const = 0; + + virtual ~IsCompleteCommandInterface() = default; +}; \ No newline at end of file diff --git a/src/engine/include/queryresultprinter.hpp b/src/engine/include/queryresultprinter.hpp index 18c1c438..c0a969f8 100644 --- a/src/engine/include/queryresultprinter.hpp +++ b/src/engine/include/queryresultprinter.hpp @@ -44,20 +44,20 @@ class QueryResultPrinter { void printDepositInfo(CurrencyCode depositCurrencyCode, const WalletPerExchange &walletPerExchange) const; - void printTrades(const TradedAmountsPerExchange &tradedAmountsPerExchange, MonetaryAmount startAmount, + void printTrades(const TradeResultPerExchange &tradeResultPerExchange, MonetaryAmount startAmount, bool isPercentageTrade, CurrencyCode toCurrency, const TradeOptions &tradeOptions) const { - printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions, + printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions, CoincenterCommandType::kTrade); } - void printBuyTrades(const TradedAmountsPerExchange &tradedAmountsPerExchange, MonetaryAmount endAmount, + void printBuyTrades(const TradeResultPerExchange &tradeResultPerExchange, MonetaryAmount endAmount, const TradeOptions &tradeOptions) const { - printTrades(tradedAmountsPerExchange, endAmount, false, CurrencyCode(), tradeOptions, CoincenterCommandType::kBuy); + printTrades(tradeResultPerExchange, endAmount, false, CurrencyCode(), tradeOptions, CoincenterCommandType::kBuy); } - void printSellTrades(const TradedAmountsPerExchange &tradedAmountsPerExchange, MonetaryAmount startAmount, + void printSellTrades(const TradeResultPerExchange &tradeResultPerExchange, MonetaryAmount startAmount, bool isPercentageTrade, const TradeOptions &tradeOptions) const { - printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, CurrencyCode(), tradeOptions, + printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, CurrencyCode(), tradeOptions, CoincenterCommandType::kSell); } @@ -91,9 +91,8 @@ class QueryResultPrinter { CurrencyCode currencyCode) const; private: - void printTrades(const TradedAmountsPerExchange &tradedAmountsPerExchange, MonetaryAmount amount, - bool isPercentageTrade, CurrencyCode toCurrency, const TradeOptions &tradeOptions, - CoincenterCommandType commandType) const; + void printTrades(const TradeResultPerExchange &tradeResultPerExchange, MonetaryAmount amount, bool isPercentageTrade, + CurrencyCode toCurrency, const TradeOptions &tradeOptions, CoincenterCommandType commandType) const; void printTable(const SimpleTable &simpleTable) const; diff --git a/src/engine/include/queryresulttypes.hpp b/src/engine/include/queryresulttypes.hpp index 94651010..a25c855e 100644 --- a/src/engine/include/queryresulttypes.hpp +++ b/src/engine/include/queryresulttypes.hpp @@ -14,7 +14,7 @@ #include "exchangepublicapitypes.hpp" #include "marketorderbook.hpp" #include "monetaryamount.hpp" -#include "tradedamounts.hpp" +#include "traderesult.hpp" #include "wallet.hpp" #include "withdrawinfo.hpp" @@ -34,7 +34,7 @@ using MonetaryAmountPerExchange = FixedCapacityVector, kNbSupportedExchanges>; -using TradedAmountsPerExchange = SmallVector, kTypicalNbPrivateAccounts>; +using TradeResultPerExchange = SmallVector, kTypicalNbPrivateAccounts>; using TradedAmountsVectorWithFinalAmountPerExchange = SmallVector, kTypicalNbPrivateAccounts>; diff --git a/src/engine/include/stringoptionparser.hpp b/src/engine/include/stringoptionparser.hpp index 92fb54a2..1fa58f15 100644 --- a/src/engine/include/stringoptionparser.hpp +++ b/src/engine/include/stringoptionparser.hpp @@ -1,6 +1,9 @@ #pragma once +#include +#include #include +#include #include #include "cct_string.hpp" @@ -17,8 +20,8 @@ class StringOptionParser { using CurrenciesPrivateExchanges = std::tuple; using CurrencyPrivateExchanges = std::pair; using MonetaryAmountCurrencyPrivateExchanges = std::tuple; - using CurrencyFromToPrivateExchange = std::tuple; - using MonetaryAmountFromToPrivateExchange = std::tuple; + using CurrencyFromToPrivateExchange = std::pair; + using MonetaryAmountFromToPrivateExchange = std::tuple; using MonetaryAmountFromToPublicExchangeToCurrency = std::tuple; using CurrencyPublicExchanges = std::pair; using CurrenciesPublicExchanges = std::tuple; diff --git a/src/engine/include/traderesult.hpp b/src/engine/include/traderesult.hpp new file mode 100644 index 00000000..d4e570d2 --- /dev/null +++ b/src/engine/include/traderesult.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "iscompletecommand.hpp" +#include "monetaryamount.hpp" +#include "tradedamounts.hpp" + +namespace cct { +class TradeResult : public IsCompleteCommandInterface { + public: + enum class State : int8_t { kComplete, kPartial, kUntouched }; + + TradeResult() noexcept = default; + + TradeResult(const TradedAmounts &tradedAmounts, MonetaryAmount from) : _tradedAmounts(tradedAmounts), _from(from) {} + + const TradedAmounts &tradedAmounts() const { return _tradedAmounts; } + + MonetaryAmount from() const { return _from; } + + bool isComplete() const override { return state() == State::kComplete; } + + State state() const; + + std::string_view stateStr() const; + + constexpr bool operator==(const TradeResult &) const noexcept = default; + + private: + TradedAmounts _tradedAmounts; + MonetaryAmount _from; +}; + +} // namespace cct \ No newline at end of file diff --git a/src/engine/src/coincenter.cpp b/src/engine/src/coincenter.cpp index 2c3fce5a..8393ea4b 100644 --- a/src/engine/src/coincenter.cpp +++ b/src/engine/src/coincenter.cpp @@ -276,21 +276,21 @@ UniquePublicSelectedExchanges Coincenter::getExchangesTradingMarket(Market mk, E return _exchangesOrchestrator.getExchangesTradingMarket(mk, exchangeNames); } -TradedAmountsPerExchange Coincenter::trade(MonetaryAmount startAmount, bool isPercentageTrade, CurrencyCode toCurrency, - std::span privateExchangeNames, - const TradeOptions &tradeOptions) { +TradeResultPerExchange Coincenter::trade(MonetaryAmount startAmount, bool isPercentageTrade, CurrencyCode toCurrency, + std::span privateExchangeNames, + const TradeOptions &tradeOptions) { return _exchangesOrchestrator.trade(startAmount, isPercentageTrade, toCurrency, privateExchangeNames, tradeOptions); } -TradedAmountsPerExchange Coincenter::smartBuy(MonetaryAmount endAmount, - std::span privateExchangeNames, - const TradeOptions &tradeOptions) { +TradeResultPerExchange Coincenter::smartBuy(MonetaryAmount endAmount, + std::span privateExchangeNames, + const TradeOptions &tradeOptions) { return _exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions); } -TradedAmountsPerExchange Coincenter::smartSell(MonetaryAmount startAmount, bool isPercentageTrade, - std::span privateExchangeNames, - const TradeOptions &tradeOptions) { +TradeResultPerExchange Coincenter::smartSell(MonetaryAmount startAmount, bool isPercentageTrade, + std::span privateExchangeNames, + const TradeOptions &tradeOptions) { return _exchangesOrchestrator.smartSell(startAmount, isPercentageTrade, privateExchangeNames, tradeOptions); } diff --git a/src/engine/src/coincentercommands.cpp b/src/engine/src/coincentercommands.cpp index b6aa964a..7faa5a27 100644 --- a/src/engine/src/coincentercommands.cpp +++ b/src/engine/src/coincentercommands.cpp @@ -325,22 +325,21 @@ bool CoincenterCommands::addOption(const CoincenterCmdLineOptions &cmdLineOption if (!cmdLineOptions.withdraw.empty()) { StringOptionParser anyParser(cmdLineOptions.withdraw); - auto [amountToWithdraw, isPercentageWithdraw, fromExchange, toExchange] = - anyParser.getMonetaryAmountFromToPrivateExchange(); + auto [amountToWithdraw, isPercentageWithdraw, exchanges] = anyParser.getMonetaryAmountFromToPrivateExchange(); _commands.emplace_back(CoincenterCommandType::kWithdraw) .setAmount(amountToWithdraw) .setPercentageAmount(isPercentageWithdraw) - .setExchangeNames(ExchangeNames{std::move(fromExchange), std::move(toExchange)}) + .setExchangeNames(std::move(exchanges)) .setWithdrawOptions(ComputeWithdrawOptions(cmdLineOptions)); } if (!cmdLineOptions.withdrawAll.empty()) { StringOptionParser anyParser(cmdLineOptions.withdrawAll); - auto [curToWithdraw, fromExchange, toExchange] = anyParser.getCurrencyFromToPrivateExchange(); + auto [curToWithdraw, exchanges] = anyParser.getCurrencyFromToPrivateExchange(); _commands.emplace_back(CoincenterCommandType::kWithdraw) .setAmount(MonetaryAmount(100, curToWithdraw)) .setPercentageAmount(true) - .setExchangeNames(ExchangeNames{std::move(fromExchange), std::move(toExchange)}) + .setExchangeNames(std::move(exchanges)) .setWithdrawOptions(ComputeWithdrawOptions(cmdLineOptions)); } diff --git a/src/engine/src/exchangesorchestrator.cpp b/src/engine/src/exchangesorchestrator.cpp index 779ec9a8..b115a71b 100644 --- a/src/engine/src/exchangesorchestrator.cpp +++ b/src/engine/src/exchangesorchestrator.cpp @@ -37,7 +37,9 @@ #include "queryresulttypes.hpp" #include "requestsconfig.hpp" #include "threadpool.hpp" +#include "tradedamounts.hpp" #include "tradeoptions.hpp" +#include "traderesult.hpp" #include "wallet.hpp" #include "withdrawinfo.hpp" #include "withdrawoptions.hpp" @@ -420,28 +422,31 @@ ExchangeAmountPairVector ComputeExchangeAmountPairVector(CurrencyCode fromCurren return exchangeAmountPairVector; } -TradedAmountsPerExchange LaunchAndCollectTrades(ThreadPool &threadPool, ExchangeAmountMarketsPathVector::iterator first, - ExchangeAmountMarketsPathVector::iterator last, CurrencyCode toCurrency, - const TradeOptions &tradeOptions) { - TradedAmountsPerExchange tradeAmountsPerExchange(std::distance(first, last)); - threadPool.parallelTransform(first, last, tradeAmountsPerExchange.begin(), [toCurrency, &tradeOptions](auto &tuple) { +TradeResultPerExchange LaunchAndCollectTrades(ThreadPool &threadPool, ExchangeAmountMarketsPathVector::iterator first, + ExchangeAmountMarketsPathVector::iterator last, CurrencyCode toCurrency, + const TradeOptions &tradeOptions) { + TradeResultPerExchange tradeResultPerExchange(std::distance(first, last)); + threadPool.parallelTransform(first, last, tradeResultPerExchange.begin(), [toCurrency, &tradeOptions](auto &tuple) { Exchange *exchange = std::get<0>(tuple); - return std::make_pair( - exchange, exchange->apiPrivate().trade(std::get<1>(tuple), toCurrency, tradeOptions, std::get<2>(tuple))); + const MonetaryAmount from = std::get<1>(tuple); + TradedAmounts tradedAmounts = exchange->apiPrivate().trade(from, toCurrency, tradeOptions, std::get<2>(tuple)); + return std::make_pair(exchange, TradeResult(std::move(tradedAmounts), from)); }); - return tradeAmountsPerExchange; + return tradeResultPerExchange; } template -TradedAmountsPerExchange LaunchAndCollectTrades(ThreadPool &threadPool, Iterator first, Iterator last, - const TradeOptions &tradeOptions) { - TradedAmountsPerExchange tradeAmountsPerExchange(std::distance(first, last)); - threadPool.parallelTransform(first, last, tradeAmountsPerExchange.begin(), [&tradeOptions](auto &tuple) { +TradeResultPerExchange LaunchAndCollectTrades(ThreadPool &threadPool, Iterator first, Iterator last, + const TradeOptions &tradeOptions) { + TradeResultPerExchange tradeResultPerExchange(std::distance(first, last)); + threadPool.parallelTransform(first, last, tradeResultPerExchange.begin(), [&tradeOptions](auto &tuple) { Exchange *exchange = std::get<0>(tuple); - return std::make_pair(exchange, exchange->apiPrivate().trade(std::get<1>(tuple), std::get<2>(tuple), tradeOptions, - std::get<3>(tuple))); + const MonetaryAmount from = std::get<1>(tuple); + const CurrencyCode toCurrency = std::get<2>(tuple); + TradedAmounts tradedAmounts = exchange->apiPrivate().trade(from, toCurrency, tradeOptions, std::get<3>(tuple)); + return std::make_pair(exchange, TradeResult(std::move(tradedAmounts), from)); }); - return tradeAmountsPerExchange; + return tradeResultPerExchange; } ExchangeAmountMarketsPathVector CreateExchangeAmountMarketsPathVector(ExchangeRetriever exchangeRetriever, @@ -465,18 +470,18 @@ ExchangeAmountMarketsPathVector CreateExchangeAmountMarketsPathVector(ExchangeRe } // namespace -TradedAmountsPerExchange ExchangesOrchestrator::trade(MonetaryAmount startAmount, bool isPercentageTrade, - CurrencyCode toCurrency, - std::span privateExchangeNames, - const TradeOptions &tradeOptions) { +TradeResultPerExchange ExchangesOrchestrator::trade(MonetaryAmount from, bool isPercentageTrade, + CurrencyCode toCurrency, + std::span privateExchangeNames, + const TradeOptions &tradeOptions) { if (privateExchangeNames.size() == 1 && !isPercentageTrade) { // In this special case we don't need to call the balance - call trade directly Exchange &exchange = _exchangeRetriever.retrieveUniqueCandidate(privateExchangeNames.front()); - return {1, std::make_pair(std::addressof(exchange), - exchange.apiPrivate().trade(startAmount, toCurrency, tradeOptions))}; + TradedAmounts tradedAmounts = exchange.apiPrivate().trade(from, toCurrency, tradeOptions); + return {1, std::make_pair(&exchange, TradeResult(std::move(tradedAmounts), from))}; } - const CurrencyCode fromCurrency = startAmount.currencyCode(); + const CurrencyCode fromCurrency = from.currencyCode(); ExchangeAmountMarketsPathVector exchangeAmountMarketsPathVector = CreateExchangeAmountMarketsPathVector( _exchangeRetriever, getBalance(privateExchangeNames), fromCurrency, toCurrency, tradeOptions); @@ -494,13 +499,13 @@ TradedAmountsPerExchange ExchangesOrchestrator::trade(MonetaryAmount startAmount MonetaryAmount totalAvailableAmount = std::accumulate( exchangeAmountMarketsPathVector.begin(), exchangeAmountMarketsPathVector.end(), currentTotalAmount, [](MonetaryAmount tot, const auto &tuple) { return tot + std::get<1>(tuple); }); - startAmount = (totalAvailableAmount * startAmount.toNeutral()) / 100; + from = (totalAvailableAmount * from.toNeutral()) / 100; } - for (auto endIt = exchangeAmountMarketsPathVector.end(); it != endIt && currentTotalAmount < startAmount; ++it) { + for (auto endIt = exchangeAmountMarketsPathVector.end(); it != endIt && currentTotalAmount < from; ++it) { MonetaryAmount &amount = std::get<1>(*it); - if (currentTotalAmount + amount > startAmount) { + if (currentTotalAmount + amount > from) { // Cap last amount such that total start trade on all exchanges reaches exactly 'startAmount' - amount = startAmount - currentTotalAmount; + amount = from - currentTotalAmount; } currentTotalAmount += amount; } @@ -508,17 +513,17 @@ TradedAmountsPerExchange ExchangesOrchestrator::trade(MonetaryAmount startAmount if (currentTotalAmount == 0) { log::warn("No available {} to trade", fromCurrency); - } else if (currentTotalAmount < startAmount) { - log::warn("Will trade {} < {} amount", currentTotalAmount, startAmount); + } else if (currentTotalAmount < from) { + log::warn("Will trade {} < {} amount", currentTotalAmount, from); } /// We have enough total available amount. Launch all trades in parallel return LaunchAndCollectTrades(_threadPool, exchangeAmountMarketsPathVector.begin(), it, toCurrency, tradeOptions); } -TradedAmountsPerExchange ExchangesOrchestrator::smartBuy(MonetaryAmount endAmount, - std::span privateExchangeNames, - const TradeOptions &tradeOptions) { +TradeResultPerExchange ExchangesOrchestrator::smartBuy(MonetaryAmount endAmount, + std::span privateExchangeNames, + const TradeOptions &tradeOptions) { const CurrencyCode toCurrency = endAmount.currencyCode(); BalancePerExchange balancePerExchange = getBalance(privateExchangeNames); @@ -619,9 +624,9 @@ TradedAmountsPerExchange ExchangesOrchestrator::smartBuy(MonetaryAmount endAmoun return LaunchAndCollectTrades(_threadPool, trades.begin(), trades.end(), tradeOptions); } -TradedAmountsPerExchange ExchangesOrchestrator::smartSell(MonetaryAmount startAmount, bool isPercentageTrade, - std::span privateExchangeNames, - const TradeOptions &tradeOptions) { +TradeResultPerExchange ExchangesOrchestrator::smartSell(MonetaryAmount startAmount, bool isPercentageTrade, + std::span privateExchangeNames, + const TradeOptions &tradeOptions) { const CurrencyCode fromCurrency = startAmount.currencyCode(); // Retrieve amount per start amount currency for each exchange ExchangeAmountPairVector exchangeAmountPairVector = diff --git a/src/engine/src/queryresultprinter.cpp b/src/engine/src/queryresultprinter.cpp index 24a1b8f7..71f6d7b1 100644 --- a/src/engine/src/queryresultprinter.cpp +++ b/src/engine/src/queryresultprinter.cpp @@ -222,7 +222,7 @@ json TradeOptionsToJson(const TradeOptions &tradeOptions) { return ret; } -json TradesJson(const TradedAmountsPerExchange &tradedAmountsPerExchange, MonetaryAmount amount, bool isPercentageTrade, +json TradesJson(const TradeResultPerExchange &tradeResultPerExchange, MonetaryAmount amount, bool isPercentageTrade, CurrencyCode toCurrency, const TradeOptions &tradeOptions, CoincenterCommandType commandType) { json in; json fromJson; @@ -254,10 +254,12 @@ json TradesJson(const TradedAmountsPerExchange &tradedAmountsPerExchange, Moneta in.emplace("opt", std::move(inOpt)); json out = json::object(); - for (const auto &[exchangePtr, tradedAmount] : tradedAmountsPerExchange) { + for (const auto &[exchangePtr, tradeResult] : tradeResultPerExchange) { json tradedAmountPerExchangeJson; - tradedAmountPerExchangeJson.emplace("from", tradedAmount.tradedFrom.amountStr()); - tradedAmountPerExchangeJson.emplace("to", tradedAmount.tradedTo.amountStr()); + tradedAmountPerExchangeJson.emplace("from", tradeResult.from().amountStr()); + tradedAmountPerExchangeJson.emplace("status", tradeResult.stateStr()); + tradedAmountPerExchangeJson.emplace("tradedFrom", tradeResult.tradedAmounts().from.amountStr()); + tradedAmountPerExchangeJson.emplace("tradedTo", tradeResult.tradedAmounts().to.amountStr()); auto it = out.find(exchangePtr->name()); if (it == out.end()) { @@ -584,8 +586,8 @@ json DustSweeperJson(const TradedAmountsVectorWithFinalAmountPerExchange &traded json tradedAmountsArray = json::array_t(); for (const auto &tradedAmounts : tradedAmountsVectorWithFinalAmount.tradedAmountsVector) { json tradedAmountsData; - tradedAmountsData.emplace("from", tradedAmounts.tradedFrom.str()); - tradedAmountsData.emplace("to", tradedAmounts.tradedTo.str()); + tradedAmountsData.emplace("from", tradedAmounts.from.str()); + tradedAmountsData.emplace("to", tradedAmounts.to.str()); tradedAmountsArray.push_back(std::move(tradedAmountsData)); } @@ -753,11 +755,10 @@ void QueryResultPrinter::printDepositInfo(CurrencyCode depositCurrencyCode, logActivity(CoincenterCommandType::kDepositInfo, jsonData); } -void QueryResultPrinter::printTrades(const TradedAmountsPerExchange &tradedAmountsPerExchange, MonetaryAmount amount, +void QueryResultPrinter::printTrades(const TradeResultPerExchange &tradeResultPerExchange, MonetaryAmount amount, bool isPercentageTrade, CurrencyCode toCurrency, const TradeOptions &tradeOptions, CoincenterCommandType commandType) const { - json jsonData = - TradesJson(tradedAmountsPerExchange, amount, isPercentageTrade, toCurrency, tradeOptions, commandType); + json jsonData = TradesJson(tradeResultPerExchange, amount, isPercentageTrade, toCurrency, tradeOptions, commandType); switch (_apiOutputType) { case ApiOutputType::kFormattedTable: { string tradedFromStr("Traded from amount ("); @@ -766,10 +767,12 @@ void QueryResultPrinter::printTrades(const TradedAmountsPerExchange &tradedAmoun string tradedToStr("Traded to amount ("); tradedToStr.append(TradeModeToStr(tradeOptions.tradeMode())); tradedToStr.push_back(')'); - SimpleTable simpleTable("Exchange", "Account", std::move(tradedFromStr), std::move(tradedToStr)); - for (const auto &[exchangePtr, tradedAmount] : tradedAmountsPerExchange) { - simpleTable.emplace_back(exchangePtr->name(), exchangePtr->keyName(), tradedAmount.tradedFrom.str(), - tradedAmount.tradedTo.str()); + SimpleTable simpleTable("Exchange", "Account", "From", std::move(tradedFromStr), std::move(tradedToStr), + "Status"); + for (const auto &[exchangePtr, tradeResult] : tradeResultPerExchange) { + const TradedAmounts &tradedAmounts = tradeResult.tradedAmounts(); + simpleTable.emplace_back(exchangePtr->name(), exchangePtr->keyName(), tradeResult.from().str(), + tradedAmounts.from.str(), tradedAmounts.to.str(), tradeResult.stateStr()); } printTable(simpleTable); break; diff --git a/src/engine/src/stringoptionparser.cpp b/src/engine/src/stringoptionparser.cpp index 1d3cddf7..7b74b5f4 100644 --- a/src/engine/src/stringoptionparser.cpp +++ b/src/engine/src/stringoptionparser.cpp @@ -183,18 +183,20 @@ StringOptionParser::getMonetaryAmountCurrencyPrivateExchanges(bool withCurrency) StringOptionParser::CurrencyFromToPrivateExchange StringOptionParser::getCurrencyFromToPrivateExchange() const { std::size_t pos = 0; CurrencyCode cur(GetNextStr(_opt, ',', pos)); - ExchangeName from = GetNextExchangeName(_opt, '-', pos); - // Warning: in C++, order of evaluation of parameters is unspecified. Because GetNextStr has side - // effects (it modifies 'pos') we need temporary variables here - return std::make_tuple(std::move(cur), std::move(from), GetNextExchangeName(_opt, '-', pos)); + // Warning: in C++, order of evaluation of parameters is unspecified, so parsing of exchanges (with + // GetNextExchangeName) should be done at different lines + ExchangeNames exchangePair(1U, GetNextExchangeName(_opt, '-', pos)); + exchangePair.push_back(GetNextExchangeName(_opt, '-', pos)); + return std::make_pair(std::move(cur), std::move(exchangePair)); } StringOptionParser::MonetaryAmountFromToPrivateExchange StringOptionParser::getMonetaryAmountFromToPrivateExchange() const { std::size_t pos = 0; auto [startAmount, isPercentage] = GetNextPercentageAmount(_opt, ",%", pos); - ExchangeName from = GetNextExchangeName(_opt, '-', pos); - return std::make_tuple(std::move(startAmount), isPercentage, std::move(from), GetNextExchangeName(_opt, '-', pos)); + ExchangeNames exchangePair(1U, GetNextExchangeName(_opt, '-', pos)); + exchangePair.push_back(GetNextExchangeName(_opt, '-', pos)); + return std::make_tuple(std::move(startAmount), isPercentage, std::move(exchangePair)); } std::size_t StringOptionParser::getNextCommaPos(std::size_t startPos, bool throwIfNone) const { diff --git a/src/engine/src/traderesult.cpp b/src/engine/src/traderesult.cpp new file mode 100644 index 00000000..f680ce38 --- /dev/null +++ b/src/engine/src/traderesult.cpp @@ -0,0 +1,31 @@ +#include "traderesult.hpp" + +#include + +#include "cct_exception.hpp" + +namespace cct { +TradeResult::State TradeResult::state() const { + // from could be lower than actually traded from amount if rounding issues + if (_from <= _tradedAmounts.from) { + return TradeResult::State::kComplete; + } + if (_tradedAmounts.from > 0) { + return TradeResult::State::kPartial; + } + return TradeResult::State::kUntouched; +} + +std::string_view TradeResult::stateStr() const { + switch (state()) { + case State::kComplete: + return "Complete"; + case State::kPartial: + return "Partial"; + case State::kUntouched: + return "Untouched"; + default: + throw exception("Invalid state {}", static_cast(state())); + } +} +} // namespace cct diff --git a/src/engine/test/exchangesorchestrator_trade_test.cpp b/src/engine/test/exchangesorchestrator_trade_test.cpp index 65c70b1f..30e84de9 100644 --- a/src/engine/test/exchangesorchestrator_trade_test.cpp +++ b/src/engine/test/exchangesorchestrator_trade_test.cpp @@ -319,7 +319,7 @@ TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeBuy) { const ExchangeName privateExchangeNames[] = {ExchangeName(exchange1.name(), exchange1.keyName())}; EXPECT_EQ(exchangesOrchestrator.trade(from, isPercentageTrade, toCurrency, privateExchangeNames, tradeOptions), - TradedAmountsPerExchange(1, std::make_pair(&exchange1, tradedAmounts))); + TradeResultPerExchange(1, std::make_pair(&exchange1, TradeResult(tradedAmounts, from)))); } TEST_F(ExchangeOrchestratorTradeTest, NoAvailableAmountToSell) { @@ -340,7 +340,7 @@ TEST_F(ExchangeOrchestratorTradeTest, NoAvailableAmountToSell) { AllOrderBooks::kExpectNoCall, true); EXPECT_EQ(exchangesOrchestrator.trade(from, isPercentageTrade, toCurrency, privateExchangeNames, tradeOptions), - TradedAmountsPerExchange{}); + TradeResultPerExchange{}); } TEST_F(ExchangeOrchestratorTradeTest, TwoAccountsSameExchangeSell) { @@ -363,10 +363,10 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoAccountsSameExchangeSell) { OrderBook::kExpect2Calls, AllOrderBooks::kExpectNoCall, true); TradedAmounts tradedAmounts4 = expectSingleTrade(4, traded2, toCurrency, side, TradableMarkets::kNoExpectation, OrderBook::kNoExpectation, AllOrderBooks::kNoExpectation, true); - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange3, tradedAmounts3), - std::make_pair(&exchange4, tradedAmounts4)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange3, TradeResult(tradedAmounts3, traded1)), + std::make_pair(&exchange4, TradeResult(tradedAmounts4, traded2))}; EXPECT_EQ(exchangesOrchestrator.trade(from, isPercentageTrade, toCurrency, privateExchangeNames, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesBuy) { @@ -391,11 +391,11 @@ TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesBuy) { TradedAmounts tradedAmounts3 = expectSingleTrade(3, from3, toCurrency, side, TradableMarkets::kExpectCall, OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange2, tradedAmounts2), - std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange3, tradedAmounts3)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2)), + std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3))}; EXPECT_EQ(exchangesOrchestrator.trade(from, isPercentageTrade, toCurrency, ExchangeNames{}, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesBuyNotEnoughAmount) { @@ -422,12 +422,12 @@ TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesBuyNotEnoughAmount) { TradedAmounts tradedAmounts4 = expectSingleTrade(4, from4, toCurrency, side, TradableMarkets::kNoExpectation, OrderBook::kNoExpectation, AllOrderBooks::kNoExpectation, true); - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange2, tradedAmounts2), - std::make_pair(&exchange3, tradedAmounts3), - std::make_pair(&exchange4, tradedAmounts4)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2)), + std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3)), + std::make_pair(&exchange4, TradeResult(tradedAmounts4, from4))}; EXPECT_EQ(exchangesOrchestrator.trade(from, isPercentageTrade, toCurrency, ExchangeNames{}, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, ManyAccountsTrade) { @@ -467,14 +467,17 @@ TEST_F(ExchangeOrchestratorTradeTest, ManyAccountsTrade) { OrderBook::kNoExpectation, AllOrderBooks::kNoExpectation, true); TradedAmounts tradedAmounts8 = expectSingleTrade(8, from1, toCurrency, side, TradableMarkets::kNoExpectation, OrderBook::kNoExpectation, AllOrderBooks::kNoExpectation, true); - TradedAmountsPerExchange tradedAmountsPerExchange{ - std::make_pair(&exchange2, tradedAmounts2), std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange8, tradedAmounts8), std::make_pair(&exchange5, tradedAmounts5), - std::make_pair(&exchange6, tradedAmounts6), std::make_pair(&exchange7, tradedAmounts7), - std::make_pair(&exchange3, tradedAmounts3), std::make_pair(&exchange4, tradedAmounts4)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2)), + std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange8, TradeResult(tradedAmounts8, from1)), + std::make_pair(&exchange5, TradeResult(tradedAmounts5, from1)), + std::make_pair(&exchange6, TradeResult(tradedAmounts6, from1)), + std::make_pair(&exchange7, TradeResult(tradedAmounts7, from1)), + std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3)), + std::make_pair(&exchange4, TradeResult(tradedAmounts4, from4))}; EXPECT_EQ(exchangesOrchestrator.trade(from, isPercentageTrade, toCurrency, ExchangeNames{}, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeBuyAll) { @@ -486,15 +489,15 @@ TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeBuyAll) { EXPECT_CALL(exchangePrivate3, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio3)); - TradedAmounts tradedAmounts3 = - expectSingleTrade(3, MonetaryAmount(1500, fromCurrency), toCurrency, side, TradableMarkets::kExpectCall, - OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); + MonetaryAmount from(1500, fromCurrency); + TradedAmounts tradedAmounts3 = expectSingleTrade(3, from, toCurrency, side, TradableMarkets::kExpectCall, + OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); constexpr bool kIsPercentageTrade = true; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange3, tradedAmounts3)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange3, TradeResult(tradedAmounts3, from))}; EXPECT_EQ(exchangesOrchestrator.trade(MonetaryAmount(100, fromCurrency), kIsPercentageTrade, toCurrency, privateExchangeNames, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSellAll) { @@ -510,19 +513,19 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSellAll) { EXPECT_CALL(exchangePrivate2, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio2)); EXPECT_CALL(exchangePrivate3, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio3)); - TradedAmounts tradedAmounts1 = - expectSingleTrade(1, balancePortfolio1.get(fromCurrency), toCurrency, side, TradableMarkets::kExpectCall, - OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); - TradedAmounts tradedAmounts3 = - expectSingleTrade(3, balancePortfolio3.get(fromCurrency), toCurrency, side, TradableMarkets::kExpectCall, - OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); + const auto from1 = balancePortfolio1.get(fromCurrency); + const auto from3 = balancePortfolio3.get(fromCurrency); + TradedAmounts tradedAmounts1 = expectSingleTrade(1, from1, toCurrency, side, TradableMarkets::kExpectCall, + OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); + TradedAmounts tradedAmounts3 = expectSingleTrade(3, from3, toCurrency, side, TradableMarkets::kExpectCall, + OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); constexpr bool kIsPercentageTrade = true; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange3, tradedAmounts3)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3))}; EXPECT_EQ(exchangesOrchestrator.trade(MonetaryAmount(100, fromCurrency), kIsPercentageTrade, toCurrency, privateExchangeNames, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, AllExchangesBuyAllOneMarketUnavailable) { @@ -542,23 +545,23 @@ TEST_F(ExchangeOrchestratorTradeTest, AllExchangesBuyAllOneMarketUnavailable) { expectSingleTrade(1, MonetaryAmount(0, fromCurrency), toCurrency, side, TradableMarkets::kExpectCall, OrderBook::kExpectNoCall, AllOrderBooks::kExpectNoCall, false); - TradedAmounts tradedAmounts2 = - expectSingleTrade(2, balancePortfolio2.get(fromCurrency), toCurrency, side, TradableMarkets::kExpectCall, - OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); - TradedAmounts tradedAmounts3 = - expectSingleTrade(3, balancePortfolio3.get(fromCurrency), toCurrency, side, TradableMarkets::kExpectCall, - OrderBook::kExpect2Calls, AllOrderBooks::kExpectNoCall, true); - TradedAmounts tradedAmounts4 = - expectSingleTrade(4, balancePortfolio4.get(fromCurrency), toCurrency, side, TradableMarkets::kNoExpectation, - OrderBook::kNoExpectation, AllOrderBooks::kNoExpectation, true); + const auto from2 = balancePortfolio2.get(fromCurrency); + const auto from3 = balancePortfolio3.get(fromCurrency); + const auto from4 = balancePortfolio4.get(fromCurrency); + TradedAmounts tradedAmounts2 = expectSingleTrade(2, from2, toCurrency, side, TradableMarkets::kExpectCall, + OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); + TradedAmounts tradedAmounts3 = expectSingleTrade(3, from3, toCurrency, side, TradableMarkets::kExpectCall, + OrderBook::kExpect2Calls, AllOrderBooks::kExpectNoCall, true); + TradedAmounts tradedAmounts4 = expectSingleTrade(4, from4, toCurrency, side, TradableMarkets::kNoExpectation, + OrderBook::kNoExpectation, AllOrderBooks::kNoExpectation, true); constexpr bool kIsPercentageTrade = true; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange2, tradedAmounts2), - std::make_pair(&exchange3, tradedAmounts3), - std::make_pair(&exchange4, tradedAmounts4)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2)), + std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3)), + std::make_pair(&exchange4, TradeResult(tradedAmounts4, from4))}; EXPECT_EQ(exchangesOrchestrator.trade(MonetaryAmount(100, fromCurrency), kIsPercentageTrade, toCurrency, privateExchangeNames, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeSmartBuy) { @@ -567,17 +570,17 @@ TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeSmartBuy) { CurrencyCode toCurrency = endAmount.currencyCode(); TradeSide side = TradeSide::kBuy; - MonetaryAmount from = MonetaryAmount(1000, "USDT"); + MonetaryAmount from1 = MonetaryAmount(1000, "USDT"); - TradedAmounts tradedAmounts1 = expectSingleTrade(1, from, toCurrency, side, TradableMarkets::kExpectCall, + TradedAmounts tradedAmounts1 = expectSingleTrade(1, from1, toCurrency, side, TradableMarkets::kExpectCall, OrderBook::kExpectCall, AllOrderBooks::kExpectCall, true); EXPECT_CALL(exchangePrivate1, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio1)); const ExchangeName privateExchangeNames[] = {ExchangeName(exchange1.name(), exchange1.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1)}; - EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradedAmountsPerExchange); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1))}; + EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeSmartBuyTwoSteps) { @@ -587,17 +590,17 @@ TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeSmartBuyTwoSteps) { CurrencyCode toCurrency = endAmount.currencyCode(); TradeSide side = TradeSide::kBuy; - MonetaryAmount from = MonetaryAmount(1000, "USDT"); + MonetaryAmount from1 = MonetaryAmount(1000, "USDT"); - TradedAmounts tradedAmounts1 = expectTwoStepTrade(1, from, toCurrency, side, TradableMarkets::kExpectCall, + TradedAmounts tradedAmounts1 = expectTwoStepTrade(1, from1, toCurrency, side, TradableMarkets::kExpectCall, OrderBook::kExpectCall, AllOrderBooks::kExpectCall, true); EXPECT_CALL(exchangePrivate1, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio1)); const ExchangeName privateExchangeNames[] = {ExchangeName(exchange1.name(), exchange1.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1)}; - EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradedAmountsPerExchange); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1))}; + EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartBuy) { @@ -622,10 +625,10 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartBuy) { const ExchangeName privateExchangeNames[] = {ExchangeName(exchange3.name(), exchange3.keyName()), ExchangeName(exchange1.name(), exchange1.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange3, tradedAmounts31), - std::make_pair(&exchange3, tradedAmounts32)}; - EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradedAmountsPerExchange); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange3, TradeResult(tradedAmounts31, from31)), + std::make_pair(&exchange3, TradeResult(tradedAmounts32, from32))}; + EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartBuyNoMarketOnOneExchange) { @@ -647,8 +650,8 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartBuyNoMarketOnOneExchange) const ExchangeName privateExchangeNames[] = {ExchangeName(exchange3.name(), exchange3.keyName()), ExchangeName(exchange1.name(), exchange1.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange3, tradedAmounts3)}; - EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradedAmountsPerExchange); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3))}; + EXPECT_EQ(exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions), tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesSmartBuy) { @@ -683,9 +686,9 @@ TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesSmartBuy) { ExchangeName(exchange2.name(), exchange2.keyName()), ExchangeName(exchange1.name(), exchange1.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange4, tradedAmounts4)}; - EXPECT_EQ(tradedAmountsPerExchange, exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions)); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange4, TradeResult(tradedAmounts4, from42))}; + EXPECT_EQ(tradeResultPerExchange, exchangesOrchestrator.smartBuy(endAmount, privateExchangeNames, tradeOptions)); } TEST_F(ExchangeOrchestratorTradeTest, SmartBuyAllExchanges) { @@ -718,11 +721,13 @@ TEST_F(ExchangeOrchestratorTradeTest, SmartBuyAllExchanges) { EXPECT_CALL(exchangePrivate3, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio3)); EXPECT_CALL(exchangePrivate4, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio4)); - TradedAmountsPerExchange tradedAmountsPerExchange{ - std::make_pair(&exchange2, tradedAmounts2), std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange3, tradedAmounts32), std::make_pair(&exchange3, tradedAmounts31), - std::make_pair(&exchange4, tradedAmounts42), std::make_pair(&exchange4, tradedAmounts41)}; - EXPECT_EQ(tradedAmountsPerExchange, exchangesOrchestrator.smartBuy(endAmount, ExchangeNames{}, tradeOptions)); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2)), + std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange3, TradeResult(tradedAmounts32, from32)), + std::make_pair(&exchange3, TradeResult(tradedAmounts31, from31)), + std::make_pair(&exchange4, TradeResult(tradedAmounts42, from42)), + std::make_pair(&exchange4, TradeResult(tradedAmounts41, from41))}; + EXPECT_EQ(tradeResultPerExchange, exchangesOrchestrator.smartBuy(endAmount, ExchangeNames{}, tradeOptions)); } TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeSmartSell) { @@ -730,18 +735,18 @@ TEST_F(ExchangeOrchestratorTradeTest, SingleExchangeSmartSell) { CurrencyCode toCurrency("USDT"); TradeSide side = TradeSide::kSell; - MonetaryAmount from = MonetaryAmount("1.5ETH"); + MonetaryAmount from1 = MonetaryAmount("1.5ETH"); - TradedAmounts tradedAmounts1 = expectSingleTrade(1, from, toCurrency, side, TradableMarkets::kExpectCall, + TradedAmounts tradedAmounts1 = expectSingleTrade(1, from1, toCurrency, side, TradableMarkets::kExpectCall, OrderBook::kExpectCall, AllOrderBooks::kExpectNoCall, true); EXPECT_CALL(exchangePrivate1, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio1)); const ExchangeName privateExchangeNames[] = {ExchangeName(exchange1.name(), exchange1.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1))}; EXPECT_EQ(exchangesOrchestrator.smartSell(startAmount, false, privateExchangeNames, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, SmartSellAllNoAvailableAmount) { @@ -779,9 +784,9 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartSell) { const ExchangeName privateExchangeNames[] = {ExchangeName(exchange1.name(), exchange1.keyName()), ExchangeName(exchange2.name(), exchange2.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1), - std::make_pair(&exchange2, tradedAmounts2)}; - EXPECT_EQ(tradedAmountsPerExchange, + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1)), + std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2))}; + EXPECT_EQ(tradeResultPerExchange, exchangesOrchestrator.smartSell(startAmount, false, privateExchangeNames, tradeOptions)); } @@ -805,8 +810,8 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartSellPercentage) { const ExchangeName privateExchangeNames[] = {ExchangeName(exchange1.name(), exchange1.keyName()), ExchangeName(exchange3.name(), exchange3.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1)}; - EXPECT_EQ(tradedAmountsPerExchange, + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1))}; + EXPECT_EQ(tradeResultPerExchange, exchangesOrchestrator.smartSell(startAmount, true, privateExchangeNames, tradeOptions)); } @@ -829,9 +834,9 @@ TEST_F(ExchangeOrchestratorTradeTest, TwoExchangesSmartSellNoMarketOnOneExchange const ExchangeName privateExchangeNames[] = {ExchangeName(exchange2.name(), exchange2.keyName()), ExchangeName(exchange3.name(), exchange3.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange2, tradedAmounts2)}; + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange2, TradeResult(tradedAmounts2, from2))}; EXPECT_EQ(exchangesOrchestrator.smartSell(startAmount, false, privateExchangeNames, tradeOptions), - tradedAmountsPerExchange); + tradeResultPerExchange); } TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesSmartSellFromAnotherPreferredCurrency) { @@ -858,9 +863,9 @@ TEST_F(ExchangeOrchestratorTradeTest, ThreeExchangesSmartSellFromAnotherPreferre ExchangeName(exchange1.name(), exchange1.keyName()), ExchangeName(exchange3.name(), exchange3.keyName())}; - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange3, tradedAmounts3), - std::make_pair(&exchange4, tradedAmounts4)}; - EXPECT_EQ(tradedAmountsPerExchange, + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange3, TradeResult(tradedAmounts3, from3)), + std::make_pair(&exchange4, TradeResult(tradedAmounts4, from4))}; + EXPECT_EQ(tradeResultPerExchange, exchangesOrchestrator.smartSell(startAmount, false, privateExchangeNames, tradeOptions)); } @@ -888,8 +893,7 @@ TEST_F(ExchangeOrchestratorTradeTest, SmartSellAllExchanges) { EXPECT_CALL(exchangePrivate3, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio3)); EXPECT_CALL(exchangePrivate4, queryAccountBalance(testing::_)).WillOnce(testing::Return(balancePortfolio4)); - TradedAmountsPerExchange tradedAmountsPerExchange{std::make_pair(&exchange1, tradedAmounts1)}; - EXPECT_EQ(tradedAmountsPerExchange, - exchangesOrchestrator.smartSell(startAmount, false, ExchangeNames{}, tradeOptions)); + TradeResultPerExchange tradeResultPerExchange{std::make_pair(&exchange1, TradeResult(tradedAmounts1, from1))}; + EXPECT_EQ(tradeResultPerExchange, exchangesOrchestrator.smartSell(startAmount, false, ExchangeNames{}, tradeOptions)); } } // namespace cct diff --git a/src/engine/test/queryresultprinter_private_test.cpp b/src/engine/test/queryresultprinter_private_test.cpp index 4ecabc34..c729f061 100644 --- a/src/engine/test/queryresultprinter_private_test.cpp +++ b/src/engine/test/queryresultprinter_private_test.cpp @@ -20,6 +20,7 @@ #include "queryresulttypes.hpp" #include "tradedamounts.hpp" #include "tradeoptions.hpp" +#include "traderesult.hpp" #include "tradeside.hpp" #include "wallet.hpp" #include "withdraw.hpp" @@ -510,23 +511,26 @@ class QueryResultPrinterTradesAmountTest : public QueryResultPrinterTest { bool isPercentageTrade{false}; CurrencyCode toCurrency{"XRP"}; TradeOptions tradeOptions; - TradedAmountsPerExchange tradedAmountsPerExchange{ - {&exchange1, TradedAmounts{MonetaryAmount("0.1BTC"), MonetaryAmount("1050XRP")}}, - {&exchange3, TradedAmounts{MonetaryAmount("0.3BTC"), MonetaryAmount("3500.6XRP")}}, - {&exchange4, TradedAmounts{MonetaryAmount(0, "BTC"), MonetaryAmount(0, "XRP")}}}; + TradedAmounts tradedAmounts1{MonetaryAmount("0.1BTC"), MonetaryAmount("1050XRP")}; + TradedAmounts tradedAmounts3{MonetaryAmount("0.3BTC"), MonetaryAmount("3500.6XRP")}; + TradedAmounts tradedAmounts4{MonetaryAmount(0, "BTC"), MonetaryAmount(0, "XRP")}; + + TradeResultPerExchange tradeResultPerExchange{{&exchange1, TradeResult{tradedAmounts1, tradedAmounts1.from}}, + {&exchange3, TradeResult{tradedAmounts3, tradedAmounts3.from * 2}}, + {&exchange4, TradeResult{tradedAmounts4, MonetaryAmount(1, "BTC")}}}; }; TEST_F(QueryResultPrinterTradesAmountTest, FormattedTable) { basicQueryResultPrinter(ApiOutputType::kFormattedTable) - .printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); static constexpr std::string_view kExpected = R"( -+----------+-----------+---------------------------+-------------------------+ -| Exchange | Account | Traded from amount (real) | Traded to amount (real) | -+----------+-----------+---------------------------+-------------------------+ -| binance | testuser1 | 0.1 BTC | 1050 XRP | -| huobi | testuser1 | 0.3 BTC | 3500.6 XRP | -| huobi | testuser2 | 0 BTC | 0 XRP | -+----------+-----------+---------------------------+-------------------------+ ++----------+-----------+---------+---------------------------+-------------------------+-----------+ +| Exchange | Account | From | Traded from amount (real) | Traded to amount (real) | Status | ++----------+-----------+---------+---------------------------+-------------------------+-----------+ +| binance | testuser1 | 0.1 BTC | 0.1 BTC | 1050 XRP | Complete | +| huobi | testuser1 | 0.6 BTC | 0.3 BTC | 3500.6 XRP | Partial | +| huobi | testuser2 | 1 BTC | 0 BTC | 0 XRP | Untouched | ++----------+-----------+---------+---------------------------+-------------------------+-----------+ )"; expectStr(kExpected); @@ -534,7 +538,7 @@ TEST_F(QueryResultPrinterTradesAmountTest, FormattedTable) { TEST_F(QueryResultPrinterTradesAmountTest, EmptyJson) { basicQueryResultPrinter(ApiOutputType::kJson) - .printTrades(TradedAmountsPerExchange{}, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(TradeResultPerExchange{}, startAmount, isPercentageTrade, toCurrency, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -567,7 +571,7 @@ TEST_F(QueryResultPrinterTradesAmountTest, EmptyJson) { TEST_F(QueryResultPrinterTradesAmountTest, Json) { basicQueryResultPrinter(ApiOutputType::kJson) - .printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -597,17 +601,23 @@ TEST_F(QueryResultPrinterTradesAmountTest, Json) { "binance": { "testuser1": { "from": "0.1", - "to": "1050" + "status": "Complete", + "tradedFrom": "0.1", + "tradedTo": "1050" } }, "huobi": { "testuser1": { - "from": "0.3", - "to": "3500.6" + "from": "0.6", + "status": "Partial", + "tradedFrom": "0.3", + "tradedTo": "3500.6" }, "testuser2": { - "from": "0", - "to": "0" + "from": "1", + "status": "Untouched", + "tradedFrom": "0", + "tradedTo": "0" } } } @@ -617,7 +627,7 @@ TEST_F(QueryResultPrinterTradesAmountTest, Json) { TEST_F(QueryResultPrinterTradesAmountTest, NoPrint) { basicQueryResultPrinter(ApiOutputType::kNoPrint) - .printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); expectNoStr(); } @@ -627,26 +637,26 @@ class QueryResultPrinterTradesPercentageTest : public QueryResultPrinterTest { bool isPercentageTrade{true}; CurrencyCode toCurrency{"SHIB"}; TradeOptions tradeOptions{PriceOptions{PriceStrategy::kTaker}}; - TradedAmountsPerExchange tradedAmountsPerExchange{ - {&exchange2, TradedAmounts{MonetaryAmount("15000.56EUR"), MonetaryAmount("885475102SHIB")}}}; + TradedAmounts tradedAmounts{MonetaryAmount("15000.56EUR"), MonetaryAmount("885475102SHIB")}; + TradeResultPerExchange tradeResultPerExchange{{&exchange2, TradeResult{tradedAmounts, tradedAmounts.from * 2}}}; }; TEST_F(QueryResultPrinterTradesPercentageTest, FormattedTable) { basicQueryResultPrinter(ApiOutputType::kFormattedTable) - .printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); static constexpr std::string_view kExpected = R"( -+----------+-----------+---------------------------+-------------------------+ -| Exchange | Account | Traded from amount (real) | Traded to amount (real) | -+----------+-----------+---------------------------+-------------------------+ -| bithumb | testuser1 | 15000.56 EUR | 885475102 SHIB | -+----------+-----------+---------------------------+-------------------------+ ++----------+-----------+--------------+---------------------------+-------------------------+---------+ +| Exchange | Account | From | Traded from amount (real) | Traded to amount (real) | Status | ++----------+-----------+--------------+---------------------------+-------------------------+---------+ +| bithumb | testuser1 | 30001.12 EUR | 15000.56 EUR | 885475102 SHIB | Partial | ++----------+-----------+--------------+---------------------------+-------------------------+---------+ )"; expectStr(kExpected); } TEST_F(QueryResultPrinterTradesPercentageTest, EmptyJson) { basicQueryResultPrinter(ApiOutputType::kJson) - .printTrades(TradedAmountsPerExchange{}, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(TradeResultPerExchange{}, startAmount, isPercentageTrade, toCurrency, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -679,7 +689,7 @@ TEST_F(QueryResultPrinterTradesPercentageTest, EmptyJson) { TEST_F(QueryResultPrinterTradesPercentageTest, Json) { basicQueryResultPrinter(ApiOutputType::kJson) - .printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -708,8 +718,10 @@ TEST_F(QueryResultPrinterTradesPercentageTest, Json) { "out": { "bithumb": { "testuser1": { - "from": "15000.56", - "to": "885475102" + "from": "30001.12", + "status": "Partial", + "tradedFrom": "15000.56", + "tradedTo": "885475102" } } } @@ -719,7 +731,7 @@ TEST_F(QueryResultPrinterTradesPercentageTest, Json) { TEST_F(QueryResultPrinterTradesPercentageTest, NoPrint) { basicQueryResultPrinter(ApiOutputType::kNoPrint) - .printTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); + .printTrades(tradeResultPerExchange, startAmount, isPercentageTrade, toCurrency, tradeOptions); expectNoStr(); } @@ -727,25 +739,25 @@ class QueryResultPrinterSmartBuyTest : public QueryResultPrinterTest { protected: MonetaryAmount endAmount{"3ETH"}; TradeOptions tradeOptions; - TradedAmountsPerExchange tradedAmountsPerExchange{ - {&exchange1, TradedAmounts{MonetaryAmount("4500.67EUR"), MonetaryAmount("3ETH")}}}; + TradedAmounts tradedAmounts{MonetaryAmount("4500.67EUR"), MonetaryAmount("3ETH")}; + TradeResultPerExchange tradeResultPerExchange{{&exchange1, TradeResult{tradedAmounts, tradedAmounts.from}}}; }; TEST_F(QueryResultPrinterSmartBuyTest, FormattedTable) { basicQueryResultPrinter(ApiOutputType::kFormattedTable) - .printBuyTrades(tradedAmountsPerExchange, endAmount, tradeOptions); + .printBuyTrades(tradeResultPerExchange, endAmount, tradeOptions); static constexpr std::string_view kExpected = R"( -+----------+-----------+---------------------------+-------------------------+ -| Exchange | Account | Traded from amount (real) | Traded to amount (real) | -+----------+-----------+---------------------------+-------------------------+ -| binance | testuser1 | 4500.67 EUR | 3 ETH | -+----------+-----------+---------------------------+-------------------------+ ++----------+-----------+-------------+---------------------------+-------------------------+----------+ +| Exchange | Account | From | Traded from amount (real) | Traded to amount (real) | Status | ++----------+-----------+-------------+---------------------------+-------------------------+----------+ +| binance | testuser1 | 4500.67 EUR | 4500.67 EUR | 3 ETH | Complete | ++----------+-----------+-------------+---------------------------+-------------------------+----------+ )"; expectStr(kExpected); } TEST_F(QueryResultPrinterSmartBuyTest, EmptyJson) { - basicQueryResultPrinter(ApiOutputType::kJson).printBuyTrades(TradedAmountsPerExchange{}, endAmount, tradeOptions); + basicQueryResultPrinter(ApiOutputType::kJson).printBuyTrades(TradeResultPerExchange{}, endAmount, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -774,7 +786,7 @@ TEST_F(QueryResultPrinterSmartBuyTest, EmptyJson) { } TEST_F(QueryResultPrinterSmartBuyTest, Json) { - basicQueryResultPrinter(ApiOutputType::kJson).printBuyTrades(tradedAmountsPerExchange, endAmount, tradeOptions); + basicQueryResultPrinter(ApiOutputType::kJson).printBuyTrades(tradeResultPerExchange, endAmount, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -801,7 +813,9 @@ TEST_F(QueryResultPrinterSmartBuyTest, Json) { "binance": { "testuser1": { "from": "4500.67", - "to": "3" + "status": "Complete", + "tradedFrom": "4500.67", + "tradedTo": "3" } } } @@ -810,7 +824,7 @@ TEST_F(QueryResultPrinterSmartBuyTest, Json) { } TEST_F(QueryResultPrinterSmartBuyTest, NoPrint) { - basicQueryResultPrinter(ApiOutputType::kNoPrint).printBuyTrades(tradedAmountsPerExchange, endAmount, tradeOptions); + basicQueryResultPrinter(ApiOutputType::kNoPrint).printBuyTrades(tradeResultPerExchange, endAmount, tradeOptions); expectNoStr(); } @@ -819,23 +833,25 @@ class QueryResultPrinterSmartSellTest : public QueryResultPrinterTest { MonetaryAmount startAmount{"0.15BTC"}; TradeOptions tradeOptions; bool isPercentageTrade{false}; - TradedAmountsPerExchange tradedAmountsPerExchange{ - {&exchange1, TradedAmounts{MonetaryAmount("0.01BTC"), MonetaryAmount("1500USDT")}}, - {&exchange3, TradedAmounts{MonetaryAmount("0.004BTC"), MonetaryAmount("350EUR")}}, - {&exchange4, TradedAmounts{MonetaryAmount("0.1BTC"), MonetaryAmount("17ETH")}}}; + TradedAmounts tradedAmounts1{MonetaryAmount("0.01BTC"), MonetaryAmount("1500USDT")}; + TradedAmounts tradedAmounts3{MonetaryAmount("0.004BTC"), MonetaryAmount("350EUR")}; + TradedAmounts tradedAmounts4{MonetaryAmount("0.1BTC"), MonetaryAmount("17ETH")}; + TradeResultPerExchange tradeResultPerExchange{{&exchange1, TradeResult{tradedAmounts1, tradedAmounts1.from}}, + {&exchange3, TradeResult{tradedAmounts3, tradedAmounts1.from * 2}}, + {&exchange4, TradeResult{tradedAmounts4, tradedAmounts4.from * 3}}}; }; TEST_F(QueryResultPrinterSmartSellTest, FormattedTable) { basicQueryResultPrinter(ApiOutputType::kFormattedTable) - .printSellTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, tradeOptions); + .printSellTrades(tradeResultPerExchange, startAmount, isPercentageTrade, tradeOptions); static constexpr std::string_view kExpected = R"( -+----------+-----------+---------------------------+-------------------------+ -| Exchange | Account | Traded from amount (real) | Traded to amount (real) | -+----------+-----------+---------------------------+-------------------------+ -| binance | testuser1 | 0.01 BTC | 1500 USDT | -| huobi | testuser1 | 0.004 BTC | 350 EUR | -| huobi | testuser2 | 0.1 BTC | 17 ETH | -+----------+-----------+---------------------------+-------------------------+ ++----------+-----------+----------+---------------------------+-------------------------+----------+ +| Exchange | Account | From | Traded from amount (real) | Traded to amount (real) | Status | ++----------+-----------+----------+---------------------------+-------------------------+----------+ +| binance | testuser1 | 0.01 BTC | 0.01 BTC | 1500 USDT | Complete | +| huobi | testuser1 | 0.02 BTC | 0.004 BTC | 350 EUR | Partial | +| huobi | testuser2 | 0.3 BTC | 0.1 BTC | 17 ETH | Partial | ++----------+-----------+----------+---------------------------+-------------------------+----------+ )"; expectStr(kExpected); @@ -843,7 +859,7 @@ TEST_F(QueryResultPrinterSmartSellTest, FormattedTable) { TEST_F(QueryResultPrinterSmartSellTest, EmptyJson) { basicQueryResultPrinter(ApiOutputType::kJson) - .printSellTrades(TradedAmountsPerExchange{}, startAmount, isPercentageTrade, tradeOptions); + .printSellTrades(TradeResultPerExchange{}, startAmount, isPercentageTrade, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -873,7 +889,7 @@ TEST_F(QueryResultPrinterSmartSellTest, EmptyJson) { TEST_F(QueryResultPrinterSmartSellTest, Json) { basicQueryResultPrinter(ApiOutputType::kJson) - .printSellTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, tradeOptions); + .printSellTrades(tradeResultPerExchange, startAmount, isPercentageTrade, tradeOptions); static constexpr std::string_view kExpected = R"( { "in": { @@ -900,17 +916,23 @@ TEST_F(QueryResultPrinterSmartSellTest, Json) { "binance": { "testuser1": { "from": "0.01", - "to": "1500" + "status": "Complete", + "tradedFrom": "0.01", + "tradedTo": "1500" } }, "huobi": { "testuser1": { - "from": "0.004", - "to": "350" + "from": "0.02", + "status": "Partial", + "tradedFrom": "0.004", + "tradedTo": "350" }, "testuser2": { - "from": "0.1", - "to": "17" + "from": "0.3", + "status": "Partial", + "tradedFrom": "0.1", + "tradedTo": "17" } } } @@ -920,7 +942,7 @@ TEST_F(QueryResultPrinterSmartSellTest, Json) { TEST_F(QueryResultPrinterSmartSellTest, NoPrint) { basicQueryResultPrinter(ApiOutputType::kNoPrint) - .printSellTrades(tradedAmountsPerExchange, startAmount, isPercentageTrade, tradeOptions); + .printSellTrades(tradeResultPerExchange, startAmount, isPercentageTrade, tradeOptions); expectNoStr(); } diff --git a/src/engine/test/stringoptionparser_test.cpp b/src/engine/test/stringoptionparser_test.cpp index 0d88fbc0..7d946e21 100644 --- a/src/engine/test/stringoptionparser_test.cpp +++ b/src/engine/test/stringoptionparser_test.cpp @@ -104,36 +104,41 @@ TEST(StringOptionParserTest, GetMonetaryAmountCurrencyCodePrivateExchangesValidi TEST(StringOptionParserTest, GetCurrencyFromToPrivateExchange) { EXPECT_EQ(StringOptionParser("btc,huobi-kraken").getCurrencyFromToPrivateExchange(), - std::make_tuple(CurrencyCode("BTC"), ExchangeName("huobi"), ExchangeName("kraken"))); - EXPECT_EQ(StringOptionParser("XLM,bithumb_user1-binance").getCurrencyFromToPrivateExchange(), - std::make_tuple(CurrencyCode("XLM"), ExchangeName("bithumb", "user1"), ExchangeName("binance"))); + std::make_pair(CurrencyCode("BTC"), ExchangeNames{ExchangeName("huobi"), ExchangeName("kraken")})); + EXPECT_EQ( + StringOptionParser("XLM,bithumb_user1-binance").getCurrencyFromToPrivateExchange(), + std::make_pair(CurrencyCode("XLM"), ExchangeNames{ExchangeName("bithumb", "user1"), ExchangeName("binance")})); EXPECT_EQ(StringOptionParser("eth,kraken_user2-huobi_user3").getCurrencyFromToPrivateExchange(), - std::make_tuple(CurrencyCode("ETH"), ExchangeName("kraken", "user2"), ExchangeName("huobi", "user3"))); + std::make_pair(CurrencyCode("ETH"), + ExchangeNames{ExchangeName("kraken", "user2"), ExchangeName("huobi", "user3")})); } TEST(StringOptionParserTest, GetMonetaryAmountFromToPrivateExchange) { - EXPECT_EQ(StringOptionParser("0.102btc,huobi-kraken").getMonetaryAmountFromToPrivateExchange(), - std::make_tuple(MonetaryAmount("0.102BTC"), false, ExchangeName("huobi"), ExchangeName("kraken"))); + EXPECT_EQ( + StringOptionParser("0.102btc,huobi-kraken").getMonetaryAmountFromToPrivateExchange(), + std::make_tuple(MonetaryAmount("0.102BTC"), false, ExchangeNames{ExchangeName("huobi"), ExchangeName("kraken")})); EXPECT_EQ(StringOptionParser("3795541.90XLM,bithumb_user1-binance").getMonetaryAmountFromToPrivateExchange(), - std::make_tuple(MonetaryAmount("3795541.90XLM"), false, ExchangeName("bithumb", "user1"), - ExchangeName("binance"))); + std::make_tuple(MonetaryAmount("3795541.90XLM"), false, + ExchangeNames{ExchangeName("bithumb", "user1"), ExchangeName("binance")})); EXPECT_EQ(StringOptionParser("4.106eth,kraken_user2-huobi_user3").getMonetaryAmountFromToPrivateExchange(), - std::make_tuple(MonetaryAmount("4.106ETH"), false, ExchangeName("kraken", "user2"), - ExchangeName("huobi", "user3"))); + std::make_tuple(MonetaryAmount("4.106ETH"), false, + ExchangeNames{ExchangeName("kraken", "user2"), ExchangeName("huobi", "user3")})); EXPECT_THROW(StringOptionParser("test").getMonetaryAmountFromToPrivateExchange(), invalid_argument); } TEST(StringOptionParserTest, GetMonetaryAmountPercentageFromToPrivateExchange) { EXPECT_EQ(StringOptionParser("1%btc,huobi-kraken").getMonetaryAmountFromToPrivateExchange(), - StringOptionParser::MonetaryAmountFromToPrivateExchange(MonetaryAmount("1BTC"), true, ExchangeName("huobi"), - ExchangeName("kraken"))); - EXPECT_EQ(StringOptionParser("90.05%XLM,bithumb_user1-binance").getMonetaryAmountFromToPrivateExchange(), StringOptionParser::MonetaryAmountFromToPrivateExchange( - MonetaryAmount("90.05XLM"), true, ExchangeName("bithumb", "user1"), ExchangeName("binance"))); + MonetaryAmount("1BTC"), true, ExchangeNames{ExchangeName("huobi"), ExchangeName("kraken")})); + EXPECT_EQ( + StringOptionParser("90.05%XLM,bithumb_user1-binance").getMonetaryAmountFromToPrivateExchange(), + StringOptionParser::MonetaryAmountFromToPrivateExchange( + MonetaryAmount("90.05XLM"), true, ExchangeNames{ExchangeName("bithumb", "user1"), ExchangeName("binance")})); EXPECT_EQ(StringOptionParser("-50.758%eth,kraken_user2-huobi_user3").getMonetaryAmountFromToPrivateExchange(), StringOptionParser::MonetaryAmountFromToPrivateExchange( - MonetaryAmount("-50.758ETH"), true, ExchangeName("kraken", "user2"), ExchangeName("huobi", "user3"))); + MonetaryAmount("-50.758ETH"), true, + ExchangeNames{ExchangeName("kraken", "user2"), ExchangeName("huobi", "user3")})); } TEST(StringOptionParserTest, GetCurrencyPublicExchanges) { diff --git a/src/objects/include/exchangename.hpp b/src/objects/include/exchangename.hpp index d05476d2..780f20a2 100644 --- a/src/objects/include/exchangename.hpp +++ b/src/objects/include/exchangename.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -42,7 +43,7 @@ class ExchangeName { std::string_view str() const { return _nameWithKey; } - bool operator==(const ExchangeName &) const = default; + bool operator==(const ExchangeName &) const noexcept = default; friend std::ostream &operator<<(std::ostream &os, const ExchangeName &v) { os << v.str(); diff --git a/src/objects/include/monetaryamount.hpp b/src/objects/include/monetaryamount.hpp index 10880569..cd381fc4 100644 --- a/src/objects/include/monetaryamount.hpp +++ b/src/objects/include/monetaryamount.hpp @@ -176,12 +176,10 @@ class MonetaryAmount { } [[nodiscard]] constexpr MonetaryAmount abs() const noexcept { - return MonetaryAmount(true, _amount < 0 ? -_amount : _amount, _curWithDecimals); + return {true, _amount < 0 ? -_amount : _amount, _curWithDecimals}; } - [[nodiscard]] constexpr MonetaryAmount operator-() const noexcept { - return MonetaryAmount(true, -_amount, _curWithDecimals); - } + [[nodiscard]] constexpr MonetaryAmount operator-() const noexcept { return {true, -_amount, _curWithDecimals}; } /// @brief Addition of two MonetaryAmounts. /// They should have same currency for addition to be possible. @@ -191,14 +189,8 @@ class MonetaryAmount { [[nodiscard]] MonetaryAmount operator-(MonetaryAmount other) const { return *this + (-other); } - MonetaryAmount &operator+=(MonetaryAmount other) { - *this = *this + other; - return *this; - } - MonetaryAmount &operator-=(MonetaryAmount other) { - *this = *this + (-other); - return *this; - } + MonetaryAmount &operator+=(MonetaryAmount other) { return *this = *this + other; } + MonetaryAmount &operator-=(MonetaryAmount other) { return *this = *this + (-other); } [[nodiscard]] MonetaryAmount operator*(AmountType mult) const; @@ -212,30 +204,18 @@ class MonetaryAmount { /// - XXXXXXX * YYYYYYY -> ??????? (exception will be thrown in this case) [[nodiscard]] MonetaryAmount operator*(MonetaryAmount mult) const; - MonetaryAmount &operator*=(AmountType mult) { - *this = *this * mult; - return *this; - } - MonetaryAmount &operator*=(MonetaryAmount mult) { - *this = *this * mult; - return *this; - } + MonetaryAmount &operator*=(AmountType mult) { return *this = *this * mult; } + MonetaryAmount &operator*=(MonetaryAmount mult) { return *this = *this * mult; } [[nodiscard]] MonetaryAmount operator/(AmountType div) const { return *this / MonetaryAmount(div); } [[nodiscard]] MonetaryAmount operator/(MonetaryAmount div) const; - MonetaryAmount &operator/=(AmountType div) { - *this = *this / div; - return *this; - } - MonetaryAmount &operator/=(MonetaryAmount div) { - *this = *this / div; - return *this; - } + MonetaryAmount &operator/=(AmountType div) { return *this = *this / div; } + MonetaryAmount &operator/=(MonetaryAmount div) { return *this = *this / div; } [[nodiscard]] constexpr MonetaryAmount toNeutral() const noexcept { - return MonetaryAmount(true, _amount, _curWithDecimals.toNeutral()); + return {true, _amount, _curWithDecimals.toNeutral()}; } [[nodiscard]] constexpr bool isDefault() const noexcept { return _amount == 0 && hasNeutralCurrency(); } @@ -261,8 +241,8 @@ class MonetaryAmount { *it = '-'; ++it; } - const int nbDigits = ndigits(_amount); - const int nbDecs = nbDecimals(); + const auto nbDigits = ndigits(_amount); + const auto nbDecs = nbDecimals(); int remNbZerosToPrint = std::max(0, nbDecs + 1 - nbDigits); // no terminating null char, +1 is for the biggest decimal exponent part that is not fully covered by 64 bits @@ -328,7 +308,7 @@ class MonetaryAmount { appendCurrencyStr(str); } - uint64_t code() const noexcept { + [[nodiscard]] uint64_t code() const noexcept { return HashCombine(static_cast(_amount), static_cast(_curWithDecimals.code())); } @@ -380,7 +360,7 @@ class MonetaryAmount { } } - constexpr inline void setNbDecimals(int8_t nbDecs) { _curWithDecimals.setNbDecimals(nbDecs); } + constexpr void setNbDecimals(int8_t nbDecs) { _curWithDecimals.setNbDecimals(nbDecs); } AmountType _amount; CurrencyCode _curWithDecimals; @@ -395,7 +375,8 @@ static_assert(std::is_trivially_copyable_v, "MonetaryAmount shou 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/objects/src/monetaryamount.cpp b/src/objects/src/monetaryamount.cpp index 88810381..8cdf4770 100644 --- a/src/objects/src/monetaryamount.cpp +++ b/src/objects/src/monetaryamount.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,14 +30,17 @@ namespace { /// Theorem 15 constexpr int kNbMaxDoubleDecimals = std::numeric_limits::max_digits10; +constexpr bool IsNotSpace(char ch) { return ch != ' '; } +constexpr bool IsNotZero(char ch) { return ch != '0'; } + constexpr void RemovePrefixSpaces(std::string_view &str) { - str.remove_prefix(std::find_if(str.begin(), str.end(), [](char ch) { return ch != ' '; }) - str.begin()); + str.remove_prefix(std::ranges::find_if(str, IsNotSpace) - str.begin()); } constexpr void RemoveTrailingSpaces(std::string_view &str) { - str.remove_suffix(std::find_if(str.rbegin(), str.rend(), [](char ch) { return ch != ' '; }) - str.rbegin()); + str.remove_suffix(std::ranges::find_if(std::ranges::reverse_view(str), IsNotSpace) - std::ranges::rbegin(str)); } constexpr void RemoveTrailingZeros(std::string_view &str) { - str.remove_suffix(std::find_if(str.rbegin(), str.rend(), [](char ch) { return ch != '0'; }) - str.rbegin()); + str.remove_suffix(std::ranges::find_if(std::ranges::reverse_view(str), IsNotZero) - std::ranges::rbegin(str)); } inline int ParseNegativeChar(std::string_view &amountStr) { @@ -64,8 +68,7 @@ inline int ParseNegativeChar(std::string_view &amountStr) { /// Converts a string into a fixed precision integral containing both the integer and decimal part. /// @param amountStr the string to convert /// @param heuristicRoundingFromDouble if true, more than 5 consecutive zeros or 9 in the decimals part will be rounded -inline std::pair AmountIntegralFromStr(std::string_view amountStr, - bool heuristicRoundingFromDouble = false) { +inline auto AmountIntegralFromStr(std::string_view amountStr, bool heuristicRoundingFromDouble = false) { std::pair ret; ret.second = 0; @@ -140,12 +143,12 @@ MonetaryAmount::MonetaryAmount(std::string_view amountCurrencyStr) { const int negMult = ParseNegativeChar(amountCurrencyStr); auto last = amountCurrencyStr.begin(); - auto endIt = amountCurrencyStr.end(); + const auto endIt = amountCurrencyStr.end(); static_assert(' ' < '+' && '+' < '-' && '+' < '.'); // Trick: all '.', '+', '-' are before digits in the ASCII code while (last != endIt && *last >= '+' && *last <= '9') { ++last; } - std::string_view amountStr(amountCurrencyStr.begin(), last); + const std::string_view amountStr(amountCurrencyStr.begin(), last); int8_t nbDecimals; std::tie(_amount, nbDecimals) = AmountIntegralFromStr(amountStr); _amount *= negMult; @@ -200,14 +203,14 @@ std::optional MonetaryAmount::amount(int8_t nbDecima } constexpr MonetaryAmount::AmountType MonetaryAmount::decimalPart() const { - auto div = ipow10(static_cast(nbDecimals())); + const auto div = ipow10(static_cast(nbDecimals())); return _amount - (_amount / div) * div; } namespace { -constexpr int8_t SafeConvertSameDecimals(MonetaryAmount::AmountType &lhsAmount, MonetaryAmount::AmountType &rhsAmount, - int8_t lhsNbDecimals, int8_t rhsNbDecimals) { +constexpr auto SafeConvertSameDecimals(MonetaryAmount::AmountType &lhsAmount, MonetaryAmount::AmountType &rhsAmount, + int8_t lhsNbDecimals, int8_t rhsNbDecimals) { int lhsNbDigits = ndigits(lhsAmount); int rhsNbDigits = ndigits(rhsAmount); while (lhsNbDecimals != rhsNbDecimals) { @@ -295,14 +298,14 @@ void MonetaryAmount::round(int8_t nbDecimals, RoundType roundType) { } bool MonetaryAmount::isCloseTo(MonetaryAmount otherAmount, double relativeDifference) const { - double ourAmount = std::abs(toDouble()); - double boundMin = ourAmount * (1.0 - relativeDifference); - double boundMax = ourAmount * (1.0 + relativeDifference); + const double ourAmount = std::abs(toDouble()); + const double boundMin = ourAmount * (1.0 - relativeDifference); + const double boundMax = ourAmount * (1.0 + relativeDifference); if (boundMin < 0 || boundMax < 0) { throw exception("Unexpected bounds [{}-{}]", boundMin, boundMax); } - double closestAmount = std::abs(otherAmount.toDouble()); + const double closestAmount = std::abs(otherAmount.toDouble()); return closestAmount > boundMin && closestAmount < boundMax; } @@ -310,18 +313,18 @@ std::strong_ordering MonetaryAmount::operator<=>(const MonetaryAmount &other) co if (currencyCode() != other.currencyCode()) { throw exception("Cannot compare amounts with different currency"); } - int8_t lhsNbDecimals = nbDecimals(); - int8_t rhsNbDecimals = other.nbDecimals(); + const auto lhsNbDecimals = nbDecimals(); + const auto rhsNbDecimals = other.nbDecimals(); if (lhsNbDecimals == rhsNbDecimals) { return _amount <=> other._amount; } - AmountType lhsIntAmount = integerPart(); - AmountType rhsIntAmount = other.integerPart(); + const auto lhsIntAmount = integerPart(); + const auto rhsIntAmount = other.integerPart(); if (lhsIntAmount != rhsIntAmount) { return lhsIntAmount <=> rhsIntAmount; } // Same integral part, so expanding one's number of decimals towards the other one is safe - auto adjustDecimals = [](AmountType lhsAmount, AmountType rhsAmount, int8_t lhsNbD, int8_t rhsNbD) { + const auto adjustDecimals = [](AmountType lhsAmount, AmountType rhsAmount, int8_t lhsNbD, int8_t rhsNbD) { for (int8_t nbD = lhsNbD; nbD < rhsNbD; ++nbD) { assert(lhsAmount <= (std::numeric_limits::max() / 10) && (lhsAmount >= (std::numeric_limits::min() / 10))); @@ -347,8 +350,8 @@ MonetaryAmount MonetaryAmount::operator+(MonetaryAmount other) const { if (currencyCode() != other.currencyCode()) { throw exception("Addition is only possible on amounts with same currency"); } - AmountType lhsAmount = _amount; - AmountType rhsAmount = other._amount; + auto lhsAmount = _amount; + auto rhsAmount = other._amount; int8_t resNbDecimals = SafeConvertSameDecimals(lhsAmount, rhsAmount, nbDecimals(), other.nbDecimals()); AmountType resAmount = lhsAmount + rhsAmount; if (resAmount >= kMaxAmountFullNDigits || resAmount <= -kMaxAmountFullNDigits) { @@ -485,9 +488,6 @@ MonetaryAmount MonetaryAmount::operator/(MonetaryAmount div) const { return {static_cast(totalIntPart) * negMult, resCurrency, nbDecs}; } -std::ostream &operator<<(std::ostream &os, const MonetaryAmount &ma) { - os << ma.str(); - return os; -} +std::ostream &operator<<(std::ostream &os, const MonetaryAmount &ma) { return os << ma.str(); } } // namespace cct