From 600ac426927d5a4dbb64a9d4e4c71c48bc3e49d5 Mon Sep 17 00:00:00 2001 From: "Nick T." Date: Wed, 13 Aug 2025 14:58:48 +0200 Subject: [PATCH] Implement order book checksum in kraken --- include/ccapi_cpp/ccapi_util_private.h | 16 ++--- .../service/ccapi_market_data_service.h | 23 +++--- .../ccapi_market_data_service_kraken.h | 72 +++++++++++++++---- 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/include/ccapi_cpp/ccapi_util_private.h b/include/ccapi_cpp/ccapi_util_private.h index 6cd11efd..a3817eae 100644 --- a/include/ccapi_cpp/ccapi_util_private.h +++ b/include/ccapi_cpp/ccapi_util_private.h @@ -958,7 +958,7 @@ class Decimal { } } - explicit Decimal(std::string_view originalValue) { + explicit Decimal(std::string_view originalValue, bool checksumEnabled=false) { if (originalValue.empty()) { throw std::invalid_argument("Decimal constructor input value cannot be empty"); } @@ -972,7 +972,7 @@ class Decimal { } std::string fixedPointValue = std::string(originalValue.substr(this->sign ? 0 : 1, this->sign ? foundE : foundE - 1)); auto foundDot = fixedPointValue.find('.'); - if (foundDot != std::string::npos) { + if (foundDot != std::string::npos and not checksumEnabled) { fixedPointValue.erase(fixedPointValue.find_last_not_of('0') + 1); fixedPointValue.erase(fixedPointValue.find_last_not_of('.') + 1); } @@ -1017,9 +1017,9 @@ class Decimal { } if (foundDot != std::string::npos) { this->frac = fixedPointValue.substr(foundDot + 1); - // if (!keepTrailingZero) { - this->frac.erase(this->frac.find_last_not_of('0') + 1); - // } + if (!checksumEnabled) { + this->frac.erase(this->frac.find_last_not_of('0') + 1); + } } } else { auto found = originalValue.find('.'); @@ -1033,9 +1033,9 @@ class Decimal { } if (found != std::string::npos) { this->frac = originalValue.substr(found + 1); - // if (!keepTrailingZero) { - this->frac.erase(this->frac.find_last_not_of('0') + 1); - // } + if (not checksumEnabled) { + this->frac.erase(this->frac.find_last_not_of('0') + 1); + } } } diff --git a/include/ccapi_cpp/service/ccapi_market_data_service.h b/include/ccapi_cpp/service/ccapi_market_data_service.h index 09ab9609..befb3072 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service.h @@ -804,7 +804,7 @@ class MarketDataService : public Service { for (auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); snapshotBid.emplace(decimalPrice, std::string(size)); } CCAPI_LOGGER_TRACE("lastNToString(snapshotBid, " + toString(maxMarketDepth) + ") = " + lastNToString(snapshotBid, maxMarketDepth)); @@ -812,7 +812,7 @@ class MarketDataService : public Service { for (auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); snapshotAsk.emplace(decimalPrice, std::string(size)); } CCAPI_LOGGER_TRACE("firstNToString(snapshotAsk, " + toString(maxMarketDepth) + ") = " + firstNToString(snapshotAsk, maxMarketDepth)); @@ -894,14 +894,14 @@ class MarketDataService : public Service { for (auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); this->updateOrderBook(snapshotBid, decimalPrice, size, this->sessionOptions.enableCheckOrderBookChecksum); } } else if (type == MarketDataMessage::DataType::ASK) { for (auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); this->updateOrderBook(snapshotAsk, decimalPrice, size, this->sessionOptions.enableCheckOrderBookChecksum); } } else { @@ -1098,7 +1098,7 @@ class MarketDataService : public Service { this->highByConnectionIdChannelIdSymbolIdMap[wsConnectionPtr->id][channelId][symbolId] = Decimal(price); this->lowByConnectionIdChannelIdSymbolIdMap[wsConnectionPtr->id][channelId][symbolId] = Decimal(price); } else { - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); if (decimalPrice > this->highByConnectionIdChannelIdSymbolIdMap[wsConnectionPtr->id][channelId][symbolId]) { this->highByConnectionIdChannelIdSymbolIdMap[wsConnectionPtr->id][channelId][symbolId] = decimalPrice; } @@ -1372,7 +1372,7 @@ class MarketDataService : public Service { for (auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); snapshotBid.emplace(decimalPrice, std::string(size)); } CCAPI_LOGGER_TRACE("lastNToString(snapshotBid, " + toString(maxMarketDepth) + ") = " + lastNToString(snapshotBid, maxMarketDepth)); @@ -1380,7 +1380,7 @@ class MarketDataService : public Service { for (auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum); snapshotAsk.emplace(decimalPrice, std::string(size)); } CCAPI_LOGGER_TRACE("firstNToString(snapshotAsk, " + toString(maxMarketDepth) + ") = " + firstNToString(snapshotAsk, maxMarketDepth)); @@ -1520,14 +1520,14 @@ class MarketDataService : public Service { for (const auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, that->sessionOptions.enableCheckOrderBookChecksum); snapshotBid.emplace(decimalPrice, std::string(size)); } } else if (type == MarketDataMessage::DataType::ASK) { for (const auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, that->sessionOptions.enableCheckOrderBookChecksum); snapshotAsk.emplace(decimalPrice, std::string(size)); } } @@ -1548,14 +1548,14 @@ class MarketDataService : public Service { for (const auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, that->sessionOptions.enableCheckOrderBookChecksum); that->updateOrderBook(snapshotBid, decimalPrice, size, that->sessionOptions.enableCheckOrderBookChecksum); } } else if (type == MarketDataMessage::DataType::ASK) { for (const auto& y : detail) { const auto& price = y.at(MarketDataMessage::DataFieldType::PRICE); const auto& size = y.at(MarketDataMessage::DataFieldType::SIZE); - Decimal decimalPrice(price); + Decimal decimalPrice(price, that->sessionOptions.enableCheckOrderBookChecksum); that->updateOrderBook(snapshotAsk, decimalPrice, size, that->sessionOptions.enableCheckOrderBookChecksum); } } @@ -1671,6 +1671,7 @@ class MarketDataService : public Service { Event& event, std::vector& marketDataMessageList) {} virtual std::string calculateOrderBookChecksum(const std::map& snapshotBid, const std::map& snapshotAsk) { + CCAPI_LOGGER_DEBUG("calculateOrderBookChecksum is not implemented for this exchange"); return {}; } diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h b/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h index 2c5df2c9..17e6d1b3 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h @@ -50,6 +50,47 @@ class MarketDataServiceKraken : public MarketDataService { } } + static void processForChecksum(std::string& str) { + str.erase(std::remove(str.begin(), str.end(), '.'), str.end()); + UtilString::ltrimInPlace(str, "0"); + } + + std::vector extractTop(const std::map& snapshot, bool reverse = false) { + std::vector result; + int count = 0; + + auto process_item = [&](auto it) { + std::string price = toString(it->first); + processForChecksum(price); + result.push_back(std::move(price)); + + std::string volume = it->second; + processForChecksum(volume); + result.push_back(std::move(volume)); + }; + + if (reverse) { + for (auto it = snapshot.rbegin(); it != snapshot.rend() && count < 10; ++it, ++count) { + process_item(it); + } + } else { + for (auto it = snapshot.begin(); it != snapshot.end() && count < 10; ++it, ++count) { + process_item(it); + } + } + return result; + }; + + std::string calculateOrderBookChecksum(const std::map& snapshotBid, const std::map& snapshotAsk) override { + auto csAskData = extractTop(snapshotAsk); + auto csBidData = extractTop(snapshotBid, true); + + std::string csStr = UtilString::join(csAskData, "") + UtilString::join(csBidData, ""); + uint_fast32_t csCalc = UtilAlgorithm::crc(csStr.begin(), csStr.end()); + CCAPI_LOGGER_DEBUG("csStr: " + csStr + ", csCalc: " + intToHex(csCalc)); + return intToHex(csCalc); + } + std::vector createSendStringList(std::shared_ptr wsConnectionPtr) override { std::vector sendStringList; for (const auto& subscriptionListByChannelIdSymbolId : this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnectionPtr->id)) { @@ -186,19 +227,26 @@ class MarketDataServiceKraken : public MarketDataService { marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; marketDataMessage.tp = latestTp; marketDataMessage.recapType = MarketDataMessage::RecapType::NONE; + if (this->sessionOptions.enableCheckOrderBookChecksum) { + if (anonymous2.HasMember("c")) { + CCAPI_LOGGER_DEBUG("Checksum for " + symbolId + ": " + anonymous2["c"].GetString()); + this->orderBookChecksumByConnectionIdSymbolIdMap[wsConnectionPtr->id][symbolId] = + intToHex(static_cast(static_cast(std::stoul(anonymous2["c"].GetString())))); + } + } if (anonymous2.HasMember("b")) { for (const auto& x : anonymous2["b"].GetArray()) { MarketDataMessage::TypeForDataPoint dataPoint; - dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalStringView(x[0].GetString())); - dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalStringView(x[1].GetString())); + dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, x[0].GetString()); + dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, x[1].GetString()); marketDataMessage.data[MarketDataMessage::DataType::BID].emplace_back(std::move(dataPoint)); } } if (anonymous2.HasMember("a")) { for (const auto& x : anonymous2["a"].GetArray()) { MarketDataMessage::TypeForDataPoint dataPoint; - dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalStringView(x[0].GetString())); - dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalStringView(x[1].GetString())); + dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, x[0].GetString()); + dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, x[1].GetString()); marketDataMessage.data[MarketDataMessage::DataType::ASK].emplace_back(std::move(dataPoint)); } } @@ -211,14 +259,14 @@ class MarketDataServiceKraken : public MarketDataService { marketDataMessage.tp = timeReceived; for (const auto& x : anonymous["bs"].GetArray()) { MarketDataMessage::TypeForDataPoint dataPoint; - dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalStringView(x[0].GetString())); - dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalStringView(x[1].GetString())); + dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, x[0].GetString()); + dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, x[1].GetString()); marketDataMessage.data[MarketDataMessage::DataType::BID].emplace_back(std::move(dataPoint)); } for (const auto& x : anonymous["as"].GetArray()) { MarketDataMessage::TypeForDataPoint dataPoint; - dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalStringView(x[0].GetString())); - dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalStringView(x[1].GetString())); + dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, x[0].GetString()); + dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, x[1].GetString()); marketDataMessage.data[MarketDataMessage::DataType::ASK].emplace_back(std::move(dataPoint)); } marketDataMessageList.emplace_back(std::move(marketDataMessage)); @@ -235,8 +283,8 @@ class MarketDataServiceKraken : public MarketDataService { tp += std::chrono::nanoseconds(timePair.second); marketDataMessage.tp = tp; MarketDataMessage::TypeForDataPoint dataPoint; - dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalStringView(x[0].GetString())); - dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalStringView(x[1].GetString())); + dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, x[0].GetString()); + dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, x[1].GetString()); dataPoint.emplace(MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string_view(x[3].GetString()) == "s" ? "1" : "0"); marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); marketDataMessageList.emplace_back(std::move(marketDataMessage)); @@ -372,8 +420,8 @@ class MarketDataServiceKraken : public MarketDataService { tp += std::chrono::nanoseconds(timePair.second); marketDataMessage.tp = tp; MarketDataMessage::TypeForDataPoint dataPoint; - dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalStringView(x[0].GetString())); - dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalStringView(x[1].GetString())); + dataPoint.emplace(MarketDataMessage::DataFieldType::PRICE, x[0].GetString()); + dataPoint.emplace(MarketDataMessage::DataFieldType::SIZE, x[1].GetString()); dataPoint.emplace(MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string_view(x[3].GetString()) == "s" ? "1" : "0"); marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); marketDataMessageList.emplace_back(std::move(marketDataMessage));