diff --git a/README.md b/README.md index 242b6a28..a3a6c752 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ * Code closely follows Bloomberg's API: https://www.bloomberg.com/professional/support/api-library/. * It is ultra fast thanks to very careful optimizations: move semantics, regex optimization, locality of reference, lock contention minimization, etc. * Supported exchanges: - * Market data: coinbase, gemini, kraken, kraken-futures, bitstamp, bitfinex, bitmex, binance-us, binance, binance-usds-futures, binance-coin-futures, huobi, huobi-usdt-swap, huobi-coin-swap, okx, erisx, kucoin, kucoin-futures, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, bybit-derivatives, ascendex, bitget, bitget-futures, bitmart, mexc, mexc-futures. + * Market data: coinbase, gemini, kraken, kraken-futures, bitstamp, bitfinex, bitmex, binance-us, binance, binance-usds-futures, binance-coin-futures, huobi, huobi-usdt-swap, huobi-coin-swap, okx, erisx, kucoin, kucoin-futures, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, bybit-derivatives, ascendex, bitget, bitget-futures, bitmart, mexc, mexc-futures, whitebit. * Execution Management: coinbase, gemini, kraken, kraken-futures, bitstamp, bitfinex, bitmex, binance-us, binance, binance-usds-futures, binance-coin-futures, huobi, huobi-usdt-swap, huobi-coin-swap, okx, erisx, kucoin, kucoin-futures, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, bybit-derivatives, ascendex, bitget, bitget-futures, bitmart, mexc. * FIX: coinbase, gemini. * A spot market making application is provided as an end-to-end solution for liquidity providers. diff --git a/binding/user_specified_cmake_include.cmake.example b/binding/user_specified_cmake_include.cmake.example index e92cff30..70067391 100644 --- a/binding/user_specified_cmake_include.cmake.example +++ b/binding/user_specified_cmake_include.cmake.example @@ -61,6 +61,8 @@ include_guard(DIRECTORY) # # add_compile_definitions(CCAPI_ENABLE_EXCHANGE_MEXC_FUTURES) # +# add_compile_definitions(CCAPI_ENABLE_EXCHANGE_WHITEBIT) +# # add_compile_definitions(CCAPI_ENABLE_LOG_TRACE) # # add_compile_definitions(CCAPI_ENABLE_LOG_DEBUG) diff --git a/include/ccapi_cpp/ccapi_macro.h b/include/ccapi_cpp/ccapi_macro.h index f3a2f78a..da98956d 100644 --- a/include/ccapi_cpp/ccapi_macro.h +++ b/include/ccapi_cpp/ccapi_macro.h @@ -148,6 +148,9 @@ #ifndef CCAPI_EXCHANGE_NAME_MEXC_FUTURES #define CCAPI_EXCHANGE_NAME_MEXC_FUTURES "mexc-futures" #endif +#ifndef CCAPI_EXCHANGE_NAME_WHITEBIT +#define CCAPI_EXCHANGE_NAME_WHITEBIT "whitebit" +#endif #ifndef CCAPI_LAST_PRICE #define CCAPI_LAST_PRICE "LAST_PRICE" #endif @@ -325,6 +328,8 @@ #define CCAPI_WEBSOCKET_MEXC_CHANNEL_DIFF_DEPTH "spot@public.increase.depth.v3.api" #define CCAPI_WEBSOCKET_MEXC_FUTURES_CHANNEL_TRANSACTION "deal" #define CCAPI_WEBSOCKET_MEXC_FUTURES_CHANNEL_DEPTH "depth" +#define CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES "trades" +#define CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_DEPTH "depth" #ifndef CCAPI_CHANNEL_ID #define CCAPI_CHANNEL_ID "channelId" #endif @@ -465,6 +470,9 @@ #ifndef CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY #define CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY "CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY" #endif +#ifndef CCAPI_EM_ORDER_AVERAGE_FILLED_PRICE +#define CCAPI_EM_ORDER_AVERAGE_FILLED_PRICE "AVERAGE_FILLED_PRICE" +#endif #ifndef CCAPI_EM_ORDER_INSTRUMENT #define CCAPI_EM_ORDER_INSTRUMENT "INSTRUMENT" #endif @@ -714,6 +722,9 @@ #ifndef CCAPI_MEXC_FUTURES_URL_REST_BASE #define CCAPI_MEXC_FUTURES_URL_REST_BASE "https://contract.mexc.com" #endif +#ifndef CCAPI_WHITEBIT_URL_REST_BASE +#define CCAPI_WHITEBIT_URL_REST_BASE "https://whitebit.com" +#endif // end: exchange REST urls // start: exchange WS urls @@ -831,6 +842,9 @@ #ifndef CCAPI_MEXC_FUTURES_URL_WS_BASE #define CCAPI_MEXC_FUTURES_URL_WS_BASE "wss://contract.mexc.com" #endif +#ifndef CCAPI_WHITEBIT_URL_WS_BASE +#define CCAPI_WHITEBIT_URL_WS_BASE "wss://api.whitebit.com" +#endif // end: exchange WS urls // start: exchange FIX urls diff --git a/include/ccapi_cpp/ccapi_message.h b/include/ccapi_cpp/ccapi_message.h index fd90e5ff..da6270d5 100644 --- a/include/ccapi_cpp/ccapi_message.h +++ b/include/ccapi_cpp/ccapi_message.h @@ -16,8 +16,8 @@ class Message CCAPI_FINAL { public: enum class RecapType { UNKNOWN, - NONE, // normal data tick; not a recap - SOLICITED, // generated on request by subscriber + NONE, // Normal data tick, not a recap. For market depth, it represents the new snapshot. For public trade, it represents the new trades. + SOLICITED, // A recap. For market depth, it represents the initial snapshot. For public trade, it represents the recent trades. }; static std::string recapTypeToString(RecapType recapType) { std::string output; diff --git a/include/ccapi_cpp/ccapi_session.h b/include/ccapi_cpp/ccapi_session.h index 571c9c43..a0959c45 100644 --- a/include/ccapi_cpp/ccapi_session.h +++ b/include/ccapi_cpp/ccapi_session.h @@ -100,6 +100,9 @@ #ifdef CCAPI_ENABLE_EXCHANGE_MEXC_FUTURES #include "ccapi_cpp/service/ccapi_market_data_service_mexc_futures.h" #endif +#ifdef CCAPI_ENABLE_EXCHANGE_WHITEBIT +#include "ccapi_cpp/service/ccapi_market_data_service_whitebit.h" +#endif #endif // end: enable exchanges for market data @@ -204,6 +207,9 @@ // #ifdef CCAPI_ENABLE_EXCHANGE_MEXC_FUTURES // #include "ccapi_cpp/service/ccapi_execution_management_service_mexc_futures.h" // #endif +#ifdef CCAPI_ENABLE_EXCHANGE_WHITEBIT +#include "ccapi_cpp/service/ccapi_execution_management_service_whitebit.h" +#endif #endif // end: enable exchanges for execution management @@ -421,6 +427,10 @@ class Session { this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_MEXC_FUTURES] = std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); #endif +#ifdef CCAPI_ENABLE_EXCHANGE_WHITEBIT + this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_WHITEBIT] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif #endif #ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT #ifdef CCAPI_ENABLE_EXCHANGE_COINBASE @@ -555,7 +565,12 @@ class Session { // this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_MEXC_FUTURES] = // std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); // #endif +// #ifdef CCAPI_ENABLE_EXCHANGE_WHITEBIT +// this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_WHITEBIT] = +// std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +// #endif #endif + #ifdef CCAPI_ENABLE_SERVICE_FIX #ifdef CCAPI_ENABLE_EXCHANGE_COINBASE this->serviceByServiceNameExchangeMap[CCAPI_FIX][CCAPI_EXCHANGE_NAME_COINBASE] = diff --git a/include/ccapi_cpp/ccapi_session_configs.h b/include/ccapi_cpp/ccapi_session_configs.h index faa82b3f..169b791e 100644 --- a/include/ccapi_cpp/ccapi_session_configs.h +++ b/include/ccapi_cpp/ccapi_session_configs.h @@ -163,6 +163,10 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_TRADE, CCAPI_WEBSOCKET_MEXC_FUTURES_CHANNEL_TRANSACTION}, {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_MEXC_FUTURES_CHANNEL_DEPTH}, }; + std::map fieldWebsocketChannelMapWhitebit = { + {CCAPI_TRADE, CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES}, + {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_DEPTH}, + }; for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapCoinbase) { this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_COINBASE].push_back(fieldWebsocketChannel.first); } @@ -259,6 +263,9 @@ class SessionConfigs CCAPI_FINAL { for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapMexcFutures) { this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_MEXC_FUTURES].push_back(fieldWebsocketChannel.first); } + for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapWhitebit) { + this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_WHITEBIT].push_back(fieldWebsocketChannel.first); + } for (auto& x : this->exchangeFieldMap) { x.second.push_back(CCAPI_GENERIC_PUBLIC_SUBSCRIPTION); } @@ -296,6 +303,7 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_EXCHANGE_NAME_BITMART, fieldWebsocketChannelMapBitmart}, {CCAPI_EXCHANGE_NAME_MEXC, fieldWebsocketChannelMapMexc}, {CCAPI_EXCHANGE_NAME_MEXC_FUTURES, fieldWebsocketChannelMapMexcFutures}, + {CCAPI_EXCHANGE_NAME_WHITEBIT, fieldWebsocketChannelMapWhitebit}, }; this->urlWebsocketBase = { {CCAPI_EXCHANGE_NAME_COINBASE, CCAPI_COINBASE_URL_WS_BASE}, @@ -334,6 +342,7 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_EXCHANGE_NAME_BITMART, CCAPI_BITMART_URL_WS_BASE}, {CCAPI_EXCHANGE_NAME_MEXC, CCAPI_MEXC_URL_WS_BASE}, {CCAPI_EXCHANGE_NAME_MEXC_FUTURES, CCAPI_MEXC_FUTURES_URL_WS_BASE}, + {CCAPI_EXCHANGE_NAME_WHITEBIT, CCAPI_WHITEBIT_URL_WS_BASE}, }; this->initialSequenceByExchangeMap = {{CCAPI_EXCHANGE_NAME_GEMINI, 0}, {CCAPI_EXCHANGE_NAME_BITFINEX, 1}}; } @@ -370,9 +379,10 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_EXCHANGE_NAME_ASCENDEX, CCAPI_ASCENDEX_URL_REST_BASE}, {CCAPI_EXCHANGE_NAME_BITGET, CCAPI_BITGET_URL_REST_BASE}, {CCAPI_EXCHANGE_NAME_BITGET_FUTURES, CCAPI_BITGET_FUTURES_URL_REST_BASE}, + {CCAPI_EXCHANGE_NAME_BITMART, CCAPI_BITMART_URL_REST_BASE}, {CCAPI_EXCHANGE_NAME_MEXC, CCAPI_MEXC_URL_REST_BASE}, {CCAPI_EXCHANGE_NAME_MEXC_FUTURES, CCAPI_MEXC_FUTURES_URL_REST_BASE}, - {CCAPI_EXCHANGE_NAME_BITMART, CCAPI_BITMART_URL_REST_BASE}, + {CCAPI_EXCHANGE_NAME_WHITEBIT, CCAPI_WHITEBIT_URL_REST_BASE}, }; } void initializUrlFixBase() { diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_kraken_futures.h b/include/ccapi_cpp/service/ccapi_execution_management_service_kraken_futures.h index 9a5c0144..6b3f08d9 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_kraken_futures.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_kraken_futures.h @@ -280,62 +280,64 @@ class ExecutionManagementServiceKrakenFutures : public ExecutionManagementServic if (document.FindMember("event") == document.MemberEnd()) { if (document.FindMember("feed") != document.MemberEnd()) { std::string feed = document["feed"].GetString(); - std::string field; - if (feed == "fills") { - field = CCAPI_EM_PRIVATE_TRADE; - } else if (feed == "open_orders") { - field = CCAPI_EM_ORDER_UPDATE; - } - const auto& fieldSet = subscription.getFieldSet(); - if (fieldSet.find(field) != fieldSet.end()) { - const auto& instrumentSet = subscription.getInstrumentSet(); - if (field == CCAPI_EM_PRIVATE_TRADE) { - for (const auto& x : document["fills"].GetArray()) { + if (feed == "fills" || feed == "open_orders") { + std::string field; + if (feed == "fills") { + field = CCAPI_EM_PRIVATE_TRADE; + } else if (feed == "open_orders") { + field = CCAPI_EM_ORDER_UPDATE; + } + const auto& fieldSet = subscription.getFieldSet(); + if (fieldSet.find(field) != fieldSet.end()) { + const auto& instrumentSet = subscription.getInstrumentSet(); + if (field == CCAPI_EM_PRIVATE_TRADE) { + for (const auto& x : document["fills"].GetArray()) { + std::string instrument = x["instrument"].GetString(); + if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { + Message message; + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_PRIVATE_TRADE); + message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(x["time"].GetString()))); + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + std::vector elementList; + Element element; + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_PRICE, x["price"].GetString()); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_SIZE, x["qty"].GetString()); + element.insert(CCAPI_EM_ORDER_SIDE, x["buy"].GetBool() ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); + element.insert(CCAPI_EM_ORDER_ID, std::string(x["order_id"].GetString())); + element.insert(CCAPI_EM_CLIENT_ORDER_ID, std::string(x["cli_ord_id"].GetString())); + element.insert(CCAPI_IS_MAKER, std::string(x["fill_type"].GetString()) == "maker" ? "1" : "0"); + element.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + element.insert(CCAPI_EM_ORDER_FEE_QUANTITY, std::string(x["fee_paid"].GetString())); + element.insert(CCAPI_EM_ORDER_FEE_ASSET, std::string(x["fee_currency"].GetString())); + elementList.emplace_back(std::move(element)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } else if (field == CCAPI_EM_ORDER_UPDATE) { + const rj::Value& x = document["order"]; std::string instrument = x["instrument"].GetString(); if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { Message message; - message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_PRIVATE_TRADE); - message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(x["time"].GetString()))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE); + message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(x["last_update_time"].GetString()))); message.setTimeReceived(timeReceived); message.setCorrelationIdList({subscription.getCorrelationId()}); std::vector elementList; Element element; - element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_PRICE, x["price"].GetString()); - element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_SIZE, x["qty"].GetString()); - element.insert(CCAPI_EM_ORDER_SIDE, x["buy"].GetBool() ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); - element.insert(CCAPI_EM_ORDER_ID, std::string(x["order_id"].GetString())); - element.insert(CCAPI_EM_CLIENT_ORDER_ID, std::string(x["cli_ord_id"].GetString())); - element.insert(CCAPI_IS_MAKER, std::string(x["fill_type"].GetString()) == "maker" ? "1" : "0"); element.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); - element.insert(CCAPI_EM_ORDER_FEE_QUANTITY, std::string(x["fee_paid"].GetString())); - element.insert(CCAPI_EM_ORDER_FEE_ASSET, std::string(x["fee_currency"].GetString())); + element.insert(CCAPI_EM_ORDER_QUANTITY, x["qty"].GetString()); + element.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, x["filled"].GetString()); + element.insert(CCAPI_EM_ORDER_LIMIT_PRICE, x["limit_price"].GetString()); + element.insert(CCAPI_EM_ORDER_ID, x["order_id"].GetString()); + element.insert(CCAPI_EM_ORDER_SIDE, std::string(x["direction"].GetString()) == "0" ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); + element.insert("is_cancel", document["is_cancel"].GetBool() ? "1" : "0"); elementList.emplace_back(std::move(element)); message.setElementList(elementList); messageList.emplace_back(std::move(message)); } } - } else if (field == CCAPI_EM_ORDER_UPDATE) { - const rj::Value& x = document["order"]; - std::string instrument = x["instrument"].GetString(); - if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { - Message message; - message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE); - message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(x["last_update_time"].GetString()))); - message.setTimeReceived(timeReceived); - message.setCorrelationIdList({subscription.getCorrelationId()}); - std::vector elementList; - Element element; - element.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); - element.insert(CCAPI_EM_ORDER_QUANTITY, x["qty"].GetString()); - element.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, x["filled"].GetString()); - element.insert(CCAPI_EM_ORDER_LIMIT_PRICE, x["limit_price"].GetString()); - element.insert(CCAPI_EM_ORDER_ID, x["order_id"].GetString()); - element.insert(CCAPI_EM_ORDER_SIDE, std::string(x["direction"].GetString()) == "0" ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); - element.insert("is_cancel", document["is_cancel"].GetBool() ? "1" : "0"); - elementList.emplace_back(std::move(element)); - message.setElementList(elementList); - messageList.emplace_back(std::move(message)); - } } } } @@ -353,25 +355,39 @@ class ExecutionManagementServiceKrakenFutures : public ExecutionManagementServic auto signature = UtilAlgorithm::base64Encode(Hmac::hmac(Hmac::ShaVersion::SHA512, UtilAlgorithm::base64Decode(apiSecret), challengeToSignSha256)); std::vector sendStringList; for (const auto& field : subscription.getFieldSet()) { - std::string feed; - if (field == CCAPI_EM_PRIVATE_TRADE) { - feed = "fills"; - } else if (field == CCAPI_EM_ORDER_UPDATE) { - feed = "open_orders"; + { + std::string feed; + if (field == CCAPI_EM_PRIVATE_TRADE) { + feed = "fills"; + } else if (field == CCAPI_EM_ORDER_UPDATE) { + feed = "open_orders"; + } + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("event", rj::Value("subscribe").Move(), allocator); + document.AddMember("feed", rj::Value(feed.c_str(), allocator).Move(), allocator); + document.AddMember("api_key", rj::Value(apiKey.c_str(), allocator).Move(), allocator); + document.AddMember("original_challenge", rj::Value(challengeToSign.c_str(), allocator).Move(), allocator); + document.AddMember("signed_challenge", rj::Value(signature.c_str(), allocator).Move(), allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + } + { + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("event", rj::Value("subscribe").Move(), allocator); + document.AddMember("feed", rj::Value("heartbeat").Move(), allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); } - rj::Document document; - document.SetObject(); - auto& allocator = document.GetAllocator(); - document.AddMember("event", rj::Value("subscribe").Move(), allocator); - document.AddMember("feed", rj::Value(feed.c_str(), allocator).Move(), allocator); - document.AddMember("api_key", rj::Value(apiKey.c_str(), allocator).Move(), allocator); - document.AddMember("original_challenge", rj::Value(challengeToSign.c_str(), allocator).Move(), allocator); - document.AddMember("signed_challenge", rj::Value(signature.c_str(), allocator).Move(), allocator); - rj::StringBuffer stringBuffer; - rj::Writer writer(stringBuffer); - document.Accept(writer); - std::string sendString = stringBuffer.GetString(); - sendStringList.push_back(sendString); } for (const auto& sendString : sendStringList) { ErrorCode ec; diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_mexc.h b/include/ccapi_cpp/service/ccapi_execution_management_service_mexc.h index 0d79f321..ac3d8263 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_mexc.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_mexc.h @@ -354,15 +354,17 @@ class ExecutionManagementServiceMexc : public ExecutionManagementService { if (document.IsObject() && document.HasMember("code") && std::string(document["code"].GetString()) == "0") { event.setType(Event::Type::SUBSCRIPTION_STATUS); std::string msg = document["msg"].GetString(); - bool success = msg != "no subscription success"; - Message message; - message.setTimeReceived(timeReceived); - message.setCorrelationIdList({subscription.getCorrelationId()}); - message.setType(success ? Message::Type::SUBSCRIPTION_STARTED : Message::Type::SUBSCRIPTION_FAILURE); - Element element; - element.insert(success ? CCAPI_INFO_MESSAGE : CCAPI_ERROR_MESSAGE, textMessage); - message.setElementList({element}); - messageList.emplace_back(std::move(message)); + if (msg != "PONG") { + bool success = msg != "no subscription success"; + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setType(success ? Message::Type::SUBSCRIPTION_STARTED : Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(success ? CCAPI_INFO_MESSAGE : CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + } } else { const auto& fieldSet = subscription.getFieldSet(); const auto& instrumentSet = subscription.getInstrumentSet(); @@ -401,14 +403,17 @@ class ExecutionManagementServiceMexc : public ExecutionManagementService { const std::map >& extractionFieldNameMap = { {CCAPI_EM_ORDER_ID, std::make_pair("i", JsonDataType::STRING)}, {CCAPI_EM_CLIENT_ORDER_ID, std::make_pair("c", JsonDataType::STRING)}, - {CCAPI_EM_ORDER_SIDE, std::make_pair("v", JsonDataType::STRING)}, {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("p", JsonDataType::STRING)}, - {CCAPI_EM_ORDER_QUANTITY, std::make_pair("size", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("v", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("cv", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, std::make_pair("ca", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_AVERAGE_FILLED_PRICE, std::make_pair("ap", JsonDataType::STRING)}, {CCAPI_EM_ORDER_REMAINING_QUANTITY, std::make_pair("V", JsonDataType::STRING)}, {CCAPI_EM_ORDER_STATUS, std::make_pair("s", JsonDataType::STRING)}, }; Element info; info.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + info.insert(CCAPI_EM_ORDER_SIDE, std::string(d["S"].GetString()) == "1" ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); this->extractOrderInfo(info, d, extractionFieldNameMap); std::vector elementList; elementList.emplace_back(std::move(info)); diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_whitebit.h b/include/ccapi_cpp/service/ccapi_execution_management_service_whitebit.h new file mode 100644 index 00000000..e69de29b diff --git a/include/ccapi_cpp/service/ccapi_market_data_service.h b/include/ccapi_cpp/service/ccapi_market_data_service.h index bf1e2a6b..24b395a5 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service.h @@ -1265,6 +1265,8 @@ class MarketDataService : public Service { const auto& correlationIdList = that->correlationIdListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).at(symbolId); std::map& snapshotBid = that->snapshotBidByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]; std::map& snapshotAsk = that->snapshotAskByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]; + snapshotBid.clear(); + snapshotAsk.clear(); MarketDataMessage::TypeForData input; that->extractOrderBookInitialData(input, document); for (const auto& x : input) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_cryptocom.h b/include/ccapi_cpp/service/ccapi_market_data_service_cryptocom.h index 56d4f66f..c1b1771d 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_cryptocom.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_cryptocom.h @@ -162,6 +162,7 @@ class MarketDataServiceCryptocom : public MarketDataService { } } } else { + int64_t id = std::stoll(it->value.GetString()); std::string method = document["method"].GetString(); if (method == "subscribe") { if (std::string(document["code"].GetString()) != "0") { @@ -200,8 +201,7 @@ class MarketDataServiceCryptocom : public MarketDataService { document.SetObject(); rj::Document::AllocatorType& allocator = document.GetAllocator(); auto now = UtilTime::now(); - this->appendParam(document, allocator, std::chrono::duration_cast(now.time_since_epoch()).count(), "public/respond-heartbeat", - {}); + this->appendParam(document, allocator, id, "public/respond-heartbeat", {}); rj::StringBuffer stringBuffer; rj::Writer writer(stringBuffer); document.Accept(writer); 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 949fed30..35726c31 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h @@ -11,7 +11,6 @@ class MarketDataServiceKraken : public MarketDataService { : MarketDataService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { this->exchangeName = CCAPI_EXCHANGE_NAME_KRAKEN; this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName); - this->shouldAlignSnapshot = true; this->baseUrlRest = sessionConfigs.getUrlRestBase().at(this->exchangeName); this->setHostRestFromUrlRest(this->baseUrlRest); try { @@ -22,6 +21,7 @@ class MarketDataServiceKraken : public MarketDataService { this->getRecentTradesTarget = "/0/public/Trades"; this->getInstrumentTarget = "/0/public/AssetPairs"; this->getInstrumentsTarget = "/0/public/AssetPairs"; + this->shouldAlignSnapshot = true; } virtual ~MarketDataServiceKraken() {} #ifndef CCAPI_EXPOSE_INTERNAL diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_kraken_futures.h b/include/ccapi_cpp/service/ccapi_market_data_service_kraken_futures.h index 7e5df46a..8d56d15f 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_kraken_futures.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_kraken_futures.h @@ -56,6 +56,18 @@ class MarketDataServiceKrakenFutures : public MarketDataService { std::string sendString = stringBuffer.GetString(); sendStringList.push_back(sendString); } + { + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("event", rj::Value("subscribe").Move(), allocator); + document.AddMember("feed", rj::Value("heartbeat").Move(), allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + } return sendStringList; } void processTextMessage(WsConnection& wsConnection, wspp::connection_hdl hdl, const std::string& textMessage, const TimePoint& timeReceived, Event& event, @@ -104,8 +116,8 @@ class MarketDataServiceKrakenFutures : public MarketDataService { } } else { std::string feed = document["feed"].GetString(); - std::string productId = document["product_id"].GetString(); if (feed == "book" || feed == "book_snapshot") { + std::string productId = document["product_id"].GetString(); if (feed == "book") { MarketDataMessage marketDataMessage; marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_MARKET_DEPTH; @@ -139,6 +151,7 @@ class MarketDataServiceKrakenFutures : public MarketDataService { marketDataMessageList.emplace_back(std::move(marketDataMessage)); } } else if (feed == "trade" || feed == "trade_snapshot") { + std::string productId = document["product_id"].GetString(); if (feed == "trade") { MarketDataMessage marketDataMessage; marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_mexc.h b/include/ccapi_cpp/service/ccapi_market_data_service_mexc.h index 5255216c..d60d2f0f 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_mexc.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_mexc.h @@ -28,7 +28,12 @@ class MarketDataServiceMexc : public MarketDataService { protected: #endif // bool doesHttpBodyContainError(const std::string& body) override { return body.find(R"("code":0)") == std::string::npos; } - void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, R"({"method":"PING"})", wspp::frame::opcode::text, ec); } + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { + WsConnection& wsConnection = this->getWsConnectionFromConnectionPtr(this->serviceContextPtr->tlsClientPtr->get_con_from_hdl(hdl)); + this->send(hdl, R"({"id":)" + std::to_string(this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]) + R"(,"method":"PING"})", + wspp::frame::opcode::text, ec); + this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] += 1; + } std::vector createSendStringList(const WsConnection& wsConnection) override { std::vector sendStringList; rj::Document document; @@ -250,6 +255,8 @@ class MarketDataServiceMexc : public MarketDataService { element.insert(CCAPI_ORDER_PRICE_INCREMENT, "1" + std::string(-quotePrecision, '0')); } element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, x["baseSizePrecision"].GetString()); + element.insert(CCAPI_ORDER_QUANTITY_MIN, x["baseSizePrecision"].GetString()); + element.insert(CCAPI_ORDER_PRICE_TIMES_QUANTITY_MIN, x["quoteAmountPrecision"].GetString()); } void convertTextMessageToMarketDataMessage(const Request& request, const std::string& textMessage, const TimePoint& timeReceived, Event& event, std::vector& marketDataMessageList) override { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_whitebit.h b/include/ccapi_cpp/service/ccapi_market_data_service_whitebit.h new file mode 100644 index 00000000..eb5a63c5 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_whitebit.h @@ -0,0 +1,331 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_WHITEBIT_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_WHITEBIT_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#ifdef CCAPI_ENABLE_EXCHANGE_WHITEBIT +#include "ccapi_cpp/service/ccapi_market_data_service.h" +namespace ccapi { +class MarketDataServiceWhitebit : public MarketDataService { + public: + MarketDataServiceWhitebit(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_WHITEBIT; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/ws"; + this->baseUrlRest = sessionConfigs.getUrlRestBase().at(this->exchangeName); + this->setHostRestFromUrlRest(this->baseUrlRest); + try { + this->tcpResolverResultsRest = this->resolver.resolve(this->hostRest, this->portRest); + } catch (const std::exception& e) { + CCAPI_LOGGER_FATAL(std::string("e.what() = ") + e.what()); + } + this->getRecentTradesTarget = "/api/v4/public/trades/{market}"; + this->getInstrumentsTarget = "/api/v4/public/markets"; + this->methodDepthSubscribe = std::string(CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_DEPTH) + "_subscribe"; + this->methodTradesSubscribe = std::string(CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES) + "_subscribe"; + this->methodDepthUpdate = std::string(CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_DEPTH) + "_update"; + this->methodTradesUpdate = std::string(CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES) + "_update"; + this->shouldAlignSnapshot = true; + } + virtual ~MarketDataServiceWhitebit() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + private: +#endif + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { + WsConnection& wsConnection = this->getWsConnectionFromConnectionPtr(this->serviceContextPtr->tlsClientPtr->get_con_from_hdl(hdl)); + this->send(hdl, R"({"id":)" + std::to_string(this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]) + R"(,"method":"ping","params":[]})", + wspp::frame::opcode::text, ec); + this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] += 1; + } + void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, + const Subscription& subscription, const std::map optionMap) override { + auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); + if (field == CCAPI_MARKET_DEPTH) { + int marketDepthSubscribedToExchange = marketDepthRequested; + // channelId += std::string("?") + CCAPI_MARKET_DEPTH_SUBSCRIBED_TO_EXCHANGE + "=" + std::to_string(marketDepthSubscribedToExchange); + this->marketDepthSubscribedToExchangeByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId] = marketDepthSubscribedToExchange; + } + } + std::vector createSendStringList(const WsConnection& wsConnection) override { + std::vector sendStringList; + for (const auto& subscriptionListByChannelIdSymbolId : this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id)) { + auto channelId = subscriptionListByChannelIdSymbolId.first; + if (channelId == CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_DEPTH) { + for (const auto& subscriptionListByInstrument : subscriptionListByChannelIdSymbolId.second) { + auto symbolId = subscriptionListByInstrument.first; + auto exchangeSubscriptionId = channelId + subscriptionListByInstrument.first; + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember("id", rj::Value(this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]).Move(), allocator); + document.AddMember("method", rj::Value("depth_subscribe").Move(), allocator); + rj::Value params(rj::kArrayType); + params.PushBack(rj::Value(symbolId.c_str(), allocator).Move(), allocator); + int marketDepthSubscribedToExchange = + this->marketDepthSubscribedToExchangeByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).at(symbolId); + params.PushBack(rj::Value(marketDepthSubscribedToExchange).Move(), allocator); + params.PushBack(rj::Value("0").Move(), allocator); + params.PushBack(rj::Value(true).Move(), allocator); + document.AddMember("params", params, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID] = channelId; + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID] = symbolId; + this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] + [this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]] = { + exchangeSubscriptionId}; + this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] += 1; + } + } else if (channelId == CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES) { + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember("id", rj::Value(this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]).Move(), allocator); + document.AddMember("method", rj::Value("trades_subscribe").Move(), allocator); + rj::Value params(rj::kArrayType); + std::vector exchangeSubscriptionIdList; + for (const auto& subscriptionListByInstrument : subscriptionListByChannelIdSymbolId.second) { + auto symbolId = subscriptionListByInstrument.first; + params.PushBack(rj::Value(symbolId.c_str(), allocator).Move(), allocator); + auto exchangeSubscriptionId = channelId + subscriptionListByInstrument.first; + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID] = channelId; + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID] = symbolId; + exchangeSubscriptionIdList.push_back(exchangeSubscriptionId); + } + document.AddMember("params", params, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] + [this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]] = + exchangeSubscriptionIdList; + this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] += 1; + } + } + return sendStringList; + } + void processTextMessage(WsConnection& wsConnection, wspp::connection_hdl hdl, const std::string& textMessage, const TimePoint& timeReceived, Event& event, + std::vector& marketDataMessageList) override { + rj::Document document; + document.Parse(textMessage.c_str()); + auto itId = document.FindMember("id"); + if (itId == document.MemberEnd() || itId->value.IsNull()) { + std::string method = document["method"].GetString(); + const auto& params = document["params"]; + if (method == this->methodDepthUpdate) { + bool isFullReload = params[0].GetBool(); + std::string symbolId = params[2].GetString(); + const auto& exchangeSubscriptionId = std::string(CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_DEPTH) + symbolId; + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_MARKET_DEPTH; + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + marketDataMessage.recapType = isFullReload ? MarketDataMessage::RecapType::SOLICITED : MarketDataMessage::RecapType::NONE; + marketDataMessage.tp = timeReceived; + auto itBids = params[1].FindMember("bids"); + if (itBids != params[1].MemberEnd()) { + const rj::Value& bids = itBids->value; + for (const auto& x : bids.GetArray()) { + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(x[0].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(x[1].GetString())}); + marketDataMessage.data[MarketDataMessage::DataType::BID].emplace_back(std::move(dataPoint)); + } + } + auto itAsks = params[1].FindMember("asks"); + if (itAsks != params[1].MemberEnd()) { + const rj::Value& asks = itAsks->value; + for (const auto& x : asks.GetArray()) { + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(x[0].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(x[1].GetString())}); + marketDataMessage.data[MarketDataMessage::DataType::ASK].emplace_back(std::move(dataPoint)); + } + } + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } else if (method == this->methodTradesUpdate) { + std::string symbolId = params[0].GetString(); + const auto& exchangeSubscriptionId = std::string(CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES) + symbolId; + for (const auto& x : params[1].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + auto timePair = UtilTime::divide(std::string(x["time"].GetString())); + auto tp = TimePoint(std::chrono::duration(timePair.first)); + tp += std::chrono::nanoseconds(timePair.second); + marketDataMessage.tp = tp; + if (this->processedInitialSnapshotByConnectionIdChannelIdSymbolIdMap[wsConnection.id][CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES][symbolId]) { + marketDataMessage.recapType = MarketDataMessage::RecapType::NONE; + } else { + marketDataMessage.recapType = MarketDataMessage::RecapType::SOLICITED; + this->processedInitialSnapshotByConnectionIdChannelIdSymbolIdMap[wsConnection.id][CCAPI_WEBSOCKET_WHITEBIT_CHANNEL_MARKET_TRADES][symbolId] = true; + } + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(std::string(x["price"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(std::string(x["amount"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::TRADE_ID, std::string(x["id"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(x["type"].GetString()) == "sell" ? "1" : "0"}); + marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } + } + } else { + int id = std::stoi(itId->value.GetString()); + auto itResult = document.FindMember("result"); + if (itResult != document.MemberEnd()) { + if (itResult->value.IsObject()) { + const auto& result = itResult->value; + auto itError = document.FindMember("error"); + bool success = itError == document.MemberEnd() || itError->value.IsNull(); + event.setType(Event::Type::SUBSCRIPTION_STATUS); + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + std::vector correlationIdList; + if (this->correlationIdListByConnectionIdChannelIdSymbolIdMap.find(wsConnection.id) != + this->correlationIdListByConnectionIdChannelIdSymbolIdMap.end()) { + if (this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap.find(wsConnection.id) != + this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap.end() && + this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap.at(wsConnection.id).find(id) != + this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap.at(wsConnection.id).end()) { + for (const auto& exchangeSubscriptionId : this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap.at(wsConnection.id).at(id)) { + std::string channelId = + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID]; + std::string symbolId = this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID]; + if (this->correlationIdListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).find(channelId) != + this->correlationIdListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).end()) { + if (this->correlationIdListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).find(symbolId) != + this->correlationIdListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).end()) { + std::vector correlationIdList_2 = + this->correlationIdListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).at(symbolId); + correlationIdList.insert(correlationIdList.end(), correlationIdList_2.begin(), correlationIdList_2.end()); + } + } + } + } + } + message.setCorrelationIdList(correlationIdList); + message.setType(success ? Message::Type::SUBSCRIPTION_STARTED : Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(success ? CCAPI_INFO_MESSAGE : CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + event.setMessageList(messageList); + } + } + } + } + void convertRequestForRest(http::request& req, const Request& request, const TimePoint& now, const std::string& symbolId, + const std::map& credential) override { + switch (request.getOperation()) { + case Request::Operation::GENERIC_PUBLIC_REQUEST: { + MarketDataService::convertRequestForRestGenericPublicRequest(req, request, now, symbolId, credential); + } break; + case Request::Operation::GET_RECENT_TRADES: { + req.method(http::verb::get); + auto target = this->getRecentTradesTarget; + this->substituteParam(target, { + {"{market}", symbolId}, + }); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, {}); + req.target(queryString.empty() ? target : target + "?" + queryString); + } break; + case Request::Operation::GET_INSTRUMENT: { + req.method(http::verb::get); + auto target = this->getInstrumentsTarget; + req.target(target); + } break; + case Request::Operation::GET_INSTRUMENTS: { + req.method(http::verb::get); + req.target(this->getInstrumentsTarget); + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractInstrumentInfo(Element& element, const rj::Value& x) { + element.insert(CCAPI_INSTRUMENT, x["name"].GetString()); + element.insert(CCAPI_BASE_ASSET, x["stock"].GetString()); + element.insert(CCAPI_QUOTE_ASSET, x["money"].GetString()); + int moneyPrec = std::stoi(x["moneyPrec"].GetString()); + if (moneyPrec > 0) { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "0." + std::string(moneyPrec - 1, '0') + "1"); + } else { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "1" + std::string(-moneyPrec, '0')); + } + int stockPrec = std::stoi(x["stockPrec"].GetString()); + if (stockPrec > 0) { + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, "0." + std::string(stockPrec - 1, '0') + "1"); + } else { + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, "1" + std::string(-stockPrec, '0')); + } + element.insert(CCAPI_ORDER_QUANTITY_MIN, x["minAmount"].GetString()); + element.insert(CCAPI_ORDER_PRICE_TIMES_QUANTITY_MIN, x["minTotal"].GetString()); + } + void convertTextMessageToMarketDataMessage(const Request& request, const std::string& textMessage, const TimePoint& timeReceived, Event& event, + std::vector& marketDataMessageList) override { + rj::Document document; + document.Parse(textMessage.c_str()); + switch (request.getOperation()) { + case Request::Operation::GET_RECENT_TRADES: { + for (const auto& x : document.GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + auto timePair = UtilTime::divide(std::string(x["trade_timestamp"].GetString())); + auto tp = TimePoint(std::chrono::duration(timePair.first)); + tp += std::chrono::nanoseconds(timePair.second); + marketDataMessage.tp = tp; + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(std::string(x["price"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(std::string(x["base_volume"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::TRADE_ID, std::string(x["tradeID"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(x["type"].GetString()) == "sell" ? "1" : "0"}); + marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } + } break; + case Request::Operation::GET_INSTRUMENT: { + Message message; + message.setTimeReceived(timeReceived); + message.setType(this->requestOperationToMessageTypeMap.at(request.getOperation())); + for (const auto& x : document.GetArray()) { + if (std::string(x["name"].GetString()) == request.getInstrument()) { + Element element; + this->extractInstrumentInfo(element, x); + message.setElementList({element}); + break; + } + } + message.setCorrelationIdList({request.getCorrelationId()}); + event.addMessages({message}); + } break; + case Request::Operation::GET_INSTRUMENTS: { + Message message; + message.setTimeReceived(timeReceived); + message.setType(this->requestOperationToMessageTypeMap.at(request.getOperation())); + std::vector elementList; + for (const auto& x : document.GetArray()) { + Element element; + this->extractInstrumentInfo(element, x); + elementList.emplace_back(std::move(element)); + } + message.setElementList(elementList); + message.setCorrelationIdList({request.getCorrelationId()}); + event.addMessages({message}); + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } + std::string methodDepthSubscribe, methodDepthUpdate, methodTradesSubscribe, methodTradesUpdate; +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_WHITEBIT_H_ diff --git a/test/test_build/CMakeLists.txt b/test/test_build/CMakeLists.txt index c661dc52..8ce77e73 100644 --- a/test/test_build/CMakeLists.txt +++ b/test/test_build/CMakeLists.txt @@ -1,8 +1,8 @@ set(NAME build_test) project(${NAME}) set(SERVICE_LIST "MARKET_DATA" "EXECUTION_MANAGEMENT" "FIX") -set(MARKET_DATA_EXCHANGE_LIST "COINBASE" "GEMINI" "KRAKEN" "KRAKEN_FUTURES" "BITSTAMP" "BITFINEX" "BITMEX" "BINANCE_US" "BINANCE" "BINANCE_USDS_FUTURES" "BINANCE_COIN_FUTURES" "HUOBI" "HUOBI_USDT_SWAP" "HUOBI_COIN_SWAP" "OKX" "ERISX" "KUCOIN" "KUCOIN_FUTURES" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT" "BYBIT_DERIVATIVES" "BITGET" "BITGET_FUTURES" "BITMART" "MEXC" "MEXC_FUTURES") -set(EXECUTION_MANAGEMENT_EXCHANGE_LIST "COINBASE" "GEMINI" "KRAKEN" "KRAKEN_FUTURES" "BITSTAMP" "BITFINEX" "BITMEX" "BINANCE_US" "BINANCE" "BINANCE_USDS_FUTURES" "BINANCE_COIN_FUTURES" "HUOBI" "HUOBI_USDT_SWAP" "HUOBI_COIN_SWAP" "OKX" "ERISX" "KUCOIN" "KUCOIN_FUTURES" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT" "BYBIT_DERIVATIVES" "BITGET" "BITGET_FUTURES" "BITMART" "MEXC") +set(MARKET_DATA_EXCHANGE_LIST "COINBASE" "GEMINI" "KRAKEN" "KRAKEN_FUTURES" "BITSTAMP" "BITFINEX" "BITMEX" "BINANCE_US" "BINANCE" "BINANCE_USDS_FUTURES" "BINANCE_COIN_FUTURES" "HUOBI" "HUOBI_USDT_SWAP" "HUOBI_COIN_SWAP" "OKX" "ERISX" "KUCOIN" "KUCOIN_FUTURES" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT" "BYBIT_DERIVATIVES" "BITGET" "BITGET_FUTURES" "BITMART" "MEXC" "MEXC_FUTURES" "WHITEBIT") +set(EXECUTION_MANAGEMENT_EXCHANGE_LIST "COINBASE" "GEMINI" "KRAKEN" "KRAKEN_FUTURES" "BITSTAMP" "BITFINEX" "BITMEX" "BINANCE_US" "BINANCE" "BINANCE_USDS_FUTURES" "BINANCE_COIN_FUTURES" "HUOBI" "HUOBI_USDT_SWAP" "HUOBI_COIN_SWAP" "OKX" "ERISX" "KUCOIN" "KUCOIN_FUTURES" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT" "BYBIT_DERIVATIVES" "BITGET" "BITGET_FUTURES" "BITMART" "MEXC" "WHITEBIT") set(FIX_EXCHANGE_LIST "COINBASE" "GEMINI") set(HFFIX_INCLUDE_DIR ${CCAPI_PROJECT_DIR}/dependency/hffix/include) foreach(SERVICE IN LISTS SERVICE_LIST)