diff --git a/README.md b/README.md index aa57c055..d347eb30 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ * 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, ftx, ftx-us, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, ascendex. - * Execution Management: coinbase, gemini, kraken, kraken-futures, bitstamp, bitfinex, bitmex, binance-us, binance, binance-margin, binance-usds-futures, binance-coin-futures, huobi, huobi-usdt-swap, huobi-coin-swap, okx, erisx, kucoin, ftx, ftx-us, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, ascendex. + * 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, ftx, ftx-us, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, bybit-derivatives, ascendex, bitget, bitget-futures, bitmart. + * Execution Management: coinbase, gemini, kraken, kraken-futures, bitstamp, bitfinex, bitmex, binance-us, binance, binance-margin, binance-usds-futures, binance-coin-futures, huobi, huobi-usdt-swap, huobi-coin-swap, okx, erisx, kucoin, ftx, ftx-us, deribit, gateio, gateio-perpetual-futures, cryptocom, bybit, bybit-derivatives, ascendex, bitget. * FIX: coinbase, gemini, ftx, ftx-us. * A spot market making application is provided as an end-to-end solution for liquidity providers. * A single order execution application is provided as an end-to-end solution for executing large orders. diff --git a/app/user_specified_cmake_include.cmake.example b/app/user_specified_cmake_include.cmake.example index 3d15bc49..f42c688d 100644 --- a/app/user_specified_cmake_include.cmake.example +++ b/app/user_specified_cmake_include.cmake.example @@ -33,6 +33,8 @@ endif() # # add_compile_definitions(CCAPI_ENABLE_EXCHANGE_ASCENDEX) # +# add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BITGET) +# # find_package(ZLIB REQUIRED) # link_libraries(ZLIB::ZLIB) diff --git a/binding/user_specified_cmake_include.cmake.example b/binding/user_specified_cmake_include.cmake.example index c102e8fe..bfd6f835 100644 --- a/binding/user_specified_cmake_include.cmake.example +++ b/binding/user_specified_cmake_include.cmake.example @@ -47,9 +47,15 @@ include_guard(DIRECTORY) # add_compile_definitions(CCAPI_ENABLE_EXCHANGE_CRYPTOCOM) # # add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BYBIT) +# add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES) # # add_compile_definitions(CCAPI_ENABLE_EXCHANGE_ASCENDEX) # +# add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BITGET) +# add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES) +# +# add_compile_definitions(CCAPI_ENABLE_EXCHANGE_BITMART) +# # 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 d5c4a079..2dc0aac0 100644 --- a/include/ccapi_cpp/ccapi_macro.h +++ b/include/ccapi_cpp/ccapi_macro.h @@ -123,15 +123,21 @@ #ifndef CCAPI_EXCHANGE_NAME_BYBIT #define CCAPI_EXCHANGE_NAME_BYBIT "bybit" #endif -// #ifndef CCAPI_EXCHANGE_NAME_BYBIT_USDT_PERPETUAL -// #define CCAPI_EXCHANGE_NAME_BYBIT_USDT_PERPETUAL "bybit-usdt-perpetual" -// #endif -// #ifndef CCAPI_EXCHANGE_NAME_BYBIT_INVERSE_PERPETUAL -// #define CCAPI_EXCHANGE_NAME_BYBIT_INVERSE_PERPETUAL "bybit-inverse-perpetual" -// #endif +#ifndef CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES +#define CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES "bybit-derivatives" +#endif #ifndef CCAPI_EXCHANGE_NAME_ASCENDEX #define CCAPI_EXCHANGE_NAME_ASCENDEX "ascendex" #endif +#ifndef CCAPI_EXCHANGE_NAME_BITGET +#define CCAPI_EXCHANGE_NAME_BITGET "bitget" +#endif +#ifndef CCAPI_EXCHANGE_NAME_BITGET_FUTURES +#define CCAPI_EXCHANGE_NAME_BITGET_FUTURES "bitget-futures" +#endif +#ifndef CCAPI_EXCHANGE_NAME_BITMART +#define CCAPI_EXCHANGE_NAME_BITMART "bitmart" +#endif #ifndef CCAPI_LAST_PRICE #define CCAPI_LAST_PRICE "LAST_PRICE" #endif @@ -284,15 +290,21 @@ #define CCAPI_WEBSOCKET_BYBIT_CHANNEL_TRADE "trade.{symbol}" #define CCAPI_WEBSOCKET_BYBIT_CHANNEL_BOOK_TICKER "bookticker.{symbol}" #define CCAPI_WEBSOCKET_BYBIT_CHANNEL_DEPTH "orderbook.40.{symbol}" -// #define CCAPI_WEBSOCKET_BYBIT_USDT_PERPETUAL_CHANNEL_TRADE "trade" -// #define CCAPI_WEBSOCKET_BYBIT_USDT_PERPETUAL_CHANNEL_ORDER_BOOK_L2_25 "orderBookL2_25" -// #define CCAPI_WEBSOCKET_BYBIT_USDT_PERPETUAL_CHANNEL_ORDER_BOOK_L2_200 "orderBookL2_200" -// #define CCAPI_WEBSOCKET_BYBIT_INVERSE_PERPETUAL_CHANNEL_TRADE "trade" -// #define CCAPI_WEBSOCKET_BYBIT_INVERSE_PERPETUAL_CHANNEL_ORDER_BOOK_L2_25 "orderBookL2_25" -// #define CCAPI_WEBSOCKET_BYBIT_INVERSE_PERPETUAL_CHANNEL_ORDER_BOOK_L2_200 "orderBookL2_200" +#define CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_TRADE "publicTrade.{symbol}" +#define CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_ORDERBOOK "orderbook.{depth}.{symbol}" #define CCAPI_WEBSOCKET_ASCENDEX_CHANNEL_TRADES "trades" #define CCAPI_WEBSOCKET_ASCENDEX_CHANNEL_BBO "bbo" #define CCAPI_WEBSOCKET_ASCENDEX_CHANNEL_DEPTH "depth" +#define CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_TRADES "trade" +#define CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS "books" +#define CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS1 "books1" +#define CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS5 "books5" +#define CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS15 "books15" +#define CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_TRADES "trade" +#define CCAPI_WEBSOCKET_BITMART_CHANNEL_TRADE "spot/trade" +#define CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH5 "spot/depth5" +#define CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH20 "spot/depth20" +#define CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH50 "spot/depth50" #ifndef CCAPI_CHANNEL_ID #define CCAPI_CHANNEL_ID "channelId" #endif @@ -397,6 +409,9 @@ #ifndef CCAPI_EM_POSITION_COST #define CCAPI_EM_POSITION_COST "COST" #endif +#ifndef CCAPI_EM_POSITION_ENTRY_PRICE +#define CCAPI_EM_POSITION_ENTRY_PRICE "ENTRY_PRICE" +#endif #ifndef CCAPI_EM_PARTY_ID #define CCAPI_EM_PARTY_ID "PARTY_ID" #endif @@ -634,15 +649,21 @@ #ifndef CCAPI_BYBIT_URL_REST_BASE #define CCAPI_BYBIT_URL_REST_BASE "https://api.bybit.com" #endif -// #ifndef CCAPI_BYBIT_USDT_PERPETUAL_URL_REST_BASE -// #define CCAPI_BYBIT_USDT_PERPETUAL_URL_REST_BASE "https://api.bybit.com" -// #endif -// #ifndef CCAPI_BYBIT_INVERSE_PERPETUAL_URL_REST_BASE -// #define CCAPI_BYBIT_INVERSE_PERPETUAL_URL_REST_BASE "https://api.bybit.com" -// #endif +#ifndef CCAPI_BYBIT_DERIVATIVES_URL_REST_BASE +#define CCAPI_BYBIT_DERIVATIVES_URL_REST_BASE "https://api.bybit.com" +#endif #ifndef CCAPI_ASCENDEX_URL_REST_BASE #define CCAPI_ASCENDEX_URL_REST_BASE "https://ascendex.com" #endif +#ifndef CCAPI_BITGET_URL_REST_BASE +#define CCAPI_BITGET_URL_REST_BASE "https://api.bitget.com" +#endif +#ifndef CCAPI_BITGET_FUTURES_URL_REST_BASE +#define CCAPI_BITGET_FUTURES_URL_REST_BASE "https://api.bitget.com" +#endif +#ifndef CCAPI_BITMART_URL_REST_BASE +#define CCAPI_BITMART_URL_REST_BASE "https://api-cloud.bitmart.com" +#endif // end: exchange REST urls // start: exchange WS urls @@ -739,15 +760,21 @@ #ifndef CCAPI_BYBIT_URL_WS_BASE #define CCAPI_BYBIT_URL_WS_BASE "wss://stream.bybit.com" #endif -// #ifndef CCAPI_BYBIT_USDT_PERPETUAL_URL_WS_BASE -// #define CCAPI_BYBIT_USDT_PERPETUAL_URL_WS_BASE "https://api.bybit.com" -// #endif -// #ifndef CCAPI_BYBIT_INVERSE_PERPETUAL_URL_WS_BASE -// #define CCAPI_BYBIT_INVERSE_PERPETUAL_URL_WS_BASE "https://api.bybit.com" -// #endif +#ifndef CCAPI_BYBIT_DERIVATIVES_URL_WS_BASE +#define CCAPI_BYBIT_DERIVATIVES_URL_WS_BASE "wss://stream.bybit.com" +#endif #ifndef CCAPI_ASCENDEX_URL_WS_BASE #define CCAPI_ASCENDEX_URL_WS_BASE "wss://ascendex.com" #endif +#ifndef CCAPI_BITGET_URL_WS_BASE +#define CCAPI_BITGET_URL_WS_BASE "wss://ws.bitget.com" +#endif +#ifndef CCAPI_BITGET_FUTURES_URL_WS_BASE +#define CCAPI_BITGET_FUTURES_URL_WS_BASE "wss://ws.bitget.com" +#endif +#ifndef CCAPI_BITMART_URL_WS_BASE +#define CCAPI_BITMART_URL_WS_BASE "wss://ws-manager-compress.bitmart.com" +#endif // end: exchange WS urls // start: exchange FIX urls @@ -961,18 +988,12 @@ #ifndef CCAPI_BYBIT_API_SECRET #define CCAPI_BYBIT_API_SECRET "BYBIT_API_SECRET" #endif -// #ifndef CCAPI_BYBIT_USDT_PERPETUAL_API_KEY -// #define CCAPI_BYBIT_USDT_PERPETUAL_API_KEY "BYBIT_USDT_PERPETUAL_API_KEY" -// #endif -// #ifndef CCAPI_BYBIT_USDT_PERPETUAL_API_SECRET -// #define CCAPI_BYBIT_USDT_PERPETUAL_API_SECRET "BYBIT_USDT_PERPETUAL_API_SECRET" -// #endif -// #ifndef CCAPI_BYBIT_INVERSE_PERPETUAL_API_KEY -// #define CCAPI_BYBIT_INVERSE_PERPETUAL_API_KEY "BYBIT_INVERSE_PERPETUAL_API_KEY" -// #endif -// #ifndef CCAPI_BYBIT_INVERSE_PERPETUAL_API_SECRET -// #define CCAPI_BYBIT_INVERSE_PERPETUAL_API_SECRET "BYBIT_INVERSE_PERPETUAL_API_SECRET" -// #endif +#ifndef CCAPI_BYBIT_DERIVATIVES_API_KEY +#define CCAPI_BYBIT_DERIVATIVES_API_KEY "BYBIT_DERIVATIVES_API_KEY" +#endif +#ifndef CCAPI_BYBIT_DERIVATIVES_API_SECRET +#define CCAPI_BYBIT_DERIVATIVES_API_SECRET "BYBIT_DERIVATIVES_API_SECRET" +#endif #ifndef CCAPI_ASCENDEX_API_KEY #define CCAPI_ASCENDEX_API_KEY "ASCENDEX_API_KEY" #endif @@ -982,6 +1003,33 @@ #ifndef CCAPI_ASCENDEX_API_ACCOUNT_GROUP #define CCAPI_ASCENDEX_API_ACCOUNT_GROUP "ASCENDEX_API_ACCOUNT_GROUP" #endif +#ifndef CCAPI_BITGET_API_KEY +#define CCAPI_BITGET_API_KEY "BITGET_API_KEY" +#endif +#ifndef CCAPI_BITGET_API_SECRET +#define CCAPI_BITGET_API_SECRET "BITGET_API_SECRET" +#endif +#ifndef CCAPI_BITGET_API_PASSPHRASE +#define CCAPI_BITGET_API_PASSPHRASE "BITGET_API_PASSPHRASE" +#endif +#ifndef CCAPI_BITGET_FUTURES_API_KEY +#define CCAPI_BITGET_FUTURES_API_KEY "BITGET_FUTURES_API_KEY" +#endif +#ifndef CCAPI_BITGET_FUTURES_API_SECRET +#define CCAPI_BITGET_FUTURES_API_SECRET "BITGET_FUTURES_API_SECRET" +#endif +#ifndef CCAPI_BITGET_FUTURES_API_PASSPHRASE +#define CCAPI_BITGET_FUTURES_API_PASSPHRASE "BITGET_FUTURES_API_PASSPHRASE" +#endif +#ifndef CCAPI_BITMART_API_KEY +#define CCAPI_BITMART_API_KEY "BITMART_API_KEY" +#endif +#ifndef CCAPI_BITMART_API_SECRET +#define CCAPI_BITMART_API_SECRET "BITMART_API_SECRET" +#endif +#ifndef CCAPI_BITMART_API_MEMO +#define CCAPI_BITMART_API_MEMO "BITMART_API_MEMO" +#endif // end: exchange API credentials #define CCAPI_HTTP_PORT_DEFAULT "80" @@ -1011,8 +1059,8 @@ #ifndef CCAPI_FIX_PROTOCOL_VERSION_DERIBIT #define CCAPI_FIX_PROTOCOL_VERSION_DERIBIT "FIX.4.4" #endif -#ifndef CCAPI_BYBIT_API_RECEIVE_WINDOW_MILLISECONDS -#define CCAPI_BYBIT_API_RECEIVE_WINDOW_MILLISECONDS 5000 +#ifndef CCAPI_BYBIT_BASE_API_RECEIVE_WINDOW_MILLISECONDS +#define CCAPI_BYBIT_BASE_API_RECEIVE_WINDOW_MILLISECONDS 5000 #endif #ifndef CCAPI_OKX_API_BROKER_CODE diff --git a/include/ccapi_cpp/ccapi_session.h b/include/ccapi_cpp/ccapi_session.h index 8e133f6b..f8aeb0fd 100644 --- a/include/ccapi_cpp/ccapi_session.h +++ b/include/ccapi_cpp/ccapi_session.h @@ -79,9 +79,21 @@ #ifdef CCAPI_ENABLE_EXCHANGE_BYBIT #include "ccapi_cpp/service/ccapi_market_data_service_bybit.h" #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES +#include "ccapi_cpp/service/ccapi_market_data_service_bybit_derivatives.h" +#endif #ifdef CCAPI_ENABLE_EXCHANGE_ASCENDEX #include "ccapi_cpp/service/ccapi_market_data_service_ascendex.h" #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET +#include "ccapi_cpp/service/ccapi_market_data_service_bitget.h" +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES +#include "ccapi_cpp/service/ccapi_market_data_service_bitget_futures.h" +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITMART +#include "ccapi_cpp/service/ccapi_market_data_service_bitmart.h" +#endif #endif // end: enable exchanges for market data @@ -165,9 +177,21 @@ #ifdef CCAPI_ENABLE_EXCHANGE_BYBIT #include "ccapi_cpp/service/ccapi_execution_management_service_bybit.h" #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES +#include "ccapi_cpp/service/ccapi_execution_management_service_bybit_derivatives.h" +#endif #ifdef CCAPI_ENABLE_EXCHANGE_ASCENDEX #include "ccapi_cpp/service/ccapi_execution_management_service_ascendex.h" #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET +#include "ccapi_cpp/service/ccapi_execution_management_service_bitget.h" +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES +#include "ccapi_cpp/service/ccapi_execution_management_service_bitget_futures.h" +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITMART +#include "ccapi_cpp/service/ccapi_execution_management_service_bitmart.h" +#endif #endif // end: enable exchanges for execution management @@ -357,10 +381,26 @@ class Session { this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_BYBIT] = std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES + this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif #ifdef CCAPI_ENABLE_EXCHANGE_ASCENDEX this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_ASCENDEX] = std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET + this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_BITGET] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES + this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_BITGET_FUTURES] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITMART + this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_BITMART] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif #endif #ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT #ifdef CCAPI_ENABLE_EXCHANGE_COINBASE @@ -468,10 +508,26 @@ class Session { this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_BYBIT] = std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES + this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif #ifdef CCAPI_ENABLE_EXCHANGE_ASCENDEX this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_ASCENDEX] = std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); #endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET + this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_BITGET] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES + this->serviceByServiceNameExchangeMap[CCAPI_EXECUTION_MANAGEMENT][CCAPI_EXCHANGE_NAME_BITGET_FUTURES] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif +#ifdef CCAPI_ENABLE_EXCHANGE_BITMART + this->serviceByServiceNameExchangeMap[CCAPI_MARKET_DATA][CCAPI_EXCHANGE_NAME_BITMART] = + std::make_shared(this->internalEventHandler, sessionOptions, sessionConfigs, this->serviceContextPtr); +#endif #endif #ifdef CCAPI_ENABLE_SERVICE_FIX #ifdef CCAPI_ENABLE_EXCHANGE_COINBASE @@ -575,6 +631,18 @@ class Session { "unsupported exchange fields: " + toString(unsupportedExchangeFieldSet)); return; } + for (const auto& subscription : subscriptionList) { + auto exchange = subscription.getExchange(); + if (exchange == CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES) { + const auto& instrumentType = subscription.getInstrumentType(); + std::vector instrumentTypeList = {"usdt-contract", "usdc-contract", "usdc-options"}; + if (std::find(instrumentTypeList.begin(), instrumentTypeList.end(), instrumentType) == instrumentTypeList.end()) { + this->onError(Event::Type::SUBSCRIPTION_STATUS, Message::Type::SUBSCRIPTION_FAILURE, + "unsupported exchange instrument types: " + toString(instrumentType) + ". Allowed values: " + toString(instrumentTypeList) + "."); + return; + } + } + } CCAPI_LOGGER_TRACE("subscriptionListByExchangeMap = " + toString(subscriptionListByExchangeMap)); for (auto& subscriptionListByExchange : subscriptionListByExchangeMap) { auto exchange = subscriptionListByExchange.first; diff --git a/include/ccapi_cpp/ccapi_session_configs.h b/include/ccapi_cpp/ccapi_session_configs.h index 09cd8a36..2fa4f48e 100644 --- a/include/ccapi_cpp/ccapi_session_configs.h +++ b/include/ccapi_cpp/ccapi_session_configs.h @@ -135,10 +135,26 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_TRADE, CCAPI_WEBSOCKET_BYBIT_CHANNEL_TRADE}, {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_BYBIT_CHANNEL_DEPTH}, }; + std::map fieldWebsocketChannelMapBybitDerivatives = { + {CCAPI_TRADE, CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_TRADE}, + {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_ORDERBOOK}, + }; std::map fieldWebsocketChannelMapAscendex = { {CCAPI_TRADE, CCAPI_WEBSOCKET_ASCENDEX_CHANNEL_TRADES}, {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_ASCENDEX_CHANNEL_DEPTH}, }; + std::map fieldWebsocketChannelMapBitget = { + {CCAPI_TRADE, CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_TRADES}, + {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS}, + }; + std::map fieldWebsocketChannelMapBitgetFutures = { + {CCAPI_TRADE, CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_TRADES}, + {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS}, + }; + std::map fieldWebsocketChannelMapBitmart = { + {CCAPI_TRADE, CCAPI_WEBSOCKET_BITMART_CHANNEL_TRADE}, + {CCAPI_MARKET_DEPTH, CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH5}, + }; for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapCoinbase) { this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_COINBASE].push_back(fieldWebsocketChannel.first); } @@ -214,9 +230,21 @@ class SessionConfigs CCAPI_FINAL { for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapBybit) { this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_BYBIT].push_back(fieldWebsocketChannel.first); } + for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapBybitDerivatives) { + this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES].push_back(fieldWebsocketChannel.first); + } for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapAscendex) { this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_ASCENDEX].push_back(fieldWebsocketChannel.first); } + for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapBitget) { + this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_BITGET].push_back(fieldWebsocketChannel.first); + } + for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapBitgetFutures) { + this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_BITGET_FUTURES].push_back(fieldWebsocketChannel.first); + } + for (auto const& fieldWebsocketChannel : fieldWebsocketChannelMapBitmart) { + this->exchangeFieldMap[CCAPI_EXCHANGE_NAME_BITMART].push_back(fieldWebsocketChannel.first); + } for (auto& x : this->exchangeFieldMap) { x.second.push_back(CCAPI_GENERIC_PUBLIC_SUBSCRIPTION); } @@ -247,7 +275,11 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_EXCHANGE_NAME_GATEIO_PERPETUAL_FUTURES, fieldWebsocketChannelMapGateioPerpetualFutures}, {CCAPI_EXCHANGE_NAME_CRYPTOCOM, fieldWebsocketChannelMapCryptocom}, {CCAPI_EXCHANGE_NAME_BYBIT, fieldWebsocketChannelMapBybit}, + {CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES, fieldWebsocketChannelMapBybitDerivatives}, {CCAPI_EXCHANGE_NAME_ASCENDEX, fieldWebsocketChannelMapAscendex}, + {CCAPI_EXCHANGE_NAME_BITGET, fieldWebsocketChannelMapBitget}, + {CCAPI_EXCHANGE_NAME_BITGET_FUTURES, fieldWebsocketChannelMapBitgetFutures}, + {CCAPI_EXCHANGE_NAME_BITMART, fieldWebsocketChannelMapBitmart}, }; this->urlWebsocketBase = { {CCAPI_EXCHANGE_NAME_COINBASE, CCAPI_COINBASE_URL_WS_BASE}, @@ -279,7 +311,11 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_EXCHANGE_NAME_GATEIO_PERPETUAL_FUTURES, CCAPI_GATEIO_PERPETUAL_FUTURES_URL_WS_BASE}, {CCAPI_EXCHANGE_NAME_CRYPTOCOM, CCAPI_CRYPTOCOM_URL_WS_BASE}, {CCAPI_EXCHANGE_NAME_BYBIT, CCAPI_BYBIT_URL_WS_BASE}, + {CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES, CCAPI_BYBIT_DERIVATIVES_URL_WS_BASE}, {CCAPI_EXCHANGE_NAME_ASCENDEX, CCAPI_ASCENDEX_URL_WS_BASE}, + {CCAPI_EXCHANGE_NAME_BITGET, CCAPI_BITGET_URL_WS_BASE}, + {CCAPI_EXCHANGE_NAME_BITGET_FUTURES, CCAPI_BITGET_FUTURES_URL_WS_BASE}, + {CCAPI_EXCHANGE_NAME_BITMART, CCAPI_BITMART_URL_WS_BASE}, }; this->initialSequenceByExchangeMap = {{CCAPI_EXCHANGE_NAME_GEMINI, 0}, {CCAPI_EXCHANGE_NAME_BITFINEX, 1}}; } @@ -312,7 +348,11 @@ class SessionConfigs CCAPI_FINAL { {CCAPI_EXCHANGE_NAME_GATEIO_PERPETUAL_FUTURES, CCAPI_GATEIO_PERPETUAL_FUTURES_URL_REST_BASE}, {CCAPI_EXCHANGE_NAME_CRYPTOCOM, CCAPI_CRYPTOCOM_URL_REST_BASE}, {CCAPI_EXCHANGE_NAME_BYBIT, CCAPI_BYBIT_URL_REST_BASE}, + {CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES, CCAPI_BYBIT_DERIVATIVES_URL_REST_BASE}, {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}, }; } void initializUrlFixBase() { diff --git a/include/ccapi_cpp/ccapi_subscription.h b/include/ccapi_cpp/ccapi_subscription.h index 81cfb96f..bfeb81bc 100644 --- a/include/ccapi_cpp/ccapi_subscription.h +++ b/include/ccapi_cpp/ccapi_subscription.h @@ -59,8 +59,8 @@ class Subscription CCAPI_FINAL { for (const auto& x : credential) { shortCredential.insert(std::make_pair(x.first, UtilString::firstNCharacter(x.second, CCAPI_CREDENTIAL_DISPLAY_LENGTH))); } - std::string output = "Subscription [exchange = " + exchange + ", instrument = " + instrument + ", field = " + field + - ", optionMap = " + ccapi::toString(optionMap) + ", correlationId = " + correlationId + + std::string output = "Subscription [exchange = " + exchange + ", instrumentType = " + instrumentType + ", instrument = " + instrument + + ", field = " + field + ", optionMap = " + ccapi::toString(optionMap) + ", correlationId = " + correlationId + ", credential = " + ccapi::toString(shortCredential) + ", serviceName = " + serviceName + ", timeSent = " + UtilTime::getISOTimestamp(timeSent) + "]"; return output; @@ -68,6 +68,7 @@ class Subscription CCAPI_FINAL { const std::string& getCorrelationId() const { return correlationId; } const std::string& getExchange() const { return exchange; } const std::string& getInstrument() const { return instrument; } + const std::string& getInstrumentType() const { return instrumentType; } const std::string& getField() const { return field; } const std::string& getRawOptions() const { return rawOptions; } const std::map& getOptionMap() const { return optionMap; } @@ -99,6 +100,7 @@ class Subscription CCAPI_FINAL { std::string getTimeSentISO() const { return UtilTime::getISOTimestamp(timeSent); } std::pair getTimeSentPair() const { return UtilTime::divide(timeSent); } void setTimeSent(TimePoint timeSent) { this->timeSent = timeSent; } + void setInstrumentType(const std::string& instrumentType) { this->instrumentType = instrumentType; } enum class Status { UNKNOWN, SUBSCRIBING, @@ -134,6 +136,7 @@ class Subscription CCAPI_FINAL { private: #endif std::string exchange; + std::string instrumentType; std::string instrument; std::string field; std::string rawOptions; diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_binance_base.h b/include/ccapi_cpp/service/ccapi_execution_management_service_binance_base.h index f48e31d6..b6b986c5 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_binance_base.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_binance_base.h @@ -397,7 +397,7 @@ class ExecutionManagementServiceBinanceBase : public ExecutionManagementService auto it = data.FindMember("ap"); if (it != data.MemberEnd() && !it->value.IsNull()) { info.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, - std::to_string(std::stod(it->value.GetString()) * std::stod(data["z"].GetString()))); + UtilString::printDoubleScientific(std::stod(it->value.GetString()) * std::stod(data["z"].GetString()))); } } std::vector elementList; diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_binance_derivatives_base.h b/include/ccapi_cpp/service/ccapi_execution_management_service_binance_derivatives_base.h index cb2a034e..95a90b4c 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_binance_derivatives_base.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_binance_derivatives_base.h @@ -43,7 +43,7 @@ class ExecutionManagementServiceBinanceDerivativesBase : public ExecutionManagem positionAmt = x["maxQty"].GetString(); } element.insert(CCAPI_EM_POSITION_QUANTITY, positionAmt); - element.insert(CCAPI_EM_POSITION_COST, std::to_string(std::stod(x["entryPrice"].GetString()) * std::stod(positionAmt))); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["entryPrice"].GetString()); element.insert(CCAPI_EM_POSITION_LEVERAGE, x["leverage"].GetString()); elementList.emplace_back(std::move(element)); } diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bitfinex.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bitfinex.h index 98c99197..1d39dc25 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_bitfinex.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bitfinex.h @@ -269,7 +269,7 @@ class ExecutionManagementServiceBitfinex : public ExecutionManagementService { std::string availPos = x["availPos"].GetString(); std::string positionQuantity = availPos.empty() ? x["pos"].GetString() : availPos; element.insert(CCAPI_EM_POSITION_QUANTITY, positionQuantity); - element.insert(CCAPI_EM_POSITION_COST, std::to_string(std::stod(x["avgPx"].GetString()) * std::stod(positionQuantity))); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["avgPx"].GetString()); element.insert(CCAPI_EM_POSITION_LEVERAGE, x["lever"].GetString()); elementList.emplace_back(std::move(element)); } diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bitget.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bitget.h new file mode 100644 index 00000000..30babb88 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bitget.h @@ -0,0 +1,389 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_H_ +#ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET +#include "ccapi_cpp/service/ccapi_execution_management_service_bitget_base.h" +namespace ccapi { +class ExecutionManagementServiceBitget : public ExecutionManagementServiceBitgetBase { + public: + ExecutionManagementServiceBitget(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + ServiceContextPtr serviceContextPtr) + : ExecutionManagementServiceBitgetBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BITGET; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/spot/v1/stream"; + 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->apiKeyName = CCAPI_BITGET_API_KEY; + this->apiSecretName = CCAPI_BITGET_API_SECRET; + this->apiPassphraseName = CCAPI_BITGET_API_PASSPHRASE; + this->setupCredential({this->apiKeyName, this->apiSecretName, this->apiPassphraseName}); + this->createOrderTarget = "/api/spot/v1/trade/orders"; + this->cancelOrderTarget = "/api/spot/v1/trade/cancel-order"; + this->getOrderTarget = "/api/spot/v1/trade/orderInfo"; + this->getOpenOrdersTarget = "/api/spot/v1/trade/open-orders"; + this->getAccountBalancesTarget = "/api/spot/v1/account/assets"; + } + virtual ~ExecutionManagementServiceBitget() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + private: +#endif + void appendParam(Request::Operation operation, rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::map& param, + const std::map standardizationMap = { + {CCAPI_EM_ORDER_SIDE, "side"}, + {CCAPI_EM_ORDER_QUANTITY, "quantity"}, + {CCAPI_EM_ORDER_LIMIT_PRICE, "price"}, + {CCAPI_EM_CLIENT_ORDER_ID, "clientOrderId"}, + {CCAPI_SYMBOL_ID, "symbol"}, + {CCAPI_EM_ORDER_ID, "orderId"}, + }) { + for (const auto& kv : param) { + auto key = standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; + auto value = kv.second; + if (key == "side") { + value = (value == CCAPI_EM_ORDER_SIDE_BUY || value == "buy") ? "buy" : "sell"; + } + rjValue.AddMember(rj::Value(key.c_str(), allocator).Move(), rj::Value(value.c_str(), allocator).Move(), allocator); + } + } + void appendParam(std::string& queryString, const std::map& param, + const std::map standardizationMap = { + {CCAPI_EM_ORDER_ID, "orderId"}, + {CCAPI_EM_CLIENT_ORDER_ID, "clientOrderId"}, + {CCAPI_SYMBOL_ID, "symbol"}, + }) { + for (const auto& kv : param) { + queryString += standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; + queryString += "="; + queryString += Url::urlEncode(kv.second); + queryString += "&"; + } + } + void convertRequestForRest(http::request& req, const Request& request, const TimePoint& now, const std::string& symbolId, + const std::map& credential) override { + this->prepareReq(req, request, now, symbolId, credential); + Request::Operation operation = request.getOperation(); + switch (operation) { + case Request::Operation::GENERIC_PRIVATE_REQUEST: { + ExecutionManagementService::convertRequestForRestGenericPrivateRequest(req, request, now, symbolId, credential); + } break; + case Request::Operation::CREATE_ORDER: { + req.method(http::verb::post); + req.target(this->createOrderTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (param.find("orderType") == param.end()) { + document.AddMember("orderType", rj::Value("limit").Move(), allocator); + } + if (param.find("force") == param.end()) { + document.AddMember("force", rj::Value("normal").Move(), allocator); + } + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::CANCEL_ORDER: { + req.method(http::verb::post); + req.target(this->cancelOrderTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::GET_ORDER: { + req.method(http::verb::post); + req.target(this->getOrderTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::GET_OPEN_ORDERS: { + req.method(http::verb::post); + req.target(this->getOpenOrdersTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::GET_ACCOUNT_BALANCES: { + req.method(http::verb::get); + req.target(this->getAccountBalancesTarget); + this->signRequest(req, "", credential); + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractOrderInfoFromRequest(std::vector& elementList, const Request& request, const Request::Operation operation, + const rj::Document& document) override { + const std::map >& extractionFieldNameMap = { + {CCAPI_EM_ORDER_ID, std::make_pair("orderId", JsonDataType::STRING)}, + {CCAPI_EM_CLIENT_ORDER_ID, std::make_pair("clientOrderId", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_SIDE, std::make_pair("side", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("quantity", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("price", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("fillQuantity", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, std::make_pair("fillTotalAmount", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_STATUS, std::make_pair("status", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_INSTRUMENT, std::make_pair("symbol", JsonDataType::STRING)}}; + if (operation != Request::Operation::CANCEL_ORDER) { + const rj::Value& data = document["data"]; + if (data.IsObject()) { + Element element; + this->extractOrderInfo(element, data, extractionFieldNameMap); + elementList.emplace_back(std::move(element)); + } else { + for (const auto& x : data.GetArray()) { + Element element; + this->extractOrderInfo(element, x, extractionFieldNameMap); + elementList.emplace_back(std::move(element)); + } + } + } + } + void extractAccountInfoFromRequest(std::vector& elementList, const Request& request, const Request::Operation operation, + const rj::Document& document) override { + switch (request.getOperation()) { + case Request::Operation::GET_ACCOUNT_BALANCES: { + for (const auto& x : document["data"].GetArray()) { + Element element; + element.insert(CCAPI_EM_ASSET, x["coinName"].GetString()); + std::string available = x["available"].GetString(); + element.insert(CCAPI_EM_QUANTITY_AVAILABLE_FOR_TRADING, available); + std::string frozen = x["frozen"].GetString(); + std::string lock = x["lock"].GetString(); + element.insert(CCAPI_EM_QUANTITY_TOTAL, (Decimal(available).add(Decimal(frozen)).add(Decimal(lock))).toString()); + elementList.emplace_back(std::move(element)); + } + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } + void extractOrderInfo(Element& element, const rj::Value& x, + const std::map >& extractionFieldNameMap) override { + ExecutionManagementService::extractOrderInfo(element, x, extractionFieldNameMap); + { + auto it1 = x.FindMember("accFillSz"); + auto it2 = x.FindMember("avgPx"); + if (it1 != x.MemberEnd() && it2 != x.MemberEnd()) { + auto it1Str = std::string(it1->value.GetString()); + auto it2Str = std::string(it2->value.GetString()); + if (!it1Str.empty() && !it2Str.empty()) { + element.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, UtilString::printDoubleScientific(std::stod(it1Str) * std::stod(it2Str))); + } + } + } + } + std::vector createSendStringListFromSubscription(const WsConnection& wsConnection, const Subscription& subscription, const TimePoint& now, + const std::map& credential) override { + std::vector sendStringList; + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("login").Move(), allocator); + rj::Value arg(rj::kObjectType); + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto apiPassphrase = mapGetWithDefault(credential, this->apiPassphraseName); + std::string ts = std::to_string(std::chrono::duration_cast(now.time_since_epoch()).count()); + arg.AddMember("apiKey", rj::Value(apiKey.c_str(), allocator).Move(), allocator); + arg.AddMember("passphrase", rj::Value(apiPassphrase.c_str(), allocator).Move(), allocator); + arg.AddMember("timestamp", rj::Value(ts.c_str(), allocator).Move(), allocator); + std::string signData = ts + "GET" + "/user/verify"; + std::string sign = UtilAlgorithm::base64Encode(Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, signData)); + arg.AddMember("sign", rj::Value(sign.c_str(), allocator).Move(), allocator); + rj::Value args(rj::kArrayType); + args.PushBack(arg, allocator); + document.AddMember("args", args, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + return sendStringList; + } + void onTextMessage(const WsConnection& wsConnection, const Subscription& subscription, const std::string& textMessage, + const TimePoint& timeReceived) override { + if (textMessage != "pong") { + rj::Document document; + document.Parse(textMessage.c_str()); + auto it = document.FindMember("event"); + std::string eventStr = it != document.MemberEnd() ? it->value.GetString() : ""; + if (eventStr == "login") { + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("subscribe").Move(), allocator); + rj::Value args(rj::kArrayType); + const auto& fieldSet = subscription.getFieldSet(); + const auto& instrumentSet = subscription.getInstrumentSet(); + for (const auto& field : fieldSet) { + std::string channel; + if (fieldSet.find(CCAPI_EM_ORDER_UPDATE) != fieldSet.end() || fieldSet.find(CCAPI_EM_PRIVATE_TRADE) != fieldSet.end()) { + channel = "orders"; + } + for (const auto& instrument : instrumentSet) { + rj::Value arg(rj::kObjectType); + arg.AddMember("channel", rj::Value(channel.c_str(), allocator).Move(), allocator); + arg.AddMember("instId", rj::Value(instrument.c_str(), allocator).Move(), allocator); + arg.AddMember("instType", rj::Value("spbl").Move(), allocator); + args.PushBack(arg, allocator); + } + } + document.AddMember("args", args, allocator); + rj::StringBuffer stringBufferSubscribe; + rj::Writer writerSubscribe(stringBufferSubscribe); + document.Accept(writerSubscribe); + std::string sendString = stringBufferSubscribe.GetString(); + ErrorCode ec; + this->send(wsConnection.hdl, sendString, wspp::frame::opcode::text, ec); + if (ec) { + this->onError(Event::Type::SUBSCRIPTION_STATUS, Message::Type::SUBSCRIPTION_FAILURE, ec, "subscribe"); + } + } else { + Event event = this->createEvent(subscription, textMessage, document, eventStr, timeReceived); + if (!event.getMessageList().empty()) { + this->eventHandler(event, nullptr); + } + } + } + } + Event createEvent(const Subscription& subscription, const std::string& textMessage, const rj::Document& document, const std::string& eventStr, + const TimePoint& timeReceived) { + Event event; + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + const auto& correlationId = subscription.getCorrelationId(); + message.setCorrelationIdList({correlationId}); + const auto& fieldSet = subscription.getFieldSet(); + const auto& instrumentSet = subscription.getInstrumentSet(); + if (eventStr.empty()) { + const rj::Value& arg = document["arg"]; + std::string channel = std::string(arg["channel"].GetString()); + event.setType(Event::Type::SUBSCRIPTION_DATA); + std::string instrument = arg["instId"].GetString(); + if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { + if (channel == "orders") { + if (fieldSet.find(CCAPI_EM_PRIVATE_TRADE) != fieldSet.end()) { + const rj::Value& data = document["data"]; + for (const auto& x : data.GetArray()) { + auto itTradeId = x.FindMember("tradeId"); + std::string tradeId = itTradeId != x.MemberEnd() ? itTradeId->value.GetString() : ""; + if (!tradeId.empty()) { + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(std::string(x["fillTime"].GetString())))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_PRIVATE_TRADE); + std::vector elementList; + Element element; + element.insert(CCAPI_TRADE_ID, tradeId); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_PRICE, std::string(x["fillPx"].GetString())); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_SIZE, std::string(x["fillSz"].GetString())); + element.insert(CCAPI_EM_ORDER_SIDE, std::string(x["side"].GetString()) == "buy" ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); + element.insert(CCAPI_IS_MAKER, std::string(x["execType"].GetString()) == "M" ? "1" : "0"); + element.insert(CCAPI_EM_ORDER_ID, std::string(x["ordId"].GetString())); + element.insert(CCAPI_EM_CLIENT_ORDER_ID, std::string(x["clOrdId"].GetString())); + element.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + element.insert(CCAPI_EM_ORDER_FEE_QUANTITY, std::string(x["fillFee"].GetString())); + element.insert(CCAPI_EM_ORDER_FEE_ASSET, std::string(x["fillFeeCcy"].GetString())); + elementList.emplace_back(std::move(element)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } + if (fieldSet.find(CCAPI_EM_ORDER_UPDATE) != fieldSet.end()) { + const rj::Value& data = document["data"]; + for (const auto& x : data.GetArray()) { + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(std::string(x["uTime"].GetString())))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE); + const std::map >& extractionFieldNameMap = { + {CCAPI_EM_ORDER_ID, std::make_pair("ordId", JsonDataType::STRING)}, + {CCAPI_EM_CLIENT_ORDER_ID, std::make_pair("clOrdId", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_SIDE, std::make_pair("side", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("px", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("sz", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("accFillSz", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_STATUS, std::make_pair("status", JsonDataType::STRING)}, + }; + Element info; + this->extractOrderInfo(info, x, extractionFieldNameMap); + info.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + std::vector elementList; + elementList.emplace_back(std::move(info)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } + } + } else if (eventStr == "subscribe") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + message.setType(Message::Type::SUBSCRIPTION_STARTED); + Element element; + element.insert(CCAPI_INFO_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + } else if (eventStr == "error") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + message.setType(Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + } + event.setMessageList(messageList); + return event; + } +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_H_ diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bitget_base.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bitget_base.h new file mode 100644 index 00000000..cf38ada0 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bitget_base.h @@ -0,0 +1,74 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_BASE_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_BASE_H_ +#ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT +#if defined(CCAPI_ENABLE_EXCHANGE_BITGET) || defined(CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES) +#include "ccapi_cpp/service/ccapi_execution_management_service.h" +namespace ccapi { +class ExecutionManagementServiceBitgetBase : public ExecutionManagementService { + public: + ExecutionManagementServiceBitgetBase(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + ServiceContextPtr serviceContextPtr) + : ExecutionManagementService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) {} + virtual ~ExecutionManagementServiceBitgetBase() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + protected: +#endif + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, "ping", wspp::frame::opcode::text, ec); } + bool doesHttpBodyContainError(const Request& request, const std::string& body) override { + return !std::regex_search(body, std::regex("\"code\":\\s*\"00000\"")); + } + void signReqeustForRestGenericPrivateRequest(http::request& req, const Request& request, std::string& methodString, + std::string& headerString, std::string& path, std::string& queryString, std::string& body, const TimePoint& now, + const std::map& credential) override { + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto preSignedText = req.base().at("ACCESS-TIMESTAMP").to_string(); + preSignedText += methodString; + auto target = path; + if (!queryString.empty()) { + target += "?"; + target += queryString; + } + preSignedText += target; + preSignedText += body; + auto signature = UtilAlgorithm::base64Encode(Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText)); + if (!headerString.empty()) { + headerString += "\r\n"; + } + headerString += "ACCESS-SIGN:" + signature; + } + void signRequest(http::request& req, const std::string& body, const std::map& credential) { + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto preSignedText = req.base().at("ACCESS-TIMESTAMP").to_string(); + preSignedText += std::string(req.method_string()); + preSignedText += req.target().to_string(); + preSignedText += body; + auto signature = UtilAlgorithm::base64Encode(Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText)); + req.set("ACCESS-SIGN", signature); + req.body() = body; + req.prepare_payload(); + } + void appendSymbolId(rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::string& symbolId) { + rjValue.AddMember("symbol", rj::Value(symbolId.c_str(), allocator).Move(), allocator); + } + void appendSymbolId(std::string& queryString, const std::string& symbolId) { + queryString += "symbol="; + queryString += Url::urlEncode(symbolId); + queryString += "&"; + } + void prepareReq(http::request& req, const Request& request, const TimePoint& now, const std::string& symbolId, + const std::map& credential) { + req.set(beast::http::field::content_type, "application/json"); + req.set("locale", "en-US"); + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + req.set("ACCESS-KEY", apiKey); + req.set("ACCESS-TIMESTAMP", std::to_string(std::chrono::duration_cast(now.time_since_epoch()).count())); + auto apiPassphrase = mapGetWithDefault(credential, this->apiPassphraseName); + req.set("ACCESS-PASSPHRASE", apiPassphrase); + } + std::string apiPassphraseName; +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_BASE_H_ diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bitget_futures.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bitget_futures.h new file mode 100644 index 00000000..bbb67f23 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bitget_futures.h @@ -0,0 +1,469 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_FUTURES_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_FUTURES_H_ +#ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES +#include "ccapi_cpp/service/ccapi_execution_management_service_bitget_base.h" +namespace ccapi { +class ExecutionManagementServiceBitgetFutures : public ExecutionManagementServiceBitgetBase { + public: + ExecutionManagementServiceBitgetFutures(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + ServiceContextPtr serviceContextPtr) + : ExecutionManagementServiceBitgetBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BITGET_FUTURES; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/mix/v1/stream"; + 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->apiKeyName = CCAPI_BITGET_FUTURES_API_KEY; + this->apiSecretName = CCAPI_BITGET_FUTURES_API_SECRET; + this->apiPassphraseName = CCAPI_BITGET_FUTURES_API_PASSPHRASE; + this->setupCredential({this->apiKeyName, this->apiSecretName, this->apiPassphraseName}); + this->createOrderTarget = "/api/mix/v1/order/placeOrder"; + this->cancelOrderTarget = "/api/mix/v1/order/cancel-order"; + this->getOrderTarget = "/api/mix/v1/order/detail"; + this->getOpenOrdersTarget = "/api/mix/v1/order/current"; + this->getAllOpenOrdersTarget = "/api/mix/v1/order/marginCoinCurrent"; + this->cancelOpenOrdersTarget = "/api/mix/v1/order/cancel-all-orders"; + this->getAccountsTarget = "/api/mix/v1/account/accounts"; + this->getAccountBalancesTarget = "/api/mix/v1/account/account"; + this->getAccountPositionsTarget = "/api/mix/v1/position/singlePosition"; + this->getAccountAllPositionsTarget = "/api/mix/v1/position/allPosition"; + } + virtual ~ExecutionManagementServiceBitgetFutures() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + private: +#endif + void appendParam(Request::Operation operation, rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::map& param, + const std::map standardizationMap = { + {CCAPI_MARGIN_ASSET, "marginCoin"}, + {CCAPI_EM_ORDER_SIDE, "side"}, + {CCAPI_EM_ORDER_QUANTITY, "size"}, + {CCAPI_EM_ORDER_LIMIT_PRICE, "price"}, + {CCAPI_EM_CLIENT_ORDER_ID, "clientOid"}, + {CCAPI_SYMBOL_ID, "symbol"}, + {CCAPI_EM_ORDER_ID, "orderId"}, + {CCAPI_INSTRUMENT_TYPE, "productType"}, + }) { + for (const auto& kv : param) { + auto key = standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; + auto value = kv.second; + if (key == "side") { + value = (value == CCAPI_EM_ORDER_SIDE_BUY || value == "buy_single") ? "buy_single" : "sell_single"; + } + rjValue.AddMember(rj::Value(key.c_str(), allocator).Move(), rj::Value(value.c_str(), allocator).Move(), allocator); + } + } + void appendParam(std::string& queryString, const std::map& param, + const std::map standardizationMap = { + {CCAPI_EM_ORDER_ID, "orderId"}, + {CCAPI_EM_CLIENT_ORDER_ID, "clientOrderId"}, + {CCAPI_SYMBOL_ID, "symbol"}, + {CCAPI_INSTRUMENT_TYPE, "productType"}, + }) { + for (const auto& kv : param) { + queryString += standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; + queryString += "="; + queryString += Url::urlEncode(kv.second); + queryString += "&"; + } + } + void convertRequestForRest(http::request& req, const Request& request, const TimePoint& now, const std::string& symbolId, + const std::map& credential) override { + this->prepareReq(req, request, now, symbolId, credential); + Request::Operation operation = request.getOperation(); + switch (operation) { + case Request::Operation::GENERIC_PRIVATE_REQUEST: { + ExecutionManagementService::convertRequestForRestGenericPrivateRequest(req, request, now, symbolId, credential); + } break; + case Request::Operation::CREATE_ORDER: { + req.method(http::verb::post); + req.target(this->createOrderTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (param.find("orderType") == param.end()) { + document.AddMember("orderType", rj::Value("limit").Move(), allocator); + } + if (param.find("timeInForceValue") == param.end()) { + document.AddMember("timeInForceValue", rj::Value("normal").Move(), allocator); + } + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::CANCEL_ORDER: { + req.method(http::verb::post); + req.target(this->cancelOrderTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::GET_ORDER: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param); + if (!symbolId.empty()) { + this->appendSymbolId(queryString, symbolId); + } + if (queryString.back() == '&') { + queryString.pop_back(); + } + req.target(this->getOrderTarget + "?" + queryString); + this->signRequest(req, "", credential); + } break; + case Request::Operation::GET_OPEN_ORDERS: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param); + std::string path; + if (symbolId.empty()) { + path = this->getAllOpenOrdersTarget; + } else { + this->appendSymbolId(queryString, symbolId); + path = this->getOpenOrdersTarget; + } + if (queryString.back() == '&') { + queryString.pop_back(); + } + req.target(path + "?" + queryString); + this->signRequest(req, "", credential); + } break; + case Request::Operation::CANCEL_OPEN_ORDERS: { + req.method(http::verb::post); + req.target(this->cancelOpenOrdersTarget); + const std::map param = request.getFirstParamWithDefault(); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(operation, document, allocator, param); + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + this->signRequest(req, body, credential); + } break; + case Request::Operation::GET_ACCOUNTS: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param); + if (queryString.back() == '&') { + queryString.pop_back(); + } + req.target(this->getAccountsTarget + "?" + queryString); + this->signRequest(req, "", credential); + } break; + case Request::Operation::GET_ACCOUNT_BALANCES: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param); + if (!symbolId.empty()) { + this->appendSymbolId(queryString, symbolId); + } + if (queryString.back() == '&') { + queryString.pop_back(); + } + req.target(this->getAccountsTarget + "?" + queryString); + this->signRequest(req, "", credential); + } break; + case Request::Operation::GET_ACCOUNT_POSITIONS: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param); + std::string path; + if (symbolId.empty()) { + path = this->getAccountAllPositionsTarget; + } else { + this->appendSymbolId(queryString, symbolId); + path = this->getAccountPositionsTarget; + } + if (queryString.back() == '&') { + queryString.pop_back(); + } + req.target(path + "?" + queryString); + this->signRequest(req, "", credential); + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractOrderInfoFromRequest(std::vector& elementList, const Request& request, const Request::Operation operation, + const rj::Document& document) override { + const std::map >& extractionFieldNameMap = { + {CCAPI_EM_ORDER_ID, std::make_pair("orderId", JsonDataType::STRING)}, + {CCAPI_EM_CLIENT_ORDER_ID, std::make_pair("clientOid", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_SIDE, std::make_pair("side", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("size", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("price", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("filledQty", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_STATUS, std::make_pair("state", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_INSTRUMENT, std::make_pair("symbol", JsonDataType::STRING)}}; + const rj::Value& data = document["data"]; + if (operation == Request::Operation::CREATE_ORDER || operation == Request::Operation::CANCEL_ORDER || operation == Request::Operation::GET_ORDER) { + Element element; + this->extractOrderInfo(element, data, extractionFieldNameMap); + elementList.emplace_back(std::move(element)); + } else if (operation == Request::Operation::GET_OPEN_ORDERS) { + for (const auto& x : data["orderList"].GetArray()) { + Element element; + this->extractOrderInfo(element, x, extractionFieldNameMap); + elementList.emplace_back(std::move(element)); + } + } + } + void extractAccountInfoFromRequest(std::vector& elementList, const Request& request, const Request::Operation operation, + const rj::Document& document) override { + switch (request.getOperation()) { + case Request::Operation::GET_ACCOUNTS: { + for (const auto& x : document["data"].GetArray()) { + Element element; + element.insert(CCAPI_EM_ASSET, x["coinName"].GetString()); + std::string available = x["available"].GetString(); + element.insert(CCAPI_EM_QUANTITY_AVAILABLE_FOR_TRADING, available); + std::string frozen = x["frozen"].GetString(); + std::string lock = x["lock"].GetString(); + element.insert(CCAPI_EM_QUANTITY_TOTAL, (Decimal(available).add(Decimal(frozen)).add(Decimal(lock))).toString()); + elementList.emplace_back(std::move(element)); + } + } break; + case Request::Operation::GET_ACCOUNT_BALANCES: { + for (const auto& x : document["data"].GetArray()) { + Element element; + element.insert(CCAPI_EM_ASSET, x["marginCoin"].GetString()); + element.insert(CCAPI_EM_QUANTITY_AVAILABLE_FOR_TRADING, x["available"].GetString()); + element.insert(CCAPI_EM_QUANTITY_TOTAL, x["equity"].GetString()); + elementList.emplace_back(std::move(element)); + } + } break; + case Request::Operation::GET_ACCOUNT_POSITIONS: { + for (const auto& x : document["data"].GetArray()) { + Element element; + element.insert(CCAPI_INSTRUMENT, x["symbol"].GetString()); + element.insert(CCAPI_MARGIN_ASSET, x["marginCoin"].GetString()); + element.insert(CCAPI_EM_POSITION_SIDE, x["holdSide"].GetString()); + element.insert(CCAPI_EM_POSITION_QUANTITY, x["total"].GetString()); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["averageOpenPrice"].GetString()); + element.insert(CCAPI_EM_POSITION_LEVERAGE, x["leverage"].GetString()); + elementList.emplace_back(std::move(element)); + } + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } + void extractOrderInfo(Element& element, const rj::Value& x, + const std::map >& extractionFieldNameMap) override { + ExecutionManagementService::extractOrderInfo(element, x, extractionFieldNameMap); + { + auto it1 = x.FindMember("accFillSz"); + auto it2 = x.FindMember("avgPx"); + if (it1 != x.MemberEnd() && it2 != x.MemberEnd()) { + auto it1Str = std::string(it1->value.GetString()); + auto it2Str = std::string(it2->value.GetString()); + if (!it1Str.empty() && !it2Str.empty()) { + element.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, UtilString::printDoubleScientific(std::stod(it1Str) * std::stod(it2Str))); + } + } + } + } + std::vector createSendStringListFromSubscription(const WsConnection& wsConnection, const Subscription& subscription, const TimePoint& now, + const std::map& credential) override { + std::vector sendStringList; + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("login").Move(), allocator); + rj::Value arg(rj::kObjectType); + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto apiPassphrase = mapGetWithDefault(credential, this->apiPassphraseName); + std::string ts = std::to_string(std::chrono::duration_cast(now.time_since_epoch()).count()); + arg.AddMember("apiKey", rj::Value(apiKey.c_str(), allocator).Move(), allocator); + arg.AddMember("passphrase", rj::Value(apiPassphrase.c_str(), allocator).Move(), allocator); + arg.AddMember("timestamp", rj::Value(ts.c_str(), allocator).Move(), allocator); + std::string signData = ts + "GET" + "/user/verify"; + std::string sign = UtilAlgorithm::base64Encode(Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, signData)); + arg.AddMember("sign", rj::Value(sign.c_str(), allocator).Move(), allocator); + rj::Value args(rj::kArrayType); + args.PushBack(arg, allocator); + document.AddMember("args", args, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + return sendStringList; + } + void onTextMessage(const WsConnection& wsConnection, const Subscription& subscription, const std::string& textMessage, + const TimePoint& timeReceived) override { + if (textMessage != "pong") { + rj::Document document; + document.Parse(textMessage.c_str()); + auto it = document.FindMember("event"); + std::string eventStr = it != document.MemberEnd() ? it->value.GetString() : ""; + if (eventStr == "login") { + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("subscribe").Move(), allocator); + rj::Value args(rj::kArrayType); + const auto& fieldSet = subscription.getFieldSet(); + const auto& instrumentSet = subscription.getInstrumentSet(); + for (const auto& field : fieldSet) { + std::string channel; + if (fieldSet.find(CCAPI_EM_ORDER_UPDATE) != fieldSet.end() || fieldSet.find(CCAPI_EM_PRIVATE_TRADE) != fieldSet.end()) { + channel = "orders"; + } + for (const auto& instrument : instrumentSet) { + rj::Value arg(rj::kObjectType); + arg.AddMember("channel", rj::Value(channel.c_str(), allocator).Move(), allocator); + arg.AddMember("instId", rj::Value(instrument.c_str(), allocator).Move(), allocator); + arg.AddMember("instType", rj::Value("spbl").Move(), allocator); + args.PushBack(arg, allocator); + } + } + document.AddMember("args", args, allocator); + rj::StringBuffer stringBufferSubscribe; + rj::Writer writerSubscribe(stringBufferSubscribe); + document.Accept(writerSubscribe); + std::string sendString = stringBufferSubscribe.GetString(); + ErrorCode ec; + this->send(wsConnection.hdl, sendString, wspp::frame::opcode::text, ec); + if (ec) { + this->onError(Event::Type::SUBSCRIPTION_STATUS, Message::Type::SUBSCRIPTION_FAILURE, ec, "subscribe"); + } + } else { + Event event = this->createEvent(subscription, textMessage, document, eventStr, timeReceived); + if (!event.getMessageList().empty()) { + this->eventHandler(event, nullptr); + } + } + } + } + Event createEvent(const Subscription& subscription, const std::string& textMessage, const rj::Document& document, const std::string& eventStr, + const TimePoint& timeReceived) { + Event event; + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + const auto& correlationId = subscription.getCorrelationId(); + message.setCorrelationIdList({correlationId}); + const auto& fieldSet = subscription.getFieldSet(); + const auto& instrumentSet = subscription.getInstrumentSet(); + if (eventStr.empty()) { + const rj::Value& arg = document["arg"]; + std::string channel = std::string(arg["channel"].GetString()); + event.setType(Event::Type::SUBSCRIPTION_DATA); + std::string instrument = arg["instId"].GetString(); + if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { + if (channel == "orders") { + if (fieldSet.find(CCAPI_EM_PRIVATE_TRADE) != fieldSet.end()) { + const rj::Value& data = document["data"]; + for (const auto& x : data.GetArray()) { + auto itTradeId = x.FindMember("tradeId"); + std::string tradeId = itTradeId != x.MemberEnd() ? itTradeId->value.GetString() : ""; + if (!tradeId.empty()) { + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(std::string(x["fillTime"].GetString())))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_PRIVATE_TRADE); + std::vector elementList; + Element element; + element.insert(CCAPI_TRADE_ID, tradeId); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_PRICE, std::string(x["fillPx"].GetString())); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_SIZE, std::string(x["fillSz"].GetString())); + element.insert(CCAPI_EM_ORDER_SIDE, std::string(x["side"].GetString()) == "buy" ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); + element.insert(CCAPI_IS_MAKER, std::string(x["execType"].GetString()) == "M" ? "1" : "0"); + element.insert(CCAPI_EM_ORDER_ID, std::string(x["ordId"].GetString())); + element.insert(CCAPI_EM_CLIENT_ORDER_ID, std::string(x["clOrdId"].GetString())); + element.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + element.insert(CCAPI_EM_ORDER_FEE_QUANTITY, std::string(x["fillFee"].GetString())); + element.insert(CCAPI_EM_ORDER_FEE_ASSET, std::string(x["fillFeeCcy"].GetString())); + elementList.emplace_back(std::move(element)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } + if (fieldSet.find(CCAPI_EM_ORDER_UPDATE) != fieldSet.end()) { + const rj::Value& data = document["data"]; + for (const auto& x : data.GetArray()) { + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setTime(UtilTime::makeTimePointFromMilliseconds(std::stoll(std::string(x["uTime"].GetString())))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE); + const std::map >& extractionFieldNameMap = { + {CCAPI_EM_ORDER_ID, std::make_pair("ordId", JsonDataType::STRING)}, + {CCAPI_EM_CLIENT_ORDER_ID, std::make_pair("clOrdId", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_SIDE, std::make_pair("side", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("px", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("sz", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("accFillSz", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_STATUS, std::make_pair("status", JsonDataType::STRING)}, + }; + Element info; + this->extractOrderInfo(info, x, extractionFieldNameMap); + info.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + std::vector elementList; + elementList.emplace_back(std::move(info)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } + } + } else if (eventStr == "subscribe") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + message.setType(Message::Type::SUBSCRIPTION_STARTED); + Element element; + element.insert(CCAPI_INFO_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + } else if (eventStr == "error") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + message.setType(Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + } + event.setMessageList(messageList); + return event; + } + std::string getAllOpenOrdersTarget, getAccountAllPositionsTarget; +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BITGET_FUTURES_H_ diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bitmex.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bitmex.h index 386c1ef6..a022b1f9 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_bitmex.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bitmex.h @@ -254,6 +254,7 @@ class ExecutionManagementServiceBitmex : public ExecutionManagementService { element.insert(CCAPI_EM_ASSET, x["currency"].GetString()); element.insert(CCAPI_EM_POSITION_QUANTITY, x["currentQty"].GetString()); element.insert(CCAPI_EM_POSITION_COST, x["openingCost"].GetString()); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["avgEntryPrice"].GetString()); element.insert(CCAPI_EM_POSITION_LEVERAGE, x["leverage"].GetString()); elementList.emplace_back(std::move(element)); } diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bybit.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bybit.h index 4b50a74b..f12d98bc 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_bybit.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bybit.h @@ -2,13 +2,13 @@ #define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_H_ #ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT #ifdef CCAPI_ENABLE_EXCHANGE_BYBIT -#include "ccapi_cpp/service/ccapi_execution_management_service.h" +#include "ccapi_cpp/service/ccapi_execution_management_service_bybit_base.h" namespace ccapi { -class ExecutionManagementServiceBybit : public ExecutionManagementService { +class ExecutionManagementServiceBybit : public ExecutionManagementServiceBybitBase { public: ExecutionManagementServiceBybit(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, ServiceContextPtr serviceContextPtr) - : ExecutionManagementService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + : ExecutionManagementServiceBybitBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { this->exchangeName = CCAPI_EXCHANGE_NAME_BYBIT; this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/spot/private/v3"; this->baseUrlRest = sessionConfigs.getUrlRestBase().at(this->exchangeName); @@ -32,46 +32,6 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { protected: #endif - bool doesHttpBodyContainError(const Request& request, const std::string& body) override { return body.find(R"("retCode":0)") == std::string::npos; } - void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, R"({"op":"ping"})", wspp::frame::opcode::text, ec); } - void signReqeustForRestGenericPrivateRequest(http::request& req, const Request& request, std::string& methodString, - std::string& headerString, std::string& path, std::string& queryString, std::string& body, const TimePoint& now, - const std::map& credential) override { - auto apiKey = mapGetWithDefault(credential, this->apiKeyName); - auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); - auto preSignedText = req.base().at("X-BAPI-TIMESTAMP").to_string(); - preSignedText += apiKey; - preSignedText += req.base().at("X-BAPI-RECV-WINDOW").to_string(); - std::string aString; - if (methodString == "GET") { - aString = queryString; - } else if (methodString == "POST") { - aString = body; - } - preSignedText += aString; - auto signature = Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText, true); - req.set("X-BAPI-SIGN", signature); - } - void signRequest(http::request& req, const std::string aString, const TimePoint& now, - const std::map& credential) { - auto apiKey = mapGetWithDefault(credential, this->apiKeyName); - auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); - auto preSignedText = req.base().at("X-BAPI-TIMESTAMP").to_string(); - preSignedText += apiKey; - preSignedText += req.base().at("X-BAPI-RECV-WINDOW").to_string(); - preSignedText += aString; - auto signature = Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText, true); - req.set("X-BAPI-SIGN", signature); - } - void appendParam(std::string& queryString, const std::map& param, - const std::map standardizationMap = {}) { - for (const auto& kv : param) { - queryString += standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; - queryString += "="; - queryString += Url::urlEncode(kv.second); - queryString += "&"; - } - } void appendParam(rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::map& param, const std::map standardizationMap = { {CCAPI_EM_ORDER_SIDE, "side"}, @@ -96,21 +56,6 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { } } } - void appendSymbolId(std::string& queryString, const std::string& symbolId) { - queryString += "symbol="; - queryString += Url::urlEncode(symbolId); - queryString += "&"; - } - void appendSymbolId(rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::string& symbolId) { - rjValue.AddMember("symbol", rj::Value(symbolId.c_str(), allocator).Move(), allocator); - } - void prepareReq(http::request& req, const TimePoint& now, const std::map& credential) { - auto apiKey = mapGetWithDefault(credential, this->apiKeyName); - req.set("X-BAPI-SIGN-TYPE", "2"); - req.set("X-BAPI-API-KEY", apiKey); - req.set("X-BAPI-TIMESTAMP", std::to_string(std::chrono::duration_cast(now.time_since_epoch()).count())); - req.set("X-BAPI-RECV-WINDOW", std::to_string(CCAPI_BYBIT_API_RECEIVE_WINDOW_MILLISECONDS)); - } void convertRequestForRest(http::request& req, const Request& request, const TimePoint& now, const std::string& symbolId, const std::map& credential) override { this->prepareReq(req, now, credential); @@ -162,7 +107,7 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { req.method(http::verb::get); std::string queryString; const std::map param = request.getFirstParamWithDefault(); - this->appendParam(queryString, param); + this->appendParamToQueryString(queryString, param); req.target(this->getOrderTarget + "?" + queryString); this->signRequest(req, queryString, now, credential); } break; @@ -170,7 +115,7 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { req.method(http::verb::get); std::string queryString; const std::map param = request.getFirstParamWithDefault(); - this->appendParam(queryString, param); + this->appendParamToQueryString(queryString, param); if (!symbolId.empty()) { this->appendSymbolId(queryString, symbolId); } @@ -180,7 +125,7 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { case Request::Operation::GET_ACCOUNT_BALANCES: { req.method(http::verb::get); std::string queryString; - this->appendParam(queryString, {}); + this->appendParamToQueryString(queryString, {}); req.target(this->getAccountBalancesTarget); this->signRequest(req, queryString, now, credential); } break; @@ -228,45 +173,8 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); } } - std::vector createSendStringListFromSubscription(const WsConnection& wsConnection, const Subscription& subscription, const TimePoint& now, - const std::map& credential) override { - std::vector sendStringList; - rj::Document document; - document.SetObject(); - auto& allocator = document.GetAllocator(); - document.AddMember("op", rj::Value("auth").Move(), allocator); - auto apiKey = mapGetWithDefault(credential, this->apiKeyName); - auto expires = - std::chrono::duration_cast((now + std::chrono::milliseconds(CCAPI_BYBIT_API_RECEIVE_WINDOW_MILLISECONDS)).time_since_epoch()) - .count(); - auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); - std::string preSignedText = "GET"; - preSignedText += "/realtime"; - preSignedText += std::to_string(expires); - auto signature = Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText, true); - rj::Value args(rj::kArrayType); - args.PushBack(rj::Value(apiKey.c_str(), allocator).Move(), allocator); - args.PushBack(rj::Value(expires).Move(), allocator); - args.PushBack(rj::Value(signature.c_str(), allocator).Move(), allocator); - document.AddMember("args", args, allocator); - rj::StringBuffer stringBuffer; - rj::Writer writer(stringBuffer); - document.Accept(writer); - std::string sendString = stringBuffer.GetString(); - sendStringList.push_back(sendString); - return sendStringList; - } - void onTextMessage(const WsConnection& wsConnection, const Subscription& subscription, const std::string& textMessage, - const TimePoint& timeReceived) override { - rj::Document document; - document.Parse(textMessage.c_str()); - Event event = this->createEvent(wsConnection.hdl, subscription, textMessage, document, timeReceived); - if (!event.getMessageList().empty()) { - this->eventHandler(event, nullptr); - } - } Event createEvent(wspp::connection_hdl hdl, const Subscription& subscription, const std::string& textMessage, const rj::Document& document, - const TimePoint& timeReceived) { + const TimePoint& timeReceived) override { Event event; std::vector messageList; Message message; @@ -284,7 +192,7 @@ class ExecutionManagementServiceBybit : public ExecutionManagementService { message.setTimeReceived(timeReceived); message.setCorrelationIdList({subscription.getCorrelationId()}); std::string instrument = x["s"].GetString(); - if (instrumentSet.empty() || instrumentSet.find(UtilString::toUpper(instrument)) != instrumentSet.end()) { + if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { message.setTime(TimePoint(std::chrono::milliseconds(std::stoll(x["E"].GetString())))); auto itTradeId = x.FindMember("t"); if (itTradeId != x.MemberEnd() && !itTradeId->value.IsNull() && fieldSet.find(CCAPI_EM_PRIVATE_TRADE) != fieldSet.end()) { diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bybit_base.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bybit_base.h new file mode 100644 index 00000000..f10f5e52 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bybit_base.h @@ -0,0 +1,117 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_BASE_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_BASE_H_ +#ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT +#if defined(CCAPI_ENABLE_EXCHANGE_BYBIT) || defined(CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES) +#include "ccapi_cpp/service/ccapi_execution_management_service.h" +namespace ccapi { +class ExecutionManagementServiceBybitBase : public ExecutionManagementService { + public: + ExecutionManagementServiceBybitBase(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + ServiceContextPtr serviceContextPtr) + : ExecutionManagementService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) {} + virtual ~ExecutionManagementServiceBybitBase() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + protected: +#endif + bool doesHttpBodyContainError(const Request& request, const std::string& body) override { return body.find(R"("retCode":0)") == std::string::npos; } + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, R"({"op":"ping"})", wspp::frame::opcode::text, ec); } + void signReqeustForRestGenericPrivateRequest(http::request& req, const Request& request, std::string& methodString, + std::string& headerString, std::string& path, std::string& queryString, std::string& body, const TimePoint& now, + const std::map& credential) override { + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto preSignedText = req.base().at("X-BAPI-TIMESTAMP").to_string(); + preSignedText += apiKey; + preSignedText += req.base().at("X-BAPI-RECV-WINDOW").to_string(); + std::string aString; + if (methodString == "GET") { + aString = queryString; + } else if (methodString == "POST") { + aString = body; + } + preSignedText += aString; + auto signature = Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText, true); + req.set("X-BAPI-SIGN", signature); + } + void signRequest(http::request& req, const std::string aString, const TimePoint& now, + const std::map& credential) { + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto preSignedText = req.base().at("X-BAPI-TIMESTAMP").to_string(); + preSignedText += apiKey; + preSignedText += req.base().at("X-BAPI-RECV-WINDOW").to_string(); + preSignedText += aString; + auto signature = Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText, true); + req.set("X-BAPI-SIGN", signature); + } + void appendParamToQueryString(std::string& queryString, const std::map& param, + const std::map standardizationMap = {}) { + for (const auto& kv : param) { + queryString += standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; + queryString += "="; + queryString += Url::urlEncode(kv.second); + queryString += "&"; + } + } + void appendSymbolId(std::string& queryString, const std::string& symbolId) { + queryString += "symbol="; + queryString += Url::urlEncode(symbolId); + queryString += "&"; + } + void appendSymbolId(rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::string& symbolId) { + rjValue.AddMember("symbol", rj::Value(symbolId.c_str(), allocator).Move(), allocator); + } + void prepareReq(http::request& req, const TimePoint& now, const std::map& credential) { + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + req.set("X-BAPI-SIGN-TYPE", "2"); + req.set("X-BAPI-API-KEY", apiKey); + req.set("X-BAPI-TIMESTAMP", std::to_string(std::chrono::duration_cast(now.time_since_epoch()).count())); + req.set("X-BAPI-RECV-WINDOW", std::to_string(CCAPI_BYBIT_BASE_API_RECEIVE_WINDOW_MILLISECONDS)); + } + std::vector createSendStringListFromSubscription(const WsConnection& wsConnection, const Subscription& subscription, const TimePoint& now, + const std::map& credential) override { + std::vector sendStringList; + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("auth").Move(), allocator); + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + auto expires = std::chrono::duration_cast( + (now + std::chrono::milliseconds(CCAPI_BYBIT_BASE_API_RECEIVE_WINDOW_MILLISECONDS)).time_since_epoch()) + .count(); + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + std::string preSignedText = "GET"; + preSignedText += "/realtime"; + preSignedText += std::to_string(expires); + auto signature = Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText, true); + rj::Value args(rj::kArrayType); + args.PushBack(rj::Value(apiKey.c_str(), allocator).Move(), allocator); + args.PushBack(rj::Value(expires).Move(), allocator); + args.PushBack(rj::Value(signature.c_str(), allocator).Move(), allocator); + document.AddMember("args", args, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + return sendStringList; + } + virtual Event createEvent(wspp::connection_hdl hdl, const Subscription& subscription, const std::string& textMessage, const rj::Document& document, + const TimePoint& timeReceived) { + return {}; + } + void onTextMessage(const WsConnection& wsConnection, const Subscription& subscription, const std::string& textMessage, + const TimePoint& timeReceived) override { + rj::Document document; + document.Parse(textMessage.c_str()); + Event event = this->createEvent(wsConnection.hdl, subscription, textMessage, document, timeReceived); + if (!event.getMessageList().empty()) { + this->eventHandler(event, nullptr); + } + } +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_BASE_H_ diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_bybit_derivatives.h b/include/ccapi_cpp/service/ccapi_execution_management_service_bybit_derivatives.h new file mode 100644 index 00000000..8100f5bb --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_bybit_derivatives.h @@ -0,0 +1,370 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_DERIVATIVES_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_DERIVATIVES_H_ +#ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT +#ifdef CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES +#include "ccapi_cpp/service/ccapi_execution_management_service_bybit_base.h" +namespace ccapi { +class ExecutionManagementServiceBybitDerivatives : public ExecutionManagementServiceBybitBase { + public: + ExecutionManagementServiceBybitDerivatives(std::function*)> eventHandler, SessionOptions sessionOptions, + SessionConfigs sessionConfigs, ServiceContextPtr serviceContextPtr) + : ExecutionManagementServiceBybitBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/unified/private/v3"; + 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->apiKeyName = CCAPI_BYBIT_DERIVATIVES_API_KEY; + this->apiSecretName = CCAPI_BYBIT_DERIVATIVES_API_SECRET; + this->setupCredential({this->apiKeyName, this->apiSecretName}); + this->createOrderTarget = "/unified/v3/private/order/create"; + this->cancelOrderTarget = "/unified/v3/private/order/cancel"; + this->getOrderTarget = "/unified/v3/private/order/list"; + this->getOpenOrdersTarget = "/unified/v3/private/order/unfilled-orders"; + this->cancelOpenOrdersTarget = "/unified/v3/private/order/cancel-all"; + this->getAccountBalancesTarget = "/unified/v3/private/account/wallet/balance"; + this->getAccountPositionsTarget = "/unified/v3/private/position/list"; + } + virtual ~ExecutionManagementServiceBybitDerivatives() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + protected: +#endif + void appendParam(rj::Value& rjValue, rj::Document::AllocatorType& allocator, const std::map& param, + const std::map standardizationMap = { + {CCAPI_EM_ORDER_SIDE, "side"}, + {CCAPI_EM_ORDER_QUANTITY, "qty"}, + {CCAPI_EM_ORDER_LIMIT_PRICE, "price"}, + {CCAPI_EM_CLIENT_ORDER_ID, "orderLinkId"}, + {CCAPI_EM_ORDER_ID, "orderId"}, + {CCAPI_LIMIT, "limit"}, + {CCAPI_INSTRUMENT_TYPE, "category"}, + }) { + for (const auto& kv : param) { + auto key = standardizationMap.find(kv.first) != standardizationMap.end() ? standardizationMap.at(kv.first) : kv.first; + auto value = kv.second; + if (key == "side") { + value = value == CCAPI_EM_ORDER_SIDE_BUY ? "Buy" : "Sell"; + } + if (value != "null") { + if (key == "reduceOnly" || key == "closeOnTrigger" || key == "mmp") { + rjValue.AddMember(rj::Value(key.c_str(), allocator).Move(), value == "true", allocator); + } else { + rjValue.AddMember(rj::Value(key.c_str(), allocator).Move(), rj::Value(value.c_str(), allocator).Move(), allocator); + } + } + } + } + void convertRequestForRest(http::request& req, const Request& request, const TimePoint& now, const std::string& symbolId, + const std::map& credential) override { + this->prepareReq(req, now, credential); + switch (request.getOperation()) { + case Request::Operation::GENERIC_PRIVATE_REQUEST: { + ExecutionManagementService::convertRequestForRestGenericPrivateRequest(req, request, now, symbolId, credential); + } break; + case Request::Operation::CREATE_ORDER: { + req.method(http::verb::post); + req.set(beast::http::field::content_type, "application/json"); + const std::map param = request.getFirstParamWithDefault(); + req.target(this->createOrderTarget); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(document, allocator, param); + if (param.find("orderType") == param.end()) { + document.AddMember("orderType", rj::Value("Limit").Move(), allocator); + } + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + req.body() = body; + req.prepare_payload(); + this->signRequest(req, body, now, credential); + } break; + case Request::Operation::CANCEL_ORDER: { + req.method(http::verb::post); + req.set(beast::http::field::content_type, "application/json"); + const std::map param = request.getFirstParamWithDefault(); + req.target(this->cancelOrderTarget); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(document, allocator, param); + this->appendSymbolId(document, allocator, symbolId); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + req.body() = body; + req.prepare_payload(); + this->signRequest(req, body, now, credential); + } break; + case Request::Operation::GET_ORDER: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParamToQueryString(queryString, param, + { + {CCAPI_EM_CLIENT_ORDER_ID, "orderLinkId"}, + {CCAPI_EM_ORDER_ID, "orderId"}, + {CCAPI_LIMIT, "limit"}, + {CCAPI_INSTRUMENT_TYPE, "category"}, + }); + req.target(this->getOrderTarget + "?" + queryString); + this->signRequest(req, queryString, now, credential); + } break; + case Request::Operation::GET_OPEN_ORDERS: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParamToQueryString(queryString, param, + { + {CCAPI_EM_CLIENT_ORDER_ID, "orderLinkId"}, + {CCAPI_EM_ORDER_ID, "orderId"}, + {CCAPI_LIMIT, "limit"}, + {CCAPI_INSTRUMENT_TYPE, "category"}, + }); + if (!symbolId.empty()) { + this->appendSymbolId(queryString, symbolId); + } + req.target(this->getOpenOrdersTarget + "?" + queryString); + this->signRequest(req, queryString, now, credential); + } break; + case Request::Operation::CANCEL_OPEN_ORDERS: { + req.method(http::verb::post); + req.set(beast::http::field::content_type, "application/json"); + const std::map param = request.getFirstParamWithDefault(); + req.target(this->cancelOpenOrdersTarget); + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + this->appendParam(document, allocator, param); + if (!symbolId.empty()) { + this->appendSymbolId(document, allocator, symbolId); + } + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + auto body = stringBuffer.GetString(); + req.body() = body; + req.prepare_payload(); + this->signRequest(req, body, now, credential); + } break; + case Request::Operation::GET_ACCOUNT_BALANCES: { + req.method(http::verb::get); + std::string queryString; + this->appendParamToQueryString(queryString, {}); + req.target(queryString.empty() ? this->getAccountBalancesTarget : this->getAccountBalancesTarget + "?" + queryString); + this->signRequest(req, queryString, now, credential); + } break; + case Request::Operation::GET_ACCOUNT_POSITIONS: { + req.method(http::verb::get); + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParamToQueryString(queryString, param); + req.target(this->getAccountPositionsTarget + "?" + queryString); + this->signRequest(req, queryString, now, credential); + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractOrderInfoFromRequest(std::vector& elementList, const Request& request, const Request::Operation operation, + const rj::Document& document) override { + std::map > extractionFieldNameMap = { + {CCAPI_EM_ORDER_ID, std::make_pair("orderId", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_SIDE, std::make_pair("side", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("qty", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("price", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("cumExecQty", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, std::make_pair("cumExecValue", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_REMAINING_QUANTITY, std::make_pair("leavesQty", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_STATUS, std::make_pair("orderStatus", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_INSTRUMENT, std::make_pair("symbol", JsonDataType::STRING)}, + }; + if (operation == Request::Operation::GET_ORDER || operation == Request::Operation::GET_OPEN_ORDERS || operation == Request::Operation::CANCEL_OPEN_ORDERS) { + for (const auto& x : document["result"]["list"].GetArray()) { + Element element; + this->extractOrderInfo(element, x, extractionFieldNameMap); + elementList.emplace_back(std::move(element)); + } + } else { + Element element; + this->extractOrderInfo(element, document["result"], extractionFieldNameMap); + elementList.emplace_back(std::move(element)); + } + } + void extractAccountInfoFromRequest(std::vector& elementList, const Request& request, const Request::Operation operation, + const rj::Document& document) override { + switch (request.getOperation()) { + case Request::Operation::GET_ACCOUNT_BALANCES: { + for (const auto& x : document["result"]["coin"].GetArray()) { + Element element; + element.insert(CCAPI_EM_ASSET, x["currencyCoin"].GetString()); + element.insert(CCAPI_EM_QUANTITY_TOTAL, x["walletBalance"].GetString()); + element.insert(CCAPI_EM_QUANTITY_AVAILABLE_FOR_TRADING, x["availableBalance"].GetString()); + elementList.emplace_back(std::move(element)); + } + } break; + case Request::Operation::GET_ACCOUNT_POSITIONS: { + for (const auto& x : document["result"]["list"].GetArray()) { + Element element; + element.insert(CCAPI_INSTRUMENT, x["symbol"].GetString()); + element.insert(CCAPI_EM_POSITION_SIDE, x["side"].GetString()); + element.insert(CCAPI_EM_POSITION_QUANTITY, x["size"].GetString()); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["entryPrice"].GetString()); + element.insert(CCAPI_EM_POSITION_LEVERAGE, x["leverage"].GetString()); + elementList.emplace_back(std::move(element)); + } + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } + Event createEvent(wspp::connection_hdl hdl, const Subscription& subscription, const std::string& textMessage, const rj::Document& document, + const TimePoint& timeReceived) override { + Event event; + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + const auto& fieldSet = subscription.getFieldSet(); + const auto& instrumentSet = subscription.getInstrumentSet(); + if (document.HasMember("topic")) { + std::string topic = document["topic"].GetString(); + if (topic == "user.order.unifiedAccount") { + if (fieldSet.find(CCAPI_EM_ORDER_UPDATE) != fieldSet.end()) { + event.setType(Event::Type::SUBSCRIPTION_DATA); + const rj::Value& data = document["data"]["result"]; + for (const auto& x : data.GetArray()) { + std::string instrument = x["symbol"].GetString(); + if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setTime(TimePoint(std::chrono::milliseconds(std::stoll(x["updatedTime"].GetString())))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE); + const std::map >& extractionFieldNameMap = { + {CCAPI_EM_ORDER_ID, std::make_pair("orderId", JsonDataType::INTEGER)}, + {CCAPI_EM_CLIENT_ORDER_ID, std::make_pair("orderLinkId", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_SIDE, std::make_pair("side", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_LIMIT_PRICE, std::make_pair("price", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_QUANTITY, std::make_pair("qty", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY, std::make_pair("cumExecQty", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, std::make_pair("cumExecValue", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_STATUS, std::make_pair("orderStatus", JsonDataType::STRING)}, + {CCAPI_EM_ORDER_INSTRUMENT, std::make_pair("symbol", JsonDataType::STRING)}, + }; + Element info; + this->extractOrderInfo(info, x, extractionFieldNameMap); + std::vector elementList; + elementList.emplace_back(std::move(info)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } + } else if (topic == "user.execution.unifiedAccount") { + if (fieldSet.find(CCAPI_EM_PRIVATE_TRADE) != fieldSet.end()) { + event.setType(Event::Type::SUBSCRIPTION_DATA); + const rj::Value& data = document["data"]["result"]; + for (const auto& x : data.GetArray()) { + std::string execType = x["execType"].GetString(); + if (execType == "TRADE") { + std::string instrument = x["symbol"].GetString(); + if (instrumentSet.empty() || instrumentSet.find(instrument) != instrumentSet.end()) { + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList({subscription.getCorrelationId()}); + message.setTime(TimePoint(std::chrono::milliseconds(std::stoll(x["execTime"].GetString())))); + message.setType(Message::Type::EXECUTION_MANAGEMENT_EVENTS_PRIVATE_TRADE); + std::vector elementList; + Element element; + element.insert(CCAPI_TRADE_ID, x["execId"].GetString()); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_PRICE, x["execPrice"].GetString()); + element.insert(CCAPI_EM_ORDER_LAST_EXECUTED_SIZE, x["execQty"].GetString()); + element.insert(CCAPI_EM_ORDER_SIDE, std::string(x["side"].GetString()) == "Buy" ? CCAPI_EM_ORDER_SIDE_BUY : CCAPI_EM_ORDER_SIDE_SELL); + element.insert(CCAPI_IS_MAKER, std::string(x["lastLiquidityInd"].GetString()) == "MAKER" ? "1" : "0"); + element.insert(CCAPI_EM_ORDER_ID, x["orderId"].GetString()); + element.insert(CCAPI_EM_CLIENT_ORDER_ID, x["orderLinkId"].GetString()); + element.insert(CCAPI_EM_ORDER_INSTRUMENT, instrument); + { + auto it = x.FindMember("execFee"); + if (it != x.MemberEnd() && !it->value.IsNull()) { + element.insert(CCAPI_EM_ORDER_FEE_QUANTITY, std::string(it->value.GetString())); + } + } + elementList.emplace_back(std::move(element)); + message.setElementList(elementList); + messageList.emplace_back(std::move(message)); + } + } + } + } + } + } else if (document.HasMember("type")) { + std::string type = document["type"].GetString(); + if (type == "AUTH_RESP") { + bool success = document["success"].GetBool(); + if (success) { + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("subscribe").Move(), allocator); + rj::Value args(rj::kArrayType); + for (const auto& field : subscription.getFieldSet()) { + std::string channelId; + if (field == CCAPI_EM_ORDER_UPDATE) { + channelId = "user.order.unifiedAccount"; + } else if (field == CCAPI_EM_PRIVATE_TRADE) { + channelId = "user.execution.unifiedAccount"; + } + args.PushBack(rj::Value(channelId.c_str(), allocator).Move(), allocator); + } + document.AddMember("args", args, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + ErrorCode ec; + this->send(hdl, sendString, wspp::frame::opcode::text, ec); + if (ec) { + this->onError(Event::Type::SUBSCRIPTION_STATUS, Message::Type::SUBSCRIPTION_FAILURE, ec, "subscribe"); + } + } + } else if (type == "COMMAND_RESP") { + bool success = document["success"].GetBool(); + event.setType(Event::Type::SUBSCRIPTION_STATUS); + 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 if (document.HasMember("ret_code")) { + std::string ret_code = document["ret_code"].GetString(); + if (ret_code != "0") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + message.setType(Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + } + } + event.setMessageList(messageList); + return event; + } +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_EXECUTION_MANAGEMENT_SERVICE_BYBIT_DERIVATIVES_H_ diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_deribit.h b/include/ccapi_cpp/service/ccapi_execution_management_service_deribit.h index 6a505822..88540ec3 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_deribit.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_deribit.h @@ -263,8 +263,7 @@ class ExecutionManagementServiceDeribit : public ExecutionManagementService { Element element; element.insert(CCAPI_INSTRUMENT, x["instrument_name"].GetString()); element.insert(CCAPI_EM_POSITION_QUANTITY, x["size"].GetString()); - element.insert(CCAPI_EM_POSITION_COST, - std::to_string(std::stod(x["average_price"].GetString()) * std::stod(x["size"].GetString()) / std::stod(x["leverage"].GetString()))); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["average_price"].GetString()); element.insert(CCAPI_EM_POSITION_LEVERAGE, x["leverage"].GetString()); elementList.emplace_back(std::move(element)); } 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 37edb293..aaaa05db 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 @@ -240,8 +240,7 @@ class ExecutionManagementServiceKrakenFutures : public ExecutionManagementServic element.insert(CCAPI_INSTRUMENT, x["symbol"].GetString()); element.insert(CCAPI_EM_POSITION_SIDE, x["side"].GetString()); element.insert(CCAPI_EM_POSITION_QUANTITY, x["size"].GetString()); - element.insert(CCAPI_EM_POSITION_COST, - Decimal(UtilString::printDoubleScientific(std::stod(x["price"].GetString()) * std::stod(x["size"].GetString()))).toString()); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["price"].GetString()); elementList.emplace_back(std::move(element)); } } break; diff --git a/include/ccapi_cpp/service/ccapi_execution_management_service_okx.h b/include/ccapi_cpp/service/ccapi_execution_management_service_okx.h index 3c518841..6bd90de8 100644 --- a/include/ccapi_cpp/service/ccapi_execution_management_service_okx.h +++ b/include/ccapi_cpp/service/ccapi_execution_management_service_okx.h @@ -304,7 +304,7 @@ class ExecutionManagementServiceOkx : public ExecutionManagementService { std::string availPos = x["availPos"].GetString(); std::string positionQuantity = availPos.empty() ? x["pos"].GetString() : availPos; element.insert(CCAPI_EM_POSITION_QUANTITY, positionQuantity); - element.insert(CCAPI_EM_POSITION_COST, std::to_string(std::stod(x["avgPx"].GetString()) * std::stod(positionQuantity))); + element.insert(CCAPI_EM_POSITION_ENTRY_PRICE, x["avgPx"].GetString()); element.insert(CCAPI_EM_POSITION_LEVERAGE, x["lever"].GetString()); elementList.emplace_back(std::move(element)); } @@ -323,7 +323,7 @@ class ExecutionManagementServiceOkx : public ExecutionManagementService { auto it1Str = std::string(it1->value.GetString()); auto it2Str = std::string(it2->value.GetString()); if (!it1Str.empty() && !it2Str.empty()) { - element.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, std::to_string(std::stod(it1Str) * std::stod(it2Str))); + element.insert(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY, UtilString::printDoubleScientific(std::stod(it1Str) * std::stod(it2Str))); } } } diff --git a/include/ccapi_cpp/service/ccapi_market_data_service.h b/include/ccapi_cpp/service/ccapi_market_data_service.h index 0d32d538..cf44f993 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service.h @@ -112,7 +112,7 @@ class MarketDataService : public Service { std::string channelId = this->sessionConfigs.getExchangeFieldWebsocketChannelMap().at(this->exchangeName).at(field); CCAPI_LOGGER_TRACE("channelId = " + channelId); CCAPI_LOGGER_TRACE("this->exchangeName = " + this->exchangeName); - this->prepareSubscriptionDetail(channelId, symbolId, field, wsConnection, optionMap); + this->prepareSubscriptionDetail(channelId, symbolId, field, wsConnection, subscription, optionMap); CCAPI_LOGGER_TRACE("channelId = " + channelId); this->correlationIdListByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId].push_back(subscription.getCorrelationId()); this->subscriptionListByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId].push_back(subscription); @@ -1183,7 +1183,7 @@ class MarketDataService : public Service { } virtual std::vector createSendStringList(const WsConnection& wsConnection) { return {}; } virtual void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) {} + const Subscription& subscription, const std::map optionMap) {} std::map>> fieldByConnectionIdChannelIdSymbolIdMap; std::map>>> optionMapByConnectionIdChannelIdSymbolIdMap; std::map>> marketDepthSubscribedToExchangeByConnectionIdChannelIdSymbolIdMap; diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_ascendex.h b/include/ccapi_cpp/service/ccapi_market_data_service_ascendex.h index 75a13d28..dc62f643 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_ascendex.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_ascendex.h @@ -29,7 +29,7 @@ class MarketDataServiceAscendex : public MarketDataService { #endif bool doesHttpBodyContainError(const Request& request, const std::string& body) override { return body.find(R"("code":0)") == std::string::npos; } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested == 1) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_binance_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_binance_base.h index 91baca5e..0537d379 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_binance_base.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_binance_base.h @@ -25,7 +25,7 @@ class MarketDataServiceBinanceBase : public MarketDataService { this->startSubscribe(wsConnection); } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_binance_derivatives_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_binance_derivatives_base.h index 1369137a..e956d7bf 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_binance_derivatives_base.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_binance_derivatives_base.h @@ -17,7 +17,7 @@ class MarketDataServiceBinanceDerivativesBase : public MarketDataServiceBinanceB protected: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bitfinex.h b/include/ccapi_cpp/service/ccapi_market_data_service_bitfinex.h index 43fe14b6..34309003 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_bitfinex.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bitfinex.h @@ -32,7 +32,7 @@ class MarketDataServiceBitfinex : public MarketDataService { this->send(hdl, "{\"cid\":" + std::to_string(UtilTime::getUnixTimestamp(now)) + ",\"event\":\"ping\"}", wspp::frame::opcode::text, ec); } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bitget.h b/include/ccapi_cpp/service/ccapi_market_data_service_bitget.h new file mode 100644 index 00000000..ea53e7d2 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bitget.h @@ -0,0 +1,30 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET +#include "ccapi_cpp/service/ccapi_market_data_service_bitget_base.h" +namespace ccapi { +class MarketDataServiceBitget : public MarketDataServiceBitgetBase { + public: + MarketDataServiceBitget(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataServiceBitgetBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BITGET; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/spot/v1/stream"; + 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/spot/v1/market/fills"; + this->getInstrumentTarget = "/api/spot/v1/public/product"; + this->getInstrumentsTarget = "/api/spot/v1/public/products"; + } + virtual ~MarketDataServiceBitget() {} +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_H_ diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bitget_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_bitget_base.h new file mode 100644 index 00000000..af206ff1 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bitget_base.h @@ -0,0 +1,379 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_BASE_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_BASE_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#if defined(CCAPI_ENABLE_EXCHANGE_BITGET) || defined(CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES) +#include "ccapi_cpp/service/ccapi_market_data_service.h" +namespace ccapi { +class MarketDataServiceBitgetBase : public MarketDataService { + public: + MarketDataServiceBitgetBase(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) {} + virtual ~MarketDataServiceBitgetBase() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + protected: +#endif + bool doesHttpBodyContainError(const Request& request, const std::string& body) override { + return !std::regex_search(body, std::regex("\"code\":\\s*\"00000\"")); + } + 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)); + auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); + if (field == CCAPI_MARKET_DEPTH) { + if (this->isDerivatives) { + if (conflateIntervalMilliSeconds < 200) { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS; + } else { + if (marketDepthRequested <= 1) { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS1; + } else if (marketDepthRequested <= 5) { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS5; + } else if (marketDepthRequested <= 15) { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS15; + } else { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS; + } + } + } else { + if (marketDepthRequested <= 5) { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS5; + } else if (marketDepthRequested <= 15) { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS15; + } else { + channelId = CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS; + } + } + } + } + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, "ping", wspp::frame::opcode::text, ec); } + std::vector createSendStringList(const WsConnection& wsConnection) override { + std::vector sendStringList; + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("subscribe").Move(), allocator); + rj::Value args(rj::kArrayType); + for (const auto& subscriptionListByChannelIdSymbolId : this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id)) { + auto channelId = subscriptionListByChannelIdSymbolId.first; + for (const auto& subscriptionListBySymbolId : subscriptionListByChannelIdSymbolId.second) { + std::string symbolId = subscriptionListBySymbolId.first; + if (channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS1 || channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS5 || + channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS15) { + this->l2UpdateIsReplaceByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId] = true; + } + std::string exchangeSubscriptionId = UtilString::split(channelId, "?").at(0) + ":" + symbolId; + rj::Value arg(rj::kObjectType); + std::string instType = this->isDerivatives ? "mc" : "sp"; + arg.AddMember("instType", rj::Value(instType.c_str(), allocator).Move(), allocator); + arg.AddMember("channel", rj::Value(channelId.c_str(), allocator).Move(), allocator); + arg.AddMember("instId", rj::Value(symbolId.c_str(), allocator).Move(), allocator); + args.PushBack(arg, allocator); + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID] = channelId; + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID] = symbolId; + } + } + document.AddMember("args", args, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + return sendStringList; + } + std::string calculateOrderBookChecksum(const std::map& snapshotBid, const std::map& snapshotAsk) override { + auto i = 0; + auto i1 = snapshotBid.rbegin(); + auto i2 = snapshotAsk.begin(); + std::vector csData; + while (i < 25 && (i1 != snapshotBid.rend() || i2 != snapshotAsk.end())) { + if (i1 != snapshotBid.rend()) { + csData.push_back(toString(i1->first)); + csData.push_back(i1->second); + ++i1; + } + if (i2 != snapshotAsk.end()) { + csData.push_back(toString(i2->first)); + csData.push_back(i2->second); + ++i2; + } + ++i; + } + std::string csStr = UtilString::join(csData, ":"); + uint_fast32_t csCalc = UtilAlgorithm::crc(csStr.begin(), csStr.end()); + return intToHex(csCalc); + } + void processTextMessage(WsConnection& wsConnection, wspp::connection_hdl hdl, const std::string& textMessage, const TimePoint& timeReceived, Event& event, + std::vector& marketDataMessageList) override { + if (textMessage != "pong") { + rj::Document document; + document.Parse(textMessage.c_str()); + auto it = document.FindMember("event"); + std::string eventStr = it != document.MemberEnd() ? it->value.GetString() : ""; + // if (eventStr == "login") { + // this->startSubscribe(wsConnection); + // } else { + if (!eventStr.empty()) { + if (eventStr == "subscribe") { + 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()) { + const rj::Value& arg = document["arg"]; + std::string channelId = arg["channel"].GetString(); + std::string symbolId = arg["instId"].GetString(); + 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(Message::Type::SUBSCRIPTION_STARTED); + Element element; + element.insert(CCAPI_INFO_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + event.setMessageList(messageList); + } else if (eventStr == "error") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + message.setType(Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + event.setMessageList(messageList); + } + } else if (document.HasMember("arg")) { + const rj::Value& arg = document["arg"]; + std::string channelId = arg["channel"].GetString(); + std::string symbolId = arg["instId"].GetString(); + std::string exchangeSubscriptionId = channelId + ":" + symbolId; + if (channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS || channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS1 || + channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS5 || channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS15) { + std::string action = document["action"].GetString(); + for (const auto& datum : document["data"].GetArray()) { + if (this->sessionOptions.enableCheckOrderBookChecksum) { + auto it = datum.FindMember("checksum"); + if (it != datum.MemberEnd()) { + this->orderBookChecksumByConnectionIdSymbolIdMap[wsConnection.id][symbolId] = + intToHex(static_cast(static_cast(std::stoi(it->value.GetString())))); + } + } + MarketDataMessage marketDataMessage; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(datum["ts"].GetString()))); + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_MARKET_DEPTH; + if (channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS1 || channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS5 || + channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_BOOKS15) { + if (this->processedInitialSnapshotByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]) { + marketDataMessage.recapType = MarketDataMessage::RecapType::NONE; + } else { + marketDataMessage.recapType = MarketDataMessage::RecapType::SOLICITED; + } + } else { + marketDataMessage.recapType = action == "update" ? MarketDataMessage::RecapType::NONE : MarketDataMessage::RecapType::SOLICITED; + } + for (const auto& x : datum["bids"].GetArray()) { + MarketDataMessage::TypeForDataPoint dataPoint; + if (this->sessionOptions.enableCheckOrderBookChecksum) { + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, x[0].GetString()}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, x[1].GetString()}); + } else { + 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)); + } + for (const auto& x : datum["asks"].GetArray()) { + MarketDataMessage::TypeForDataPoint dataPoint; + if (this->sessionOptions.enableCheckOrderBookChecksum) { + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, x[0].GetString()}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, x[1].GetString()}); + } else { + 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 (channelId == CCAPI_WEBSOCKET_BITGET_BASE_CHANNEL_TRADES) { + std::string action = document["action"].GetString(); + MarketDataMessage::RecapType recapType = action == "update" ? MarketDataMessage::RecapType::NONE : MarketDataMessage::RecapType::SOLICITED; + for (const auto& datum : document["data"].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.recapType = recapType; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(datum[0].GetString()))); + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(datum[1].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(datum[2].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(datum[3].GetString()) == "sell" ? "1" : "0"}); + marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } + } + } + // } + } + } + 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; + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, + { + {CCAPI_LIMIT, "limit"}, + }); + this->appendSymbolId(queryString, symbolId, "symbol"); + req.target(target + "?" + queryString); + } break; + case Request::Operation::GET_INSTRUMENT: { + req.method(http::verb::get); + std::string target; + std::string queryString; + if (this->isDerivatives) { + target = this->getInstrumentsTarget; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, + { + {CCAPI_INSTRUMENT_TYPE, "productType"}, + }); + } else { + target = this->getInstrumentTarget; + const std::map param = request.getFirstParamWithDefault(); + this->appendSymbolId(queryString, symbolId, "symbol"); + } + req.target(target + "?" + queryString); + } break; + case Request::Operation::GET_INSTRUMENTS: { + req.method(http::verb::get); + auto target = this->getInstrumentsTarget; + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + if (this->isDerivatives) { + this->appendParam(queryString, param, + { + {CCAPI_INSTRUMENT_TYPE, "productType"}, + }); + req.target(target + "?" + queryString); + } else { + req.target(target); + } + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractInstrumentInfo(Element& element, const rj::Value& x) { + if (this->isDerivatives) { + element.insert(CCAPI_INSTRUMENT, x["symbol"].GetString()); + element.insert(CCAPI_BASE_ASSET, x["baseCoin"].GetString()); + element.insert(CCAPI_QUOTE_ASSET, x["quoteCoin"].GetString()); + int pricePlace = std::stoi(x["pricePlace"].GetString()); + if (pricePlace > 0) { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "0." + std::string(pricePlace - 1, '0') + x["priceEndStep"].GetString()); + } else { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, x["priceEndStep"].GetString()); + } + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, x["sizeMultiplier"].GetString()); + element.insert(CCAPI_ORDER_QUANTITY_MIN, x["minTradeNum"].GetString()); + } else { + element.insert(CCAPI_INSTRUMENT, x["symbolName"].GetString()); + element.insert(CCAPI_BASE_ASSET, x["baseCoin"].GetString()); + element.insert(CCAPI_QUOTE_ASSET, x["quoteCoin"].GetString()); + int priceScale = std::stoi(x["priceScale"].GetString()); + if (priceScale > 0) { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "0." + std::string(priceScale - 1, '0') + "1"); + } else { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "1"); + } + int quantityScale = std::stoi(x["quantityScale"].GetString()); + if (quantityScale > 0) { + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, "0." + std::string(quantityScale - 1, '0') + "1"); + } else { + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, "1"); + } + element.insert(CCAPI_ORDER_QUANTITY_MIN, x["minTradeAmount"].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& datum : document["data"].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(datum[this->isDerivatives ? "timestamp" : "fillTime"].GetString()))); + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert( + {MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(datum[this->isDerivatives ? "price" : "fillPrice"].GetString())}); + dataPoint.insert( + {MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(datum[this->isDerivatives ? "size" : "fillQuantity"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::TRADE_ID, datum["tradeId"].GetString()}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(datum["side"].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["data"].GetArray()) { + if (std::string(x["symbol"].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["data"].GetArray()) { + Element element; + this->extractInstrumentInfo(element, x); + elementList.push_back(element); + } + message.setElementList(elementList); + message.setCorrelationIdList({request.getCorrelationId()}); + event.addMessages({message}); + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } + bool isDerivatives{}; +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_BASE_H_ diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bitget_futures.h b/include/ccapi_cpp/service/ccapi_market_data_service_bitget_futures.h new file mode 100644 index 00000000..32f24efc --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bitget_futures.h @@ -0,0 +1,31 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_FUTURES_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_FUTURES_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#ifdef CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES +#include "ccapi_cpp/service/ccapi_market_data_service_bitget_base.h" +namespace ccapi { +class MarketDataServiceBitgetFutures : public MarketDataServiceBitgetBase { + public: + MarketDataServiceBitgetFutures(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataServiceBitgetBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BITGET_FUTURES; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/mix/v1/stream"; + 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/mix/v1/market/fills"; + this->getInstrumentTarget = "/api/mix/v1/market/contracts"; + this->getInstrumentsTarget = "/api/mix/v1/market/contracts"; + this->isDerivatives = true; + } + virtual ~MarketDataServiceBitgetFutures() {} +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITGET_FUTURES_H_ diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bitmart.h b/include/ccapi_cpp/service/ccapi_market_data_service_bitmart.h new file mode 100644 index 00000000..952a62cd --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bitmart.h @@ -0,0 +1,318 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITMART_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITMART_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#ifdef CCAPI_ENABLE_EXCHANGE_BITMART +#include "ccapi_cpp/service/ccapi_market_data_service.h" +namespace ccapi { +class MarketDataServiceBitmart : public MarketDataService { + public: + MarketDataServiceBitmart(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BITMART; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/api?protocol=1.1"; + // this->needDecompressWebsocketMessage = true; + // ErrorCode ec = this->inflater.init(false); + // if (ec) { + // CCAPI_LOGGER_FATAL(ec.message()); + // } + 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->apiKeyName = CCAPI_BITMART_API_KEY; + this->apiSecretName = CCAPI_BITMART_API_SECRET; + this->apiMemo = CCAPI_BITMART_API_MEMO; + this->setupCredential({this->apiKeyName, this->apiSecretName, this->apiMemo}); + this->getRecentTradesTarget = "/spot/v1/symbols/trades"; + this->getInstrumentsTarget = "/spot/v1/symbols/details"; + } + virtual ~MarketDataServiceBitmart() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + private: +#endif + 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) { + if (marketDepthRequested <= 5) { + channelId = CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH5; + } else if (marketDepthRequested <= 20) { + channelId = CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH20; + } else { + channelId = CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH50; + } + } + } + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, "ping", wspp::frame::opcode::text, ec); } + std::vector createSendStringList(const WsConnection& wsConnection) override { + std::vector sendStringList; + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("subscribe").Move(), allocator); + rj::Value args(rj::kArrayType); + for (const auto& subscriptionListByChannelIdSymbolId : this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id)) { + auto channelId = subscriptionListByChannelIdSymbolId.first; + for (const auto& subscriptionListBySymbolId : subscriptionListByChannelIdSymbolId.second) { + std::string symbolId = subscriptionListBySymbolId.first; + if (channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH5 || channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH20 || + channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH50) { + this->l2UpdateIsReplaceByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId] = true; + } + std::string exchangeSubscriptionId = UtilString::split(channelId, "?").at(0) + ":" + symbolId; + args.PushBack(rj::Value(exchangeSubscriptionId.c_str(), allocator).Move(), allocator); + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID] = channelId; + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID] = symbolId; + } + } + document.AddMember("args", args, 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, + std::vector& marketDataMessageList) override { + if (textMessage != "pong") { + rj::Document document; + document.Parse(textMessage.c_str()); + auto it = document.FindMember("errorCode"); + std::string errorCode = it != document.MemberEnd() ? it->value.GetString() : ""; + if (errorCode.empty()) { + std::string channelId = document["table"].GetString(); + std::string symbolId = document["data"][0]["symbol"].GetString(); + std::string exchangeSubscriptionId = channelId + ":" + symbolId; + if (!this->subscriptionStartedByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]) { + const auto& subscriptionList = this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).at(symbolId); + std::vector correlationIdList; + for (const auto& subscription : subscriptionList) { + correlationIdList.push_back(subscription.getCorrelationId()); + } + Event event; + event.setType(Event::Type::SUBSCRIPTION_STATUS); + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + message.setCorrelationIdList(correlationIdList); + message.setType(Message::Type::SUBSCRIPTION_STARTED); + Element element; + element.insert(CCAPI_INFO_MESSAGE, textMessage); + message.setElementList({element}); + messageList.emplace_back(std::move(message)); + event.addMessages(messageList); + this->eventHandler(event, nullptr); + } + if (channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH5 || channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH20 || + channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_PUBLIC_DEPTH50) { + for (const auto& datum : document["data"].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(datum["ms_t"].GetString()))); + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_MARKET_DEPTH; + if (this->processedInitialSnapshotByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]) { + marketDataMessage.recapType = MarketDataMessage::RecapType::NONE; + } else { + marketDataMessage.recapType = MarketDataMessage::RecapType::SOLICITED; + } + for (const auto& x : datum["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)); + } + for (const auto& x : datum["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 (channelId == CCAPI_WEBSOCKET_BITMART_CHANNEL_TRADE) { + MarketDataMessage::RecapType recapType = MarketDataMessage::RecapType::NONE; + if (!this->subscriptionStartedByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]) { + recapType = MarketDataMessage::RecapType::SOLICITED; + } + for (const auto& datum : document["data"].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.recapType = recapType; + marketDataMessage.tp = TimePoint(std::chrono::seconds(std::stoll(datum["s_t"].GetString()))); + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(datum["price"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(datum["size"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(datum["side"].GetString()) == "buy" ? "1" : "0"}); + marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } + } + if (!this->subscriptionStartedByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]) { + this->subscriptionStartedByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId] = true; + } + } else { + std::string eventStr = document["event"].GetString(); + const auto& splitted = UtilString::split(eventStr, ':'); + if (splitted.at(0) == "subscribe") { + event.setType(Event::Type::SUBSCRIPTION_STATUS); + std::vector messageList; + Message message; + message.setTimeReceived(timeReceived); + message.setType(Message::Type::SUBSCRIPTION_FAILURE); + Element element; + element.insert(CCAPI_ERROR_MESSAGE, textMessage); + message.setElementList({element}); + const auto& channelId = splitted.at(1); + const auto& symbolId = splitted.at(2); + const auto& subscriptionList = this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).at(symbolId); + std::vector correlationIdList; + for (const auto& subscription : subscriptionList) { + correlationIdList.push_back(subscription.getCorrelationId()); + } + message.setCorrelationIdList(correlationIdList); + 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; + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, + { + {CCAPI_LIMIT, "N"}, + }); + this->appendSymbolId(queryString, symbolId, "symbol"); + req.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); + auto target = this->getInstrumentsTarget; + req.target(target); + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractInstrumentInfo(Element& element, const rj::Value& x) { + element.insert(CCAPI_INSTRUMENT, x["symbol"].GetString()); + element.insert(CCAPI_BASE_ASSET, x["base_currency"].GetString()); + element.insert(CCAPI_QUOTE_ASSET, x["quote_currency"].GetString()); + int priceMaxPrecision = std::stoi(x["price_max_precision"].GetString()); + if (priceMaxPrecision > 0) { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "0." + std::string(priceMaxPrecision - 1, '0') + "1"); + } else { + element.insert(CCAPI_ORDER_PRICE_INCREMENT, "1" + std::string(-priceMaxPrecision, '0')); + } + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, UtilString::normalizeDecimalString(x["quote_increment"].GetString())); + element.insert(CCAPI_ORDER_QUANTITY_MIN, UtilString::normalizeDecimalString(x["base_min_size"].GetString())); + element.insert(CCAPI_ORDER_PRICE_TIMES_QUANTITY_MIN, + std::max(Decimal(x["min_buy_amount"].GetString()), Decimal(x["min_sell_amount"].GetString())).toString()); + } + 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& datum : document["data"]["trades"].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(datum["order_time"].GetString()))); + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(datum["price"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(datum["count"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(datum["type"].GetString()) == "buy" ? "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["data"]["symbols"].GetArray()) { + if (std::string(x["symbol"].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["data"]["symbols"].GetArray()) { + Element element; + this->extractInstrumentInfo(element, x); + elementList.push_back(element); + } + message.setElementList(elementList); + message.setCorrelationIdList({request.getCorrelationId()}); + event.addMessages({message}); + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } + std::vector createSendStringListFromSubscriptionList(const WsConnection& wsConnection, const std::vector& subscriptionList, + const TimePoint& now, const std::map& credential) override { + std::vector sendStringList; + rj::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("login").Move(), allocator); + rj::Value arg(rj::kObjectType); + auto apiKey = mapGetWithDefault(credential, this->apiKeyName); + auto apiSecret = mapGetWithDefault(credential, this->apiSecretName); + auto apiPassphrase = mapGetWithDefault(credential, this->apiMemo); + std::string ts = std::to_string(std::chrono::duration_cast(now.time_since_epoch()).count()); + arg.AddMember("apiKey", rj::Value(apiKey.c_str(), allocator).Move(), allocator); + arg.AddMember("passphrase", rj::Value(apiPassphrase.c_str(), allocator).Move(), allocator); + arg.AddMember("timestamp", rj::Value(ts.c_str(), allocator).Move(), allocator); + std::string signData = ts + "GET" + "/users/self/verify"; + std::string sign = UtilAlgorithm::base64Encode(Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, signData)); + arg.AddMember("sign", rj::Value(sign.c_str(), allocator).Move(), allocator); + rj::Value args(rj::kArrayType); + args.PushBack(arg, allocator); + document.AddMember("args", args, allocator); + rj::StringBuffer stringBuffer; + rj::Writer writer(stringBuffer); + document.Accept(writer); + std::string sendString = stringBuffer.GetString(); + sendStringList.push_back(sendString); + return sendStringList; + } + std::string apiMemo; + std::map>> subscriptionStartedByConnectionIdChannelIdSymbolIdMap; +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BITMART_H_ diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bitmex.h b/include/ccapi_cpp/service/ccapi_market_data_service_bitmex.h index 31fd9eaa..1cc733af 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_bitmex.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bitmex.h @@ -29,7 +29,7 @@ class MarketDataServiceBitmex : public MarketDataService { private: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested == 1) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bybit.h b/include/ccapi_cpp/service/ccapi_market_data_service_bybit.h index 47fb4907..9ccb0cf6 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_bybit.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bybit.h @@ -2,13 +2,13 @@ #define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_H_ #ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA #ifdef CCAPI_ENABLE_EXCHANGE_BYBIT -#include "ccapi_cpp/service/ccapi_market_data_service.h" +#include "ccapi_cpp/service/ccapi_market_data_service_bybit_base.h" namespace ccapi { -class MarketDataServiceBybit : public MarketDataService { +class MarketDataServiceBybit : public MarketDataServiceBybitBase { public: MarketDataServiceBybit(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, std::shared_ptr serviceContextPtr) - : MarketDataService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + : MarketDataServiceBybitBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { this->exchangeName = CCAPI_EXCHANGE_NAME_BYBIT; this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/spot/public/v3"; this->baseUrlRest = sessionConfigs.getUrlRestBase().at(this->exchangeName); @@ -19,7 +19,6 @@ class MarketDataServiceBybit : public MarketDataService { CCAPI_LOGGER_FATAL(std::string("e.what() = ") + e.what()); } this->getRecentTradesTarget = "/spot/v3/public/quote/trades"; - this->getInstrumentTarget = "/spot/v3/public/symbols"; this->getInstrumentsTarget = "/spot/v3/public/symbols"; } virtual ~MarketDataServiceBybit() {} @@ -27,9 +26,8 @@ class MarketDataServiceBybit : public MarketDataService { protected: #endif - void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, R"({"op":"ping"})", wspp::frame::opcode::text, ec); } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested == 1) { @@ -210,12 +208,12 @@ class MarketDataServiceBybit : public MarketDataService { } break; case Request::Operation::GET_INSTRUMENT: { req.method(http::verb::get); - auto target = this->getInstrumentTarget; + auto target = this->getInstrumentsTarget; req.target(target); } break; case Request::Operation::GET_INSTRUMENTS: { req.method(http::verb::get); - auto target = this->getInstrumentTarget; + auto target = this->getInstrumentsTarget; req.target(target); } break; default: diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bybit_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_bybit_base.h new file mode 100644 index 00000000..0a23f6a2 --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bybit_base.h @@ -0,0 +1,23 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_BASE_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_BASE_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#if defined(CCAPI_ENABLE_EXCHANGE_BYBIT) || defined(CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES) +#include "ccapi_cpp/service/ccapi_market_data_service.h" +namespace ccapi { +class MarketDataServiceBybitBase : public MarketDataService { + public: + MarketDataServiceBybitBase(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataService(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) {} + virtual ~MarketDataServiceBybitBase() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + protected: +#endif + bool doesHttpBodyContainError(const Request& request, const std::string& body) override { return body.find(R"("retCode":0)") == std::string::npos; } + void pingOnApplicationLevel(wspp::connection_hdl hdl, ErrorCode& ec) override { this->send(hdl, R"({"op":"ping"})", wspp::frame::opcode::text, ec); } +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_BASE_H_ diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_bybit_derivatives.h b/include/ccapi_cpp/service/ccapi_market_data_service_bybit_derivatives.h new file mode 100644 index 00000000..3f37fbdc --- /dev/null +++ b/include/ccapi_cpp/service/ccapi_market_data_service_bybit_derivatives.h @@ -0,0 +1,319 @@ +#ifndef INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_DERIVATIVES_H_ +#define INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_DERIVATIVES_H_ +#ifdef CCAPI_ENABLE_SERVICE_MARKET_DATA +#ifdef CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES +#include "ccapi_cpp/service/ccapi_market_data_service_bybit_base.h" +namespace ccapi { +class MarketDataServiceBybitDerivatives : public MarketDataServiceBybitBase { + public: + MarketDataServiceBybitDerivatives(std::function*)> eventHandler, SessionOptions sessionOptions, SessionConfigs sessionConfigs, + std::shared_ptr serviceContextPtr) + : MarketDataServiceBybitBase(eventHandler, sessionOptions, sessionConfigs, serviceContextPtr) { + this->exchangeName = CCAPI_EXCHANGE_NAME_BYBIT_DERIVATIVES; + this->baseUrl = sessionConfigs.getUrlWebsocketBase().at(this->exchangeName) + "/{instrumentTypeSubstitute}/public/v3"; + 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 = "/derivatives/v3/public/recent-trade"; + this->getInstrumentTarget = "/derivatives/v3/public/instruments-info"; + this->getInstrumentsTarget = "/derivatives/v3/public/instruments-info"; + } + virtual ~MarketDataServiceBybitDerivatives() {} +#ifndef CCAPI_EXPOSE_INTERNAL + + protected: +#endif + std::string getInstrumentGroup(const Subscription& subscription) override { + const auto& instrumentType = subscription.getInstrumentType(); + std::string instrumentTypeSubstitute; + if (instrumentType == "usdt-contract") { + instrumentTypeSubstitute = "contract/usdt"; + } else if (instrumentType == "usdc-contract") { + instrumentTypeSubstitute = "contract/usdc"; + } else if (instrumentType == "usdc-options") { + instrumentTypeSubstitute = "option/usdc"; + } + std::string url = this->baseUrl; + std::string toReplace("{instrumentTypeSubstitute}"); + url.replace(url.find(toReplace), toReplace.length(), instrumentTypeSubstitute); + return url; + } + void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, + const Subscription& subscription, const std::map optionMap) override { + const auto& marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); + const auto& instrumentType = subscription.getInstrumentType(); + if (field == CCAPI_MARKET_DEPTH) { + std::vector depths; + if (instrumentType == "usdc-options") { + depths = {25, 100}; + } else { + depths = {1, 50, 200}; + } + int marketDepthSubscribedToExchange = 1; + marketDepthSubscribedToExchange = this->calculateMarketDepthSubscribedToExchange(marketDepthRequested, depths); + 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; + std::vector exchangeSubscriptionIdList; + rj::Document document; + document.SetObject(); + rj::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember("op", rj::Value("subscribe").Move(), allocator); + for (const auto& subscriptionListByChannelIdSymbolId : this->subscriptionListByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id)) { + auto channelId = subscriptionListByChannelIdSymbolId.first; + for (const auto& subscriptionListByInstrument : subscriptionListByChannelIdSymbolId.second) { + auto symbolId = subscriptionListByInstrument.first; + std::string exchangeSubscriptionId; + if (channelId.rfind(CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_ORDERBOOK, 0) == 0) { + int marketDepthSubscribedToExchange = + this->marketDepthSubscribedToExchangeByConnectionIdChannelIdSymbolIdMap.at(wsConnection.id).at(channelId).at(symbolId); + exchangeSubscriptionId = CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_ORDERBOOK; + std::string toReplace = "{depth}"; + exchangeSubscriptionId.replace(exchangeSubscriptionId.find(toReplace), toReplace.length(), std::to_string(marketDepthSubscribedToExchange)); + } else if (channelId == CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_TRADE) { + exchangeSubscriptionId = CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_TRADE; + } + std::string toReplace = "{symbol}"; + exchangeSubscriptionId.replace(exchangeSubscriptionId.find(toReplace), toReplace.length(), symbolId); + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID] = channelId; + this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID] = symbolId; + rj::Value args(rj::kArrayType); + args.PushBack(rj::Value(exchangeSubscriptionId.c_str(), allocator).Move(), allocator); + document.AddMember("args", args, allocator); + exchangeSubscriptionIdList.push_back(exchangeSubscriptionId); + } + } + document.AddMember("req_id", rj::Value(std::to_string(this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]).c_str(), allocator).Move(), allocator); + this->exchangeSubscriptionIdListByExchangeJsonPayloadIdByConnectionIdMap[wsConnection.id][this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id]] = + exchangeSubscriptionIdList; + this->exchangeJsonPayloadIdByConnectionIdMap[wsConnection.id] += 1; + 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, + std::vector& marketDataMessageList) override { + rj::Document document; + document.Parse(textMessage.c_str()); + if (document.IsObject() && document.HasMember("op")) { + std::string op = document["op"].GetString(); + if (op == "subscribe") { + bool success = document["success"].GetBool(); + 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()) { + int id = std::stoi(document["req_id"].GetString()); + 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); + } + } else if (document.IsObject() && document.HasMember("data")) { + std::string exchangeSubscriptionId = document["topic"].GetString(); + std::string channelId = this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_CHANNEL_ID]; + std::string symbolId = this->channelIdSymbolIdByConnectionIdExchangeSubscriptionIdMap[wsConnection.id][exchangeSubscriptionId][CCAPI_SYMBOL_ID]; + auto optionMap = this->optionMapByConnectionIdChannelIdSymbolIdMap[wsConnection.id][channelId][symbolId]; + const rj::Value& data = document["data"]; + if (channelId.rfind(CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_ORDERBOOK, 0) == 0) { + MarketDataMessage marketDataMessage; + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(document["ts"].GetString()))); + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_MARKET_DEPTH; + std::string type = document["type"].GetString(); + marketDataMessage.recapType = type == "snapshot" ? MarketDataMessage::RecapType::SOLICITED : MarketDataMessage::RecapType::NONE; + const char* bidsName = "b"; + int bidIndex = 0; + int maxMarketDepth = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); + for (const auto& x : data[bidsName].GetArray()) { + if (bidIndex >= maxMarketDepth) { + break; + } + 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)); + ++bidIndex; + } + const char* asksName = "a"; + int askIndex = 0; + for (const auto& x : data[asksName].GetArray()) { + if (askIndex >= maxMarketDepth) { + break; + } + 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)); + ++askIndex; + } + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } else if (channelId == CCAPI_WEBSOCKET_BYBIT_DERIVATIVES_CHANNEL_TRADE) { + for (const auto& x : data.GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.tp = TimePoint(std::chrono::milliseconds(std::stoll(x["T"].GetString()))); + marketDataMessage.exchangeSubscriptionId = exchangeSubscriptionId; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.recapType = MarketDataMessage::RecapType::NONE; + MarketDataMessage::TypeForDataPoint dataPoint; + dataPoint.insert({MarketDataMessage::DataFieldType::PRICE, UtilString::normalizeDecimalString(std::string(x["p"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::SIZE, UtilString::normalizeDecimalString(std::string(x["v"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::TRADE_ID, std::string(x["i"].GetString())}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(x["S"].GetString()) == "Buy" ? "0" : "1"}); + marketDataMessage.data[MarketDataMessage::DataType::TRADE].emplace_back(std::move(dataPoint)); + marketDataMessageList.emplace_back(std::move(marketDataMessage)); + } + } + } + } + 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; + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, + { + {CCAPI_LIMIT, "limit"}, + }); + this->appendSymbolId(queryString, symbolId, "symbol"); + req.target(target + "?" + queryString); + } break; + case Request::Operation::GET_INSTRUMENT: { + req.method(http::verb::get); + auto target = this->getInstrumentTarget; + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, + { + {CCAPI_INSTRUMENT_TYPE, "category"}, + }); + if (!symbolId.empty()) { + this->appendSymbolId(queryString, symbolId, "symbol"); + } + req.target(target + "?" + queryString); + } break; + case Request::Operation::GET_INSTRUMENTS: { + req.method(http::verb::get); + auto target = this->getInstrumentsTarget; + std::string queryString; + const std::map param = request.getFirstParamWithDefault(); + this->appendParam(queryString, param, + { + {CCAPI_INSTRUMENT_TYPE, "category"}, + }); + req.target(target + "?" + queryString); + } break; + default: + this->convertRequestForRestCustom(req, request, now, symbolId, credential); + } + } + void extractInstrumentInfo(Element& element, const rj::Value& x) { + element.insert(CCAPI_INSTRUMENT, x["symbol"].GetString()); + element.insert(CCAPI_BASE_ASSET, x["baseCoin"].GetString()); + element.insert(CCAPI_QUOTE_ASSET, x["quoteCoin"].GetString()); + auto it = x.FindMember("settleCoin"); + if (it != x.MemberEnd()) { + element.insert(CCAPI_MARGIN_ASSET, it->value.GetString()); + } + element.insert(CCAPI_ORDER_PRICE_INCREMENT, x["priceFilter"]["tickSize"].GetString()); + element.insert(CCAPI_ORDER_QUANTITY_INCREMENT, x["lotSizeFilter"]["qtyStep"].GetString()); + element.insert(CCAPI_ORDER_QUANTITY_MIN, x["lotSizeFilter"]["minTradingQty"].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["result"]["list"].GetArray()) { + MarketDataMessage marketDataMessage; + marketDataMessage.type = MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE; + marketDataMessage.tp = UtilTime::makeTimePointFromMilliseconds(std::stoll(x["time"].GetString())); + 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["size"].GetString()))}); + dataPoint.insert({MarketDataMessage::DataFieldType::IS_BUYER_MAKER, std::string(x["side"].GetString()) == "sell" ? "0" : "1"}); + dataPoint.insert({MarketDataMessage::DataFieldType::TRADE_ID, x["execId"].GetString()}); + 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["result"]["list"].GetArray()) { + if (std::string(x["symbol"].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["result"]["list"].GetArray()) { + Element element; + this->extractInstrumentInfo(element, x); + elementList.push_back(element); + } + message.setElementList(elementList); + message.setCorrelationIdList({request.getCorrelationId()}); + event.addMessages({message}); + } break; + default: + CCAPI_LOGGER_FATAL(CCAPI_UNSUPPORTED_VALUE); + } + } +}; +} /* namespace ccapi */ +#endif +#endif +#endif // INCLUDE_CCAPI_CPP_SERVICE_CCAPI_MARKET_DATA_SERVICE_BYBIT_DERIVATIVES_H_ 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 fbfffdb4..56d4f66f 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_cryptocom.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_cryptocom.h @@ -28,7 +28,7 @@ class MarketDataServiceCryptocom : public MarketDataService { private: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + 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 = 1; diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_deribit.h b/include/ccapi_cpp/service/ccapi_market_data_service_deribit.h index eaafbfd8..fa5a40bc 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_deribit.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_deribit.h @@ -50,7 +50,7 @@ class MarketDataServiceDeribit : public MarketDataService { } } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested == 1) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_erisx.h b/include/ccapi_cpp/service/ccapi_market_data_service_erisx.h index a61d17ef..a05f7417 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_erisx.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_erisx.h @@ -19,7 +19,7 @@ class MarketDataServiceErisx : public MarketDataService { private: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested <= 20) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_ftx_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_ftx_base.h index db5795f9..40bd73de 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_ftx_base.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_ftx_base.h @@ -21,7 +21,7 @@ class MarketDataServiceFtxBase : public MarketDataService { private: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested == 1) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_gateio_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_gateio_base.h index 48b40d53..47a9d9d3 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_gateio_base.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_gateio_base.h @@ -21,7 +21,7 @@ class MarketDataServiceGateioBase : public MarketDataService { wspp::frame::opcode::text, ec); } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_gemini.h b/include/ccapi_cpp/service/ccapi_market_data_service_gemini.h index 25370beb..3fef4384 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_gemini.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_gemini.h @@ -28,7 +28,7 @@ class MarketDataServiceGemini : public MarketDataService { private: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_huobi_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_huobi_base.h index fe5424d6..40835354 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_huobi_base.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_huobi_base.h @@ -22,7 +22,7 @@ class MarketDataServiceHuobiBase : public MarketDataService { protected: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { 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 a3b5a843..8c1f0b42 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_kraken.h @@ -34,7 +34,7 @@ class MarketDataServiceKraken : public MarketDataService { } bool doesHttpBodyContainError(const Request& request, const std::string& body) override { return body.find(R"("error":[])") == std::string::npos; } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { 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 68e46c75..7e5df46a 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 @@ -29,7 +29,7 @@ class MarketDataServiceKrakenFutures : public MarketDataService { private: #endif void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { symbolId = UtilString::toUpper(symbolId); } std::vector createSendStringList(const WsConnection& wsConnection) override { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_kucoin_base.h b/include/ccapi_cpp/service/ccapi_market_data_service_kucoin_base.h index 38f356aa..b6577222 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_kucoin_base.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_kucoin_base.h @@ -18,7 +18,7 @@ class MarketDataServiceKucoinBase : public MarketDataService { return !std::regex_search(body, std::regex("\"code\":\\s*\"200000\"")); } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); if (field == CCAPI_MARKET_DEPTH) { if (marketDepthRequested == 1) { diff --git a/include/ccapi_cpp/service/ccapi_market_data_service_okx.h b/include/ccapi_cpp/service/ccapi_market_data_service_okx.h index a923514e..5d4ff7b0 100644 --- a/include/ccapi_cpp/service/ccapi_market_data_service_okx.h +++ b/include/ccapi_cpp/service/ccapi_market_data_service_okx.h @@ -38,7 +38,7 @@ class MarketDataServiceOkx : public MarketDataService { #endif bool doesHttpBodyContainError(const Request& request, const std::string& body) override { return !std::regex_search(body, std::regex("\"code\":\\s*\"0\"")); } void prepareSubscriptionDetail(std::string& channelId, std::string& symbolId, const std::string& field, const WsConnection& wsConnection, - const std::map optionMap) override { + const Subscription& subscription, const std::map optionMap) override { auto marketDepthRequested = std::stoi(optionMap.at(CCAPI_MARKET_DEPTH_MAX)); auto conflateIntervalMilliSeconds = std::stoi(optionMap.at(CCAPI_CONFLATE_INTERVAL_MILLISECONDS)); if (field == CCAPI_MARKET_DEPTH) { @@ -271,7 +271,6 @@ class MarketDataServiceOkx : public MarketDataService { { {CCAPI_INSTRUMENT_TYPE, "instType"}, }); - this->appendSymbolId(queryString, symbolId, "instId"); req.target(target + "?" + queryString); } break; default: diff --git a/test/test_build/CMakeLists.txt b/test/test_build/CMakeLists.txt index 7805c84b..1bdede27 100644 --- a/test/test_build/CMakeLists.txt +++ b/test/test_build/CMakeLists.txt @@ -1,9 +1,9 @@ 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" "FTX" "FTX_US" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT") -set(EXECUTION_MANAGEMENT_EXCHANGE_LIST "COINBASE" "GEMINI" "KRAKEN" "KRAKEN_FUTURES" "BITSTAMP" "BITFINEX" "BITMEX" "BINANCE_US" "BINANCE" "BINANCE_MARGIN" "BINANCE_USDS_FUTURES" "BINANCE_COIN_FUTURES" "HUOBI" "HUOBI_USDT_SWAP" "HUOBI_COIN_SWAP" "OKX" "ERISX" "KUCOIN" "FTX" "FTX_US" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT") -set(FIX_EXCHANGE_LIST "COINBASE" "GEMINI" "FTX" "FTX_US") +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") +set(EXECUTION_MANAGEMENT_EXCHANGE_LIST "COINBASE" "GEMINI" "KRAKEN" "KRAKEN_FUTURES" "BITSTAMP" "BITFINEX" "BITMEX" "BINANCE_US" "BINANCE" "BINANCE_MARGIN" "BINANCE_USDS_FUTURES" "BINANCE_COIN_FUTURES" "HUOBI" "HUOBI_USDT_SWAP" "HUOBI_COIN_SWAP" "OKX" "ERISX" "KUCOIN" "DERIBIT" "GATEIO" "GATEIO_PERPETUAL_FUTURES" "CRYPTOCOM" "ASCENDEX" "BYBIT" "BYBIT_DERIVATIVES" "BITGET" "BITGET_FUTURES") +set(FIX_EXCHANGE_LIST "COINBASE" "GEMINI") set(HFFIX_INCLUDE_DIR ${CCAPI_PROJECT_DIR}/dependency/hffix/include) foreach(SERVICE IN LISTS SERVICE_LIST) message(STATUS "SERVICE=${SERVICE}") diff --git a/test/test_unit/CMakeLists.txt b/test/test_unit/CMakeLists.txt index 49b54cb5..a4e3683c 100644 --- a/test/test_unit/CMakeLists.txt +++ b/test/test_unit/CMakeLists.txt @@ -45,7 +45,6 @@ add_subdirectory(src/execution_management/binance_us) add_subdirectory(src/execution_management/bitmex) add_subdirectory(src/execution_management/coinbase) add_subdirectory(src/execution_management/erisx) -add_subdirectory(src/execution_management/ftx) add_subdirectory(src/execution_management/gateio) add_subdirectory(src/execution_management/gemini) add_subdirectory(src/execution_management/huobi) diff --git a/test/test_unit/src/execution_management/binance_usds_futures/test.cpp b/test/test_unit/src/execution_management/binance_usds_futures/test.cpp index c6330d60..6d0ab2d9 100644 --- a/test/test_unit/src/execution_management/binance_usds_futures/test.cpp +++ b/test/test_unit/src/execution_management/binance_usds_futures/test.cpp @@ -391,7 +391,7 @@ TEST_F(ExecutionManagementServiceBinanceUsdsFuturesTest, convertTextMessageToMes EXPECT_EQ(element.getValue(CCAPI_INSTRUMENT), "BTCUSDT"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_SIDE), "BOTH"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_QUANTITY), "0"); - EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_POSITION_COST)), 0); + EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_POSITION_ENTRY_PRICE)), 0); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_LEVERAGE), "100"); } } /* namespace ccapi */ diff --git a/test/test_unit/src/execution_management/bitmex/test.cpp b/test/test_unit/src/execution_management/bitmex/test.cpp index 1e67f7d3..bd545363 100644 --- a/test/test_unit/src/execution_management/bitmex/test.cpp +++ b/test/test_unit/src/execution_management/bitmex/test.cpp @@ -649,6 +649,7 @@ TEST_F(ExecutionManagementServiceBitmexTest, convertTextMessageToMessageRestGetA EXPECT_EQ(element.getValue(CCAPI_EM_ASSET), "XBt"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_QUANTITY), "500"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_COST), "-1485034"); + EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_ENTRY_PRICE), "33669.3535"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_LEVERAGE), "100"); } diff --git a/test/test_unit/src/execution_management/ftx/CMakeLists.txt b/test/test_unit/src/execution_management/ftx/CMakeLists.txt deleted file mode 100644 index 00c0dbf3..00000000 --- a/test/test_unit/src/execution_management/ftx/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(NAME execution_management_ftx) -project(${NAME}) -add_compile_definitions(CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT) -add_compile_definitions(CCAPI_ENABLE_EXCHANGE_FTX) -add_compile_definitions(CCAPI_ENABLE_LOG_TRACE) -add_executable(${NAME} ${SOURCE_LOGGER} test.cpp) -gtest_discover_tests(${NAME}) diff --git a/test/test_unit/src/execution_management/ftx/test.cpp b/test/test_unit/src/execution_management/ftx/test.cpp deleted file mode 100644 index 383a0667..00000000 --- a/test/test_unit/src/execution_management/ftx/test.cpp +++ /dev/null @@ -1,602 +0,0 @@ -#ifdef CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT -#ifdef CCAPI_ENABLE_EXCHANGE_FTX -// clang-format off -#include "gtest/gtest.h" -#include "ccapi_cpp/ccapi_test_execution_management_helper.h" -#include "ccapi_cpp/service/ccapi_execution_management_service_ftx.h" -// clang-format on -namespace ccapi { -class ExecutionManagementServiceFtxTest : public ::testing::Test { - public: - typedef Service::ServiceContextPtr ServiceContextPtr; - void SetUp() override { - this->service = std::make_shared([](Event&, Queue*) {}, SessionOptions(), SessionConfigs(), - wspp::lib::make_shared()); - this->credential = { - {CCAPI_FTX_API_KEY, "h3Pc-sRGCtWQANSbWi-6TzoOsLIkHD2_KbRvLULr"}, - {CCAPI_FTX_API_SECRET, "aJj_9jfURAz6JFtVKklvIIrvNRMIRfKUshUjfcYB"}, - }; - this->credential_2 = { - {CCAPI_FTX_API_KEY, "h3Pc-sRGCtWQANSbWi-6TzoOsLIkHD2_KbRvLULr"}, - {CCAPI_FTX_API_SECRET, "aJj_9jfURAz6JFtVKklvIIrvNRMIRfKUshUjfcYB"}, - {CCAPI_FTX_API_SUBACCOUNT, "0x1a5y8koaa9"}, - }; - this->timestamp = 1499827319; - this->now = UtilTime::makeTimePointFromMilliseconds(this->timestamp * 1000LL); - } - std::shared_ptr service{nullptr}; - std::map credential; - std::map credential_2; - long long timestamp{}; - TimePoint now{}; -}; - -void verifyApiKeyEtc(const http::request& req, const std::string& apiKey, const std::string& apiSubaccount, long long timestamp) { - EXPECT_EQ(req.base().at("FTX-KEY").to_string(), apiKey); - EXPECT_EQ(req.base().at("FTX-TS").to_string(), std::to_string(timestamp * 1000LL)); - if (!apiSubaccount.empty()) { - EXPECT_EQ(req.base().at("FTX-SUBACCOUNT").to_string(), Url::urlEncode(apiSubaccount)); - } else { - EXPECT_TRUE(req.base().find("FTX-SUBACCOUNT") == req.base().end()); - } -} - -void verifySignature(const http::request& req, const std::string& apiSecret) { - auto preSignedText = req.base().at("FTX-TS").to_string(); - preSignedText += UtilString::toUpper(std::string(req.method_string())); - preSignedText += req.target().to_string(); - preSignedText += req.body(); - auto signature = req.base().at("FTX-SIGN").to_string(); - EXPECT_EQ(UtilString::toLower(UtilAlgorithm::stringToHex(Hmac::hmac(Hmac::ShaVersion::SHA256, apiSecret, preSignedText))), signature); -} - -TEST_F(ExecutionManagementServiceFtxTest, signRequest) { - http::request req; - req.set("FTX-TS", "1622256962809"); - req.method(http::verb::post); - req.target("/api/orders"); - std::string body = "{\"price\":20000.0,\"size\":0.001,\"side\":\"buy\",\"type\":\"limit\",\"market\":\"BTC/USD\"}"; - this->service->signRequest(req, body, this->credential); - EXPECT_EQ(req.base().at("FTX-SIGN").to_string(), "6ea0081ca5fc8f095a748498a284e812fa92923a60d289fe889d838107d30e95"); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestCreateOrder) { - Request request(Request::Operation::CREATE_ORDER, CCAPI_EXCHANGE_NAME_FTX, "XRP-PERP", "foo", this->credential); - std::map param{ - {CCAPI_EM_ORDER_SIDE, CCAPI_EM_ORDER_SIDE_SELL}, - {CCAPI_EM_ORDER_QUANTITY, "31431.0"}, - {CCAPI_EM_ORDER_LIMIT_PRICE, "0.306525"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::post); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/orders"); - rj::Document document; - document.Parse(req.body().c_str()); - EXPECT_EQ(std::string(document["market"].GetString()), "XRP-PERP"); - EXPECT_EQ(std::string(document["side"].GetString()), "sell"); - EXPECT_EQ(std::string(document["price"].GetString()), "0.306525"); - EXPECT_EQ(std::string(document["size"].GetString()), "31431.0"); - EXPECT_EQ(std::string(document["type"].GetString()), "limit"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestCreateOrder_2) { - Request request(Request::Operation::CREATE_ORDER, CCAPI_EXCHANGE_NAME_FTX, "XRP-PERP", "foo", this->credential_2); - std::map param{ - {CCAPI_EM_ORDER_SIDE, CCAPI_EM_ORDER_SIDE_SELL}, - {CCAPI_EM_ORDER_QUANTITY, "31431.0"}, - {CCAPI_EM_ORDER_LIMIT_PRICE, "0.306525"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - verifyApiKeyEtc(req, this->credential_2.at(CCAPI_FTX_API_KEY), this->credential_2.at(CCAPI_FTX_API_SUBACCOUNT), this->timestamp); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestCreateOrder) { - Request request(Request::Operation::CREATE_ORDER, CCAPI_EXCHANGE_NAME_FTX, "XRP-PERP", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": { - "createdAt": "2019-03-05T09:56:55.728933+00:00", - "filledSize": 0, - "future": "XRP-PERP", - "id": 9596912, - "market": "XRP-PERP", - "price": 0.306525, - "remainingSize": 31431, - "side": "sell", - "size": 31431, - "status": "open", - "type": "limit", - "reduceOnly": false, - "ioc": false, - "postOnly": false, - "clientId": null - } - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::CREATE_ORDER); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_ID), "9596912"); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestCancelOrderByOrderId) { - Request request(Request::Operation::CANCEL_ORDER, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::map param{ - {CCAPI_EM_ORDER_ID, "d0c5340b-6d6c-49d9-b567-48c4bfca13d2"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::delete_); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/orders/d0c5340b-6d6c-49d9-b567-48c4bfca13d2"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestCancelOrderByClientOrderId) { - Request request(Request::Operation::CANCEL_ORDER, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::map param{ - {CCAPI_EM_CLIENT_ORDER_ID, "d0c5340b-6d6c-49d9-b567-48c4bfca13d2"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::delete_); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/orders/by_client_id/d0c5340b-6d6c-49d9-b567-48c4bfca13d2"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestCancelOrder) { - Request request(Request::Operation::CANCEL_ORDER, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": "Order queued for cancelation" - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::CANCEL_ORDER); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetOrderByOrderId) { - Request request(Request::Operation::GET_ORDER, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::map param{ - {CCAPI_EM_ORDER_ID, "d0c5340b-6d6c-49d9-b567-48c4bfca13d2"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/orders/d0c5340b-6d6c-49d9-b567-48c4bfca13d2"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetOrderByClientOrderId) { - Request request(Request::Operation::GET_ORDER, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::map param{ - {CCAPI_EM_CLIENT_ORDER_ID, "d0c5340b-6d6c-49d9-b567-48c4bfca13d2"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/orders/by_client_id/d0c5340b-6d6c-49d9-b567-48c4bfca13d2"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetOrder) { - Request request(Request::Operation::GET_ORDER, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": { - "createdAt": "2019-03-05T09:56:55.728933+00:00", - "filledSize": 10, - "future": "XRP-PERP", - "id": 9596912, - "market": "XRP-PERP", - "price": 0.306525, - "avgFillPrice": 0.306526, - "remainingSize": 31421, - "side": "sell", - "size": 31431, - "status": "open", - "type": "limit", - "reduceOnly": false, - "ioc": false, - "postOnly": false, - "clientId": null - } - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::GET_ORDER); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_ID), "9596912"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_SIDE), CCAPI_EM_ORDER_SIDE_SELL); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_QUANTITY), "31431"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY), "10"); - EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY)), 0.306526 * 10); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_STATUS), "open"); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetOpenOrdersOneInstrument) { - Request request(Request::Operation::GET_OPEN_ORDERS, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - auto splitted = UtilString::split(req.target().to_string(), "?"); - EXPECT_EQ(splitted.at(0), "/api/orders"); - auto paramMap = Url::convertQueryStringToMap(splitted.at(1)); - EXPECT_EQ(paramMap.at("market"), "BTC/USD"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetOpenOrdersAllInstruments) { - Request request(Request::Operation::GET_OPEN_ORDERS, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - auto splitted = UtilString::split(req.target().to_string(), "?"); - EXPECT_EQ(splitted.at(0), "/api/orders"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -void verifyconvertTextMessageToMessageRestGetOpenOrders(const ExecutionManagementServiceFtxTest* fixture, bool isOneInstrument) { - std::string symbol = isOneInstrument ? "XRP-PERP" : ""; - Request request(Request::Operation::GET_OPEN_ORDERS, CCAPI_EXCHANGE_NAME_FTX, symbol, "", fixture->credential); - std::string textMessage = - R"( - { - "success": true, - "result": [ - { - "createdAt": "2019-03-05T09:56:55.728933+00:00", - "filledSize": 10, - "future": "XRP-PERP", - "id": 9596912, - "market": "XRP-PERP", - "price": 0.306525, - "avgFillPrice": 0.306526, - "remainingSize": 31421, - "side": "sell", - "size": 31431, - "status": "open", - "type": "limit", - "reduceOnly": false, - "ioc": false, - "postOnly": false, - "clientId": null - } - ] - } - )"; - auto messageList = fixture->service->convertTextMessageToMessageRest(request, textMessage, fixture->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::GET_OPEN_ORDERS); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_ID), "9596912"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_SIDE), CCAPI_EM_ORDER_SIDE_SELL); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_QUANTITY), "31431"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_LIMIT_PRICE), "0.306525"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY), "10"); - EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY)), 0.306526 * 10); - if (!isOneInstrument) { - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_INSTRUMENT), "XRP-PERP"); - } -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetOpenOrdersOneInstrument) { - verifyconvertTextMessageToMessageRestGetOpenOrders(this, true); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetOpenOrdersAllInstruments) { - verifyconvertTextMessageToMessageRestGetOpenOrders(this, false); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestCancelOpenOrders) { - Request request(Request::Operation::CANCEL_OPEN_ORDERS, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::delete_); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/orders"); - rj::Document document; - document.Parse(req.body().c_str()); - EXPECT_EQ(std::string(document["market"].GetString()), "BTC/USD"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestCancelOpenOrders) { - Request request(Request::Operation::CANCEL_OPEN_ORDERS, CCAPI_EXCHANGE_NAME_FTX, "BTC/USD", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": "Orders queued for cancelation" - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::CANCEL_OPEN_ORDERS); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetAccounts) { - Request request(Request::Operation::GET_ACCOUNTS, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/subaccounts"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetAccounts) { - Request request(Request::Operation::GET_ACCOUNTS, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": [ - { - "nickname": "sub1", - "deletable": true, - "editable": true, - "competition": true - } - ] - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::GET_ACCOUNTS); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ACCOUNT_ID), "sub1"); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetAccountBalances) { - Request request(Request::Operation::GET_ACCOUNT_BALANCES, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/wallet/balances"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetAccountBalances) { - Request request(Request::Operation::GET_ACCOUNT_BALANCES, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": [ - { - "coin": "USDTBEAR", - "free": 2320.2, - "total": 2340.2 - } - ] - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::GET_ACCOUNT_BALANCES); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ASSET), "USDTBEAR"); - EXPECT_EQ(element.getValue(CCAPI_EM_QUANTITY_AVAILABLE_FOR_TRADING), "2320.2"); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetAccountBalances_2) { - Request request(Request::Operation::GET_ACCOUNT_BALANCES, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential_2); - std::map param{ - {CCAPI_EM_ACCOUNT_ID, "sub1"}, - }; - request.appendParam(param); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential_2.at(CCAPI_FTX_API_KEY), this->credential_2.at(CCAPI_FTX_API_SUBACCOUNT), this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/subaccounts/sub1/balances"); - verifySignature(req, this->credential_2.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetAccountBalances_2) { - Request request(Request::Operation::GET_ACCOUNT_BALANCES, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential_2); - std::string textMessage = - R"( - { - "success": true, - "result": [ - { - "coin": "USDT", - "free": 4321.2, - "total": 4340.2, - "spotBorrow": 0, - "availableWithoutBorrow": 2320.2 - } - ] - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::GET_ACCOUNT_BALANCES); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ASSET), "USDT"); - EXPECT_EQ(element.getValue(CCAPI_EM_QUANTITY_AVAILABLE_FOR_TRADING), "4321.2"); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertRequestGetAccountPositions) { - Request request(Request::Operation::GET_ACCOUNT_POSITIONS, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - auto req = this->service->convertRequest(request, this->now); - EXPECT_EQ(req.method(), http::verb::get); - verifyApiKeyEtc(req, this->credential.at(CCAPI_FTX_API_KEY), "", this->timestamp); - EXPECT_EQ(req.target().to_string(), "/api/positions"); - verifySignature(req, this->credential.at(CCAPI_FTX_API_SECRET)); -} - -TEST_F(ExecutionManagementServiceFtxTest, convertTextMessageToMessageRestGetAccountPositions) { - Request request(Request::Operation::GET_ACCOUNT_POSITIONS, CCAPI_EXCHANGE_NAME_FTX, "", "foo", this->credential); - std::string textMessage = - R"( - { - "success": true, - "result": [ - { - "cost": -31.7906, - "entryPrice": 138.22, - "estimatedLiquidationPrice": 152.1, - "future": "ETH-PERP", - "initialMarginRequirement": 0.1, - "longOrderSize": 1744.55, - "maintenanceMarginRequirement": 0.04, - "netSize": -0.23, - "openSize": 1744.32, - "realizedPnl": 3.39441714, - "shortOrderSize": 1732.09, - "side": "sell", - "size": 0.23, - "unrealizedPnl": 0, - "collateralUsed": 3.17906 - } - ] - } - )"; - auto messageList = this->service->convertTextMessageToMessageRest(request, textMessage, this->now); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, request.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::GET_ACCOUNT_POSITIONS); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_INSTRUMENT), "ETH-PERP"); - EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_SIDE), "sell"); - EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_QUANTITY), "0.23"); - EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_COST), "-31.7906"); -} - -TEST_F(ExecutionManagementServiceFtxTest, createEventFills) { - Subscription subscription(CCAPI_EXCHANGE_NAME_FTX, "BTC-PERP", CCAPI_EM_PRIVATE_TRADE); - std::string textMessage = R"( - { - "channel": "fills", - "data": { - "fee": 78.05799225, - "feeRate": 0.0014, - "future": "BTC-PERP", - "id": 7828307, - "liquidity": "taker", - "market": "BTC-PERP", - "orderId": 38065410, - "tradeId": 19129310, - "price": 3723.75, - "side": "buy", - "size": 14.973, - "time": "2019-05-07T16:40:58.358438+00:00", - "type": "order" - }, - "type": "update" - } -)"; - rj::Document document; - document.Parse(textMessage.c_str()); - auto messageList = this->service->createEvent(subscription, textMessage, document, this->now).getMessageList(); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, subscription.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::EXECUTION_MANAGEMENT_EVENTS_PRIVATE_TRADE); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_TRADE_ID), "19129310"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_LAST_EXECUTED_PRICE), "3723.75"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_LAST_EXECUTED_SIZE), "14.973"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_SIDE), CCAPI_EM_ORDER_SIDE_BUY); - EXPECT_EQ(element.getValue(CCAPI_IS_MAKER), "0"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_ID), "38065410"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_INSTRUMENT), "BTC-PERP"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_FEE_QUANTITY), "78.05799225"); -} - -TEST_F(ExecutionManagementServiceFtxTest, createEventOrders) { - Subscription subscription(CCAPI_EXCHANGE_NAME_FTX, "XRP-PERP", CCAPI_EM_ORDER_UPDATE); - std::string textMessage = R"( - { - "channel": "orders", - "data": { - "id": 24852229, - "clientId": null, - "market": "XRP-PERP", - "type": "limit", - "side": "buy", - "size": 42353.0, - "price": 0.2977, - "reduceOnly": false, - "ioc": false, - "postOnly": false, - "status": "closed", - "filledSize": 0.0, - "remainingSize": 0.0, - "avgFillPrice": 0.2978 - }, - "type": "update" - } -)"; - rj::Document document; - document.Parse(textMessage.c_str()); - auto messageList = this->service->createEvent(subscription, textMessage, document, this->now).getMessageList(); - EXPECT_EQ(messageList.size(), 1); - verifyCorrelationId(messageList, subscription.getCorrelationId()); - auto message = messageList.at(0); - EXPECT_EQ(message.getType(), Message::Type::EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE); - auto elementList = message.getElementList(); - EXPECT_EQ(elementList.size(), 1); - Element element = elementList.at(0); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_ID), "24852229"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_SIDE), CCAPI_EM_ORDER_SIDE_BUY); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_LIMIT_PRICE), "0.2977"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_QUANTITY), "42353.0"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_REMAINING_QUANTITY), "0.0"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_CUMULATIVE_FILLED_QUANTITY), "0.0"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_STATUS), "closed"); - EXPECT_EQ(element.getValue(CCAPI_EM_ORDER_INSTRUMENT), "XRP-PERP"); - EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_ORDER_CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY)), 0); -} -} /* namespace ccapi */ -#endif -#endif diff --git a/test/test_unit/src/execution_management/kraken_futures/test.cpp b/test/test_unit/src/execution_management/kraken_futures/test.cpp index 0f57c6a2..16814b0c 100644 --- a/test/test_unit/src/execution_management/kraken_futures/test.cpp +++ b/test/test_unit/src/execution_management/kraken_futures/test.cpp @@ -408,7 +408,6 @@ TEST_F(ExecutionManagementServiceKrakenFuturesTest, convertTextMessageToMessageR EXPECT_EQ(element.getValue(CCAPI_INSTRUMENT), "pi_xbtusd"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_SIDE), "short"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_QUANTITY), "10000"); - EXPECT_NEAR(std::stod(element.getValue(CCAPI_EM_POSITION_COST)), 9392.749993345933 * 10000, 9392.749993345933 * 10000 * CCAPI_DOUBLE_ERROR_DEFAULT); } TEST_F(ExecutionManagementServiceKrakenFuturesTest, createEventFills) { diff --git a/test/test_unit/src/execution_management/okx/test.cpp b/test/test_unit/src/execution_management/okx/test.cpp index 7d90a94d..9997c9ee 100644 --- a/test/test_unit/src/execution_management/okx/test.cpp +++ b/test/test_unit/src/execution_management/okx/test.cpp @@ -516,7 +516,7 @@ TEST_F(ExecutionManagementServiceOkxTest, convertTextMessageToMessageRestGetAcco EXPECT_EQ(element.getValue(CCAPI_INSTRUMENT), "ETH-USD-210430"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_SIDE), "long"); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_QUANTITY), "1"); - EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_POSITION_COST)), 2566.31); + EXPECT_DOUBLE_EQ(std::stod(element.getValue(CCAPI_EM_POSITION_ENTRY_PRICE)), 2566.31); EXPECT_EQ(element.getValue(CCAPI_EM_POSITION_LEVERAGE), "10"); }