Skip to content

Commit

Permalink
Correctly take into account depth parameter for last trades for some …
Browse files Browse the repository at this point in the history
…exchanges
  • Loading branch information
sjanel committed Mar 24, 2024
1 parent daaaf07 commit 0566c85
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 46 deletions.
6 changes: 4 additions & 2 deletions src/api/exchanges/src/binancepublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@
namespace cct::api {
namespace {

constexpr int kMaxNbLastTrades = 1000;

json PublicQuery(CurlHandle& curlHandle, std::string_view method, const CurlPostData& curlPostData = CurlPostData()) {
string endpoint(method);
if (!curlPostData.empty()) {
Expand Down Expand Up @@ -524,15 +522,19 @@ MonetaryAmount BinancePublic::TradedVolumeFunc::operator()(Market mk) {
}

PublicTradeVector BinancePublic::queryLastTrades(Market mk, int nbTrades) {
static constexpr int kMaxNbLastTrades = 1000;

if (nbTrades > kMaxNbLastTrades) {
log::warn("{} is larger than maximum number of last trades of {} on {}", nbTrades, kMaxNbLastTrades, _name);
nbTrades = kMaxNbLastTrades;
}

json result = PublicQuery(_commonInfo._curlHandle, "/api/v3/trades",
{{"symbol", mk.assetsPairStrUpper()}, {"limit", nbTrades}});

PublicTradeVector ret;
ret.reserve(static_cast<PublicTradeVector::size_type>(result.size()));

for (const json& detail : result) {
MonetaryAmount amount(detail["qty"].get<std::string_view>(), mk.base());
MonetaryAmount price(detail["price"].get<std::string_view>(), mk.quote());
Expand Down
29 changes: 23 additions & 6 deletions src/api/exchanges/src/bithumbpublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <iterator>
#include <memory>
#include <optional>
#include <ranges>
#include <sstream>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -292,27 +293,43 @@ MonetaryAmount BithumbPublic::TradedVolumeFunc::operator()(Market mk) {
}

namespace {
TimePoint EpochTime(std::string&& dateStr) {
std::istringstream ss(std::move(dateStr));
TimePoint EpochTime(std::string_view dateStr) {
// In C++26, std::istringstream can be built from a std::string_view
std::istringstream ss(std::string{dateStr});
std::tm tm{};
ss >> std::get_time(&tm, kTimeYearToSecondSpaceSeparatedFormat);
static constexpr Duration kKoreaUTCTime = std::chrono::hours(9);
return Clock::from_time_t(std::mktime(&tm)) - kKoreaUTCTime;
}
} // namespace

PublicTradeVector BithumbPublic::queryLastTrades(Market mk, [[maybe_unused]] int nbTrades) {
json result = PublicQuery(_curlHandle, "/public/transaction_history/", mk.base(), mk.quote());
PublicTradeVector BithumbPublic::queryLastTrades(Market mk, int nbTrades) {
static constexpr auto kNbMinLastTrades = 1;
static constexpr auto kNbMaxLastTrades = 100;

if (nbTrades < kNbMinLastTrades) {
log::warn("Minimum number of last trades to ask on {} is {}", name(), kNbMinLastTrades);
nbTrades = kNbMinLastTrades;
} else if (nbTrades > kNbMaxLastTrades) {
log::warn("Maximum number of last trades to ask on {} is {}", name(), kNbMaxLastTrades);
nbTrades = kNbMaxLastTrades;
}

string urlOpts("count=");
AppendString(urlOpts, nbTrades);

json result = PublicQuery(_curlHandle, "/public/transaction_history/", mk.base(), mk.quote(), urlOpts);

PublicTradeVector ret;
ret.reserve(result.size());

for (const json& detail : result) {
MonetaryAmount amount(detail["units_traded"].get<std::string_view>(), mk.base());
MonetaryAmount price(detail["price"].get<std::string_view>(), mk.quote());
// Korea time (UTC+9) in this format: "2021-11-29 03:29:35"
TradeSide tradeSide = detail["type"].get<std::string_view>() == "bid" ? TradeSide::kBuy : TradeSide::kSell;

ret.emplace_back(tradeSide, amount, price,
EpochTime(std::string(detail["transaction_date"].get<std::string_view>())));
ret.emplace_back(tradeSide, amount, price, EpochTime(detail["transaction_date"].get<std::string_view>()));
}
std::ranges::sort(ret);
return ret;
Expand Down
33 changes: 26 additions & 7 deletions src/api/exchanges/src/huobipublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,22 +456,41 @@ MonetaryAmount HuobiPublic::TradedVolumeFunc::operator()(Market mk) {
}

PublicTradeVector HuobiPublic::queryLastTrades(Market mk, int nbTrades) {
nbTrades = std::min(nbTrades, 2000); // max authorized
nbTrades = std::max(nbTrades, 1); // min authorized
static constexpr auto kNbMinLastTrades = 1;
static constexpr auto kNbMaxLastTrades = 2000;

if (nbTrades < kNbMinLastTrades) {
log::warn("Minimum number of last trades to ask on {} is {}", name(), kNbMinLastTrades);
nbTrades = kNbMinLastTrades;
} else if (nbTrades > kNbMaxLastTrades) {
log::warn("Maximum number of last trades to ask on {} is {}", name(), kNbMaxLastTrades);
nbTrades = kNbMaxLastTrades;
}

json result =
PublicQuery(_curlHandle, "/market/history/trade", {{"symbol", mk.assetsPairStrLower()}, {"size", nbTrades}});

PublicTradeVector ret;
ret.reserve(nbTrades);

for (const json& detail : result) {
auto dataDetails = detail.find("data");
const auto dataDetails = detail.find("data");
if (dataDetails != detail.end()) {
for (const json& detail2 : *dataDetails) {
MonetaryAmount amount(detail2["amount"].get<double>(), mk.base());
MonetaryAmount price(detail2["price"].get<double>(), mk.quote());
int64_t millisecondsSinceEpoch = detail2["ts"].get<int64_t>();
TradeSide tradeSide =
const MonetaryAmount amount(detail2["amount"].get<double>(), mk.base());
const MonetaryAmount price(detail2["price"].get<double>(), mk.quote());
const int64_t millisecondsSinceEpoch = detail2["ts"].get<int64_t>();
const TradeSide tradeSide =
detail2["direction"].get<std::string_view>() == "buy" ? TradeSide::kBuy : TradeSide::kSell;

ret.emplace_back(tradeSide, amount, price, TimePoint(milliseconds(millisecondsSinceEpoch)));

if (static_cast<int>(ret.size()) == nbTrades) {
break;
}
}
if (static_cast<int>(ret.size()) == nbTrades) {
break;
}
}
}
Expand Down
18 changes: 11 additions & 7 deletions src/api/exchanges/src/krakenpublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,16 +340,20 @@ KrakenPublic::TickerFunc::Last24hTradedVolumeAndLatestPricePair KrakenPublic::Ti
PublicTradeVector KrakenPublic::queryLastTrades(Market mk, int nbLastTrades) {
Market krakenMarket(_tradableCurrenciesCache.get().getOrThrow(mk.base()).altCode(),
_tradableCurrenciesCache.get().getOrThrow(mk.quote()).altCode());
PublicTradeVector ret;

json result = PublicQuery(_curlHandle, "/public/Trades",
{{"pair", krakenMarket.assetsPairStrUpper()}, {"count", nbLastTrades}});

PublicTradeVector ret;

if (!result.empty()) {
ret.reserve(result.front().size());
for (const json& det : result.front()) {
MonetaryAmount price(det[0].get<std::string_view>(), mk.quote());
MonetaryAmount amount(det[1].get<std::string_view>(), mk.base());
int64_t millisecondsSinceEpoch = static_cast<int64_t>(det[2].get<double>() * 1000);
TradeSide tradeSide = det[3].get<std::string_view>() == "b" ? TradeSide::kBuy : TradeSide::kSell;
const auto& lastTrades = result.front();
ret.reserve(lastTrades.size());
for (const json& det : lastTrades) {
const MonetaryAmount price(det[0].get<std::string_view>(), mk.quote());
const MonetaryAmount amount(det[1].get<std::string_view>(), mk.base());
const int64_t millisecondsSinceEpoch = static_cast<int64_t>(det[2].get<double>() * 1000);
const TradeSide tradeSide = det[3].get<std::string_view>() == "b" ? TradeSide::kBuy : TradeSide::kSell;

ret.emplace_back(tradeSide, amount, price, TimePoint(milliseconds(millisecondsSinceEpoch)));
}
Expand Down
22 changes: 15 additions & 7 deletions src/api/exchanges/src/kucoinpublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,16 +343,24 @@ MonetaryAmount KucoinPublic::TradedVolumeFunc::operator()(Market mk) {
return {amountStr, mk.base()};
}

PublicTradeVector KucoinPublic::queryLastTrades(Market mk, [[maybe_unused]] int nbTrades) {
PublicTradeVector KucoinPublic::queryLastTrades(Market mk, int nbTrades) {
static constexpr auto kMaxNbLastTrades = 100;
if (nbTrades > kMaxNbLastTrades) {
log::warn("Maximum number of last trades to query from {} is {}", name(), kMaxNbLastTrades);
}

json result = PublicQuery(_curlHandle, "/api/v1/market/histories", GetSymbolPostData(mk));

PublicTradeVector ret;
ret.reserve(static_cast<PublicTradeVector::size_type>(result.size()));
for (const json& detail : result) {
MonetaryAmount amount(detail["size"].get<std::string_view>(), mk.base());
MonetaryAmount price(detail["price"].get<std::string_view>(), mk.quote());
ret.reserve(std::min(static_cast<PublicTradeVector::size_type>(result.size()),
static_cast<PublicTradeVector::size_type>(nbTrades)));

for (const json& detail : result | std::ranges::views::take(nbTrades)) {
const MonetaryAmount amount(detail["size"].get<std::string_view>(), mk.base());
const MonetaryAmount price(detail["price"].get<std::string_view>(), mk.quote());
// time is in nanoseconds
int64_t millisecondsSinceEpoch = static_cast<int64_t>(detail["time"].get<uintmax_t>() / 1000000UL);
TradeSide tradeSide = detail["side"].get<std::string_view>() == "buy" ? TradeSide::kBuy : TradeSide::kSell;
const int64_t millisecondsSinceEpoch = static_cast<int64_t>(detail["time"].get<uintmax_t>() / 1000000UL);
const TradeSide tradeSide = detail["side"].get<std::string_view>() == "buy" ? TradeSide::kBuy : TradeSide::kSell;

ret.emplace_back(tradeSide, amount, price, TimePoint(milliseconds(millisecondsSinceEpoch)));
}
Expand Down
2 changes: 2 additions & 0 deletions src/api/exchanges/src/upbitpublicapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,10 @@ MonetaryAmount UpbitPublic::TradedVolumeFunc::operator()(Market mk) {

PublicTradeVector UpbitPublic::queryLastTrades(Market mk, int nbTrades) {
json result = PublicQuery(_curlHandle, "/v1/trades/ticks", {{"count", nbTrades}, {"market", ReverseMarketStr(mk)}});

PublicTradeVector ret;
ret.reserve(static_cast<PublicTradeVector::size_type>(result.size()));

for (const json& detail : result) {
MonetaryAmount amount(detail["trade_volume"].get<double>(), mk.base());
MonetaryAmount price(detail["trade_price"].get<double>(), mk.quote());
Expand Down
2 changes: 1 addition & 1 deletion src/engine/include/coincenter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class Coincenter {
MonetaryAmountPerExchange getLast24hTradedVolumePerExchange(Market mk, ExchangeNameSpan exchangeNames);

/// Retrieve the last trades for each queried exchange
TradesPerExchange getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames, int nbLastTrades);
TradesPerExchange getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames, std::optional<int> depth);

/// Retrieve the last price for exchanges supporting given market.
MonetaryAmountPerExchange getLastPricePerExchange(Market mk, ExchangeNameSpan exchangeNames);
Expand Down
2 changes: 1 addition & 1 deletion src/engine/include/exchangesorchestrator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class ExchangesOrchestrator {

MonetaryAmountPerExchange getLast24hTradedVolumePerExchange(Market mk, ExchangeNameSpan exchangeNames);

TradesPerExchange getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames, int nbLastTrades);
TradesPerExchange getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames, std::optional<int> depth);

MonetaryAmountPerExchange getLastPricePerExchange(Market mk, ExchangeNameSpan exchangeNames);

Expand Down
3 changes: 2 additions & 1 deletion src/engine/include/queryresultprinter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class QueryResultPrinter {

void printLast24hTradedVolume(Market mk, const MonetaryAmountPerExchange &tradedVolumePerExchange) const;

void printLastTrades(Market mk, int nbLastTrades, const TradesPerExchange &lastTradesPerExchange) const;
void printLastTrades(Market mk, std::optional<int> nbLastTrades,
const TradesPerExchange &lastTradesPerExchange) const;

void printLastPrice(Market mk, const MonetaryAmountPerExchange &pricePerExchange) const;

Expand Down
10 changes: 5 additions & 5 deletions src/engine/src/coincenter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,8 @@ TransferableCommandResultVector Coincenter::processCommand(
break;
}
case CoincenterCommandType::kLastTrades: {
const int depth = cmd.depth() == 0 ? api::ExchangePublic::kNbLastTradesDefault : cmd.depth();
const auto lastTradesPerExchange = getLastTradesPerExchange(cmd.market(), cmd.exchangeNames(), depth);
_queryResultPrinter.printLastTrades(cmd.market(), depth, lastTradesPerExchange);
const auto lastTradesPerExchange = getLastTradesPerExchange(cmd.market(), cmd.exchangeNames(), cmd.optDepth());
_queryResultPrinter.printLastTrades(cmd.market(), cmd.optDepth(), lastTradesPerExchange);
break;
}
case CoincenterCommandType::kLast24hTradedVolume: {
Expand Down Expand Up @@ -405,8 +404,9 @@ MonetaryAmountPerExchange Coincenter::getLast24hTradedVolumePerExchange(Market m
return _exchangesOrchestrator.getLast24hTradedVolumePerExchange(mk, exchangeNames);
}

TradesPerExchange Coincenter::getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames, int nbLastTrades) {
const auto ret = _exchangesOrchestrator.getLastTradesPerExchange(mk, exchangeNames, nbLastTrades);
TradesPerExchange Coincenter::getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames,
std::optional<int> depth) {
const auto ret = _exchangesOrchestrator.getLastTradesPerExchange(mk, exchangeNames, depth);

_metricsExporter.exportLastTradesMetrics(ret);

Expand Down
13 changes: 7 additions & 6 deletions src/engine/src/exchangesorchestrator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ ExchangeTickerMaps ExchangesOrchestrator::getTickerInformation(ExchangeNameSpan
MarketOrderBookConversionRates ExchangesOrchestrator::getMarketOrderBooks(Market mk, ExchangeNameSpan exchangeNames,
CurrencyCode equiCurrencyCode,
std::optional<int> depth) {
log::info("Order book of {} on {} requested{}{}", mk, ConstructAccumulatedExchangeNames(exchangeNames),
const auto actualDepth = depth.value_or(api::ExchangePublic::kDefaultDepth);
log::info("{} order book of depth {} on {} requested{}{}", mk, actualDepth,
ConstructAccumulatedExchangeNames(exchangeNames),
equiCurrencyCode.isNeutral() ? "" : " with equi currency ",
equiCurrencyCode.isNeutral() ? "" : equiCurrencyCode);
UniquePublicSelectedExchanges selectedExchanges = _exchangeRetriever.selectOneAccount(exchangeNames);
Expand All @@ -160,17 +162,15 @@ MarketOrderBookConversionRates ExchangesOrchestrator::getMarketOrderBooks(Market
FilterVector(selectedExchanges, isMarketTradable);

MarketOrderBookConversionRates ret(selectedExchanges.size());
auto marketOrderBooksFunc = [mk, equiCurrencyCode, depth](Exchange *exchange) {
auto marketOrderBooksFunc = [mk, equiCurrencyCode, actualDepth](Exchange *exchange) {
std::optional<MonetaryAmount> optConversionRate =
equiCurrencyCode.isNeutral()
? std::nullopt
: exchange->apiPublic().estimatedConvert(MonetaryAmount(1, mk.quote()), equiCurrencyCode);
if (!optConversionRate && !equiCurrencyCode.isNeutral()) {
log::warn("Unable to convert {} into {} on {}", mk.quote(), equiCurrencyCode, exchange->name());
}
return std::make_tuple(exchange->name(),
exchange->queryOrderBook(mk, depth.value_or(api::ExchangePublic::kDefaultDepth)),
optConversionRate);
return std::make_tuple(exchange->name(), exchange->queryOrderBook(mk, actualDepth), optConversionRate);
};
_threadPool.parallelTransform(selectedExchanges.begin(), selectedExchanges.end(), ret.begin(), marketOrderBooksFunc);
return ret;
Expand Down Expand Up @@ -911,7 +911,8 @@ MonetaryAmountPerExchange ExchangesOrchestrator::getLast24hTradedVolumePerExchan
}

TradesPerExchange ExchangesOrchestrator::getLastTradesPerExchange(Market mk, ExchangeNameSpan exchangeNames,
int nbLastTrades) {
std::optional<int> depth) {
const auto nbLastTrades = depth.value_or(api::ExchangePublic::kNbLastTradesDefault);
log::info("Query {} last trades on {} volume from {}", nbLastTrades, mk,
ConstructAccumulatedExchangeNames(exchangeNames));
UniquePublicSelectedExchanges selectedExchanges = getExchangesTradingMarket(mk, exchangeNames);
Expand Down
8 changes: 5 additions & 3 deletions src/engine/src/queryresultprinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,11 +581,13 @@ json Last24hTradedVolumeJson(Market mk, const MonetaryAmountPerExchange &tradedV
return ToJson(CoincenterCommandType::kLast24hTradedVolume, std::move(in), std::move(out));
}

json LastTradesJson(Market mk, int nbLastTrades, const TradesPerExchange &lastTradesPerExchange) {
json LastTradesJson(Market mk, std::optional<int> nbLastTrades, const TradesPerExchange &lastTradesPerExchange) {
json in;
json inOpt;
inOpt.emplace("market", mk.str());
inOpt.emplace("nb", nbLastTrades);
if (nbLastTrades) {
inOpt.emplace("nb", *nbLastTrades);
}
in.emplace("opt", std::move(inOpt));

json out = json::object();
Expand Down Expand Up @@ -1234,7 +1236,7 @@ void QueryResultPrinter::printLast24hTradedVolume(Market mk,
logActivity(CoincenterCommandType::kLast24hTradedVolume, jsonData);
}

void QueryResultPrinter::printLastTrades(Market mk, int nbLastTrades,
void QueryResultPrinter::printLastTrades(Market mk, std::optional<int> nbLastTrades,
const TradesPerExchange &lastTradesPerExchange) const {
json jsonData = LastTradesJson(mk, nbLastTrades, lastTradesPerExchange);
switch (_apiOutputType) {
Expand Down

0 comments on commit 0566c85

Please sign in to comment.