A header-only C++ library for interacting with crypto exchanges. Bindings for Python, Java, C#, Go, and Javascript are provided.
Table of Contents generated with DocToc
- Branches - Build - C++ - non-C++ - Constants - Examples - Documentations - Simple Market Data - Advanced Market Data - Complex request parameters - Specify subscription market depth - Specify correlation id - Multiple exchanges and/or instruments - Receive subscription events at periodic intervals - Receive subscription events at periodic intervals including when the market depth snapshot hasn't changed - Receive subscription market depth updates - Receive subscription trade events - Receive subscription calculated-candlestick events at periodic intervals - Receive subscription exchange-provided-candlestick events at periodic intervals - Send generic public requests - Make generic public subscriptions - Send generic private requests - Simple Execution Management - Advanced Execution Management - Specify correlation id - Multiple exchanges and/or instruments - Multiple subscription fields - Make Session::sendRequest blocking - Provide API credentials for an exchange - Complex request parameters - Send request by Websocket API - Specify instrument type - FIX API - More Advanced Topics - Handle events in "immediate" vs. "batching" mode - Thread safety - Enable library logging - Set timer - Heartbeat - Use multiple sessions - Override exchange urls - Connect to a proxy - Reduce build time - Performance Tuning - Known Issues and Workaroundsccapi
- A header-only C++ library for streaming market data and executing trades directly from cryptocurrency exchanges (i.e. the connections are between your server and the exchange server without anything in-between).
- Bindings for other languages such as Python, Java, C#, Go, and Javascript are provided.
- Code closely follows Bloomberg's API: https://www.bloomberg.com/professional/support/api-library/.
- It is ultra fast thanks to very careful optimizations.
- Supported exchanges:
- Join us on Discord https://discord.gg/b5EKcp9s8T and Medium https://cryptochassis.medium.com.
- For any questions, email hello@cryptochassis.com.
- We’re experts in market data collection, high-speed trading system, infrastructure optimization, and proprietary market making. Hire us as engineers, liquidity providers, traders, or asset managers.
Branches
- The
developbranch may contain experimental features. - The
masterbranch represents the most recent stable release.
Build
C++
- This library is header-only.
- Example CMake: example/CMakeLists.txt.
- Require C++17 and OpenSSL.
- Macros in the compiler command line:
CCAPIENABLESERVICEMARKETDATA, CCAPIENABLESERVICEEXECUTIONMANAGEMENT, CCAPIENABLESERVICEFIX, etc. and exchange enablement macros such as CCAPIENABLEEXCHANGEBYBIT, etc. These macros can be found at the top of include/ccapicpp/ccapi_session.h.
- Dependencies:
- Include directory for this library:
- Link libraries:
- Compiler flags:
-pthread for GCC and MinGW.
- Tested platforms:
- Troubleshoot:
cmake -DOPENSSLROOTDIR=.... On macOS, you might be missing headers for OpenSSL, brew install openssl and cmake -DOPENSSLROOTDIR=/usr/local/opt/openssl. On Ubuntu, sudo apt-get install libssl-dev. On Windows, vcpkg install openssl:x64-windows and cmake -DOPENSSLROOTDIR=C:/vcpkg/installed/x64-windows-static.
* "Fatal error: can't write \ bytes to section .text of \: 'File too big'". Try to add compiler flag -Wa,-mbig-obj. See https://github.com/assimp/assimp/issues/2067.
* "string table overflow at offset \". Try to add optimization flag -O1 or -O2. See https://stackoverflow.com/questions/14125007/gcc-string-table-overflow-error-during-compilation.
* On Windows, if you still encounter resource related issues, try to add optimization flag -O3 -DNDEBUG.
non-C++
- Require SWIG and CMake.
brew install SWIG. On Linux, sudo apt-get install -y swig.
* CMake: https://cmake.org/download/.
- Run the following commands.
mkdir binding/build
cd binding/build
rm -rf * (if rebuild from scratch)
cmake -DBUILDPYTHON=ON -DBUILDVERSION=1.0.0 .. (Use -DBUILDJAVA=ON if the target language is Java, -DBUILDCSHARP=ON if the target language is C#, -DBUILDGO=ON if the target language is Go, -DBUILDJAVASCRIPT=ON if the target language is Javascript)
cmake --build .
- The packaged build artifacts are located in the
binding/build/<language>/packaging/<BUILDVERSION>directory. SWIG generated raw files and build artifacts are located in thebinding/build/<language>/ccapibinding_<language>directory. - Python: If a virtual environment (managed by
venvorconda) is active (i.e. theactivatescript has been evaluated), the package will be installed into the virtual environment rather than globally. - C#: The shared library is built using the .NET framework.
- Troubleshoot:
cmake -DOPENSSLROOTDIR=.... On macOS, you might be missing headers for OpenSSL, brew install openssl and cmake -DOPENSSLROOTDIR=/usr/local/opt/openssl. On Ubuntu, sudo apt-get install libssl-dev. On Windows, vcpkg install openssl:x64-windows and cmake -DOPENSSLROOTDIR=C:/vcpkg/installed/x64-windows-static.
* Python:
* "CMake Error at python/CMakeLists.txt:... (message): Require Python 3". Try to create and activate a virtual environment (managed by venv or conda) with Python 3.
* "‘PyObjectGC_UNTRACK’ was not declared in this scope". If you use Python >= 3.8, please use SWIG >= 4.0.
* Java:
* "Could NOT find JNI (missing: JAVAINCLUDEPATH JAVAINCLUDEPATH2 JAVAAWTINCLUDEPATH)". Check that the environment variable JAVAHOME is correct.
*
* "Check for node-gyp Program: not found". You can install node-gyp using npm: npm install -g node-gyp
Constants
include/ccapicpp/ccapi_macro.h
Examples
C++- Require CMake.
- Run the following commands.
mkdir example/build
cd example/build
rm -rf * (if rebuild from scratch)
cmake ..
cmake --build . --target <example-name>
- The executable is
example/build/src/<example-name>/<example-name>. Run it.
- Python API is nearly identical to C++ API and covers nearly all the functionalities from C++ API.
- Build and install the Python binding as shown above.
- Inside a concrete example directory (e.g. binding/python/example/marketdatasimple_subscription), run
python3 main.py
- Troubleshoot:
- Java API is nearly identical to C++ API and covers nearly all the functionalities from C++ API.
- Build and install the Java binding as shown above.
- Inside a concrete example directory (e.g. binding/python/example/marketdatasimple_subscription), run
mkdir build cd build rm -rf * (if rebuild from scratch) javac -cp ../../../../build/java/packaging/1.0.0/ccapi-1.0.0.jar -d . ../Main.java java -cp .:../../../../build/java/packaging/1.0.0/ccapi-1.0.0.jar -Djava.library.path=../../../../build/java/packaging/1.0.0 Main - Troubleshoot:
javac's classpath includes binding/build/java/packaging/1.0.0/ccapi-1.0.0.jar. * "Exception in thread "main" java.lang.UnsatisfiedLinkError: no ccapibindingjava in java.library.path: ...". Check that java's java.library.path property includes binding/build/java/packaging/1.0.0. See https://stackoverflow.com/questions/1403788/java-lang-unsatisfiedlinkerror-no-dll-in-java-library-path.
- C# API is nearly identical to C++ API and covers nearly all the functionalities from C++ API.
- Build and install the C# binding as shown above.
- Inside a concrete example directory (e.g. binding/csharp/example/marketdatasimple_subscription), run
dotnet clean (if rebuild from scratch) env LDLIBRARYPATH="$LDLIBRARYPATH:../../../build/csharp/packaging/1.0.0" dotnet run --property:CcapiLibraryPath=../../../build/csharp/packaging/1.0.0/ccapi.dll -c Release - Troubleshoot:
LDLIBRARYPATH includes binding/build/csharp/packaging/1.0.0.
- Go API is nearly identical to C++ API and covers nearly all the functionalities from C++ API.
- Build and install the Go binding as shown above.
- Inside a concrete example directory (e.g. binding/go/example/marketdatasimple_subscription), run
go clean (if rebuild from scratch) source ../../../build/go/packaging/1.0.0/exportcompileroptions.sh (this step is important) go build . ./main - Troubleshoot:
- Javascript API is nearly identical to C++ API and covers nearly all the functionalities from C++ API.
- Build and install the Javascript binding as shown above.
- Inside a concrete example directory (e.g. binding/javascript/example/marketdatasimple_subscription), run
rm -rf node_modules (if rebuild from scratch) npm install node index.js
Documentations
Simple Market Data
Objective 1:
For a specific exchange and instrument, get recents trades.
Code 1:
C++ / Python / Java / C# / Go / Javascript
#include "ccapicpp/ccapisession.h"
namespace ccapi {
Logger* Logger::logger = nullptr; // This line is needed.
class MyEventHandler : public EventHandler { public: void processEvent(const Event& event, Session* sessionPtr) override { std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl; } };
} / namespace ccapi /
using ::ccapi::MyEventHandler; using ::ccapi::Request; using ::ccapi::Session; using ::ccapi::SessionConfigs; using ::ccapi::SessionOptions;
int main(int argc, char** argv) { SessionOptions sessionOptions; SessionConfigs sessionConfigs; MyEventHandler eventHandler; Session session(sessionOptions, sessionConfigs, &eventHandler); Request request(Request::Operation::GETRECENTTRADES, "bybit", "BTCUSDT"); request.appendParam({ {"LIMIT", "1"}, }); session.sendRequest(request); std::thisthread::sleepfor(std::chrono::seconds(10)); session.stop(); std::cout << "Bye" << std::endl; return EXIT_SUCCESS; }
Output 1:
Received an event: Event [ type = RESPONSE, messageList = [ Message [ type = GETRECENTTRADES, recapType = UNKNOWN, time = 2021-05-25T03:23:31.124000000Z, timeReceived = 2021-05-25T03:23:31.239734000Z, elementList = [ Element [ nameValueMap = { ISBUYERMAKER = 1, LAST_PRICE = 38270.71, LAST_SIZE = 0.001, TRADE_ID = 178766798 } ] ], correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ] ] ] ] Bye - Request operation types:
GETSERVERTIME,GETINSTRUMENT,GETINSTRUMENTS,GETBBOS,GETRECENTTRADES,GETHISTORICALTRADES,GETRECENTCANDLESTICKS,GETHISTORICALCANDLESTICKS,GETRECENTAGGTRADES,GETHISTORICALAGG_TRADES(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list), `. - Request parameter names: LIMIT
,INSTRUMENTTYPE,CANDLESTICKINTERVALSECONDS,STARTTIMESECONDS,ENDTIMESECONDS,STARTTRADEID,ENDTRADEID,STARTAGGTRADEID,ENDAGGTRADEID. Instead of these convenient names you can also choose to use arbitrary parameter names and they will be passed to the exchange's native API. See this example. - Message's time
represents the exchange's reported timestamp. ItstimeReceivedrepresents the library's receiving timestamp.timecan be retrieved bygetTimemethod andtimeReceivedcan be retrieved bygetTimeReceivedmethod. (For non-C++, please usegetTimeUnixandgetTimeReceivedUnixmethods orgetTimeISOandgetTimeReceivedISOmethods).
For a specific exchange and instrument, whenever the best bid's or ask's price or size changes, print the market depth snapshot at that moment.
Code 2:
C++ / Python / Java / C# / Go / Javascript <pre><code class="lang-">#include "ccapicpp/ccapisession.h"
namespace ccapi {
Logger* Logger::logger = nullptr; // This line is needed.
class MyEventHandler : public EventHandler { public: void processEvent(const Event& event, Session* sessionPtr) override { if (event.getType() == Event::Type::SUBSCRIPTION_STATUS) { std::cout << "Received an event of type SUBSCRIPTION_STATUS:\n" + event.toPrettyString(2, 2) << std::endl; } else if (event.getType() == Event::Type::SUBSCRIPTION_DATA) { for (const auto& message : event.getMessageList()) { std::cout << std::string("Best bid and ask at ") + UtilTime::getISOTimestamp(message.getTime()) + " are:" << std::endl; for (const auto& element : message.getElementList()) { // They key std::string_view is created from a string literal and therefore is safe, because string // literals have static storage duration, meaning they live for the entire duration of the program. const std::map<std::string_view, std::string>& elementNameValueMap = element.getNameValueMap(); std::cout << " " + toString(elementNameValueMap) << std::endl; } } } } };
} / namespace ccapi /
using ::ccapi::MyEventHandler; using ::ccapi::Session; using ::ccapi::SessionConfigs; using ::ccapi::SessionOptions; using ::ccapi::Subscription; using ::ccapi::toString;
int main(int argc, char** argv) { SessionOptions sessionOptions; SessionConfigs sessionConfigs; MyEventHandler eventHandler; Session session(sessionOptions, sessionConfigs, &eventHandler); Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH"); session.subscribe(subscription); std::thisthread::sleepfor(std::chrono::seconds(10)); session.stop(); std::cout << "Bye" << std::endl; return EXIT_SUCCESS; }</code></pre>
Output 2: <pre><code class="lang-console">Best bid and ask at 2020-07-27T23:56:51.884855000Z are: {BIDPRICE=10995, BIDSIZE=0.22187803} {ASKPRICE=10995.44, ASKSIZE=2} Best bid and ask at 2020-07-27T23:56:51.935993000Z are: ...</code></pre>
- Subscription fields: MARKETDEPTH
,TRADE,CANDLESTICK,AGGTRADE(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#aggregate-trade-streams).
Advanced Market Data
Complex request parameters
Please follow the exchange's API documentations: e.g. https://bybit-exchange.github.io/docs/v5/market/instrument. <pre><code class="lang-">Request request(Request::Operation::GET_INSTRUMENTS, "bybit"); request.appendParam({ {"category", "linear"}, {"limit", "1000"}, });</code></pre>Specify subscription market depth
Instantiate Subscription with option MARKETDEPTHMAX set to be the desired market depth (e.g. you want to receive market depth snapshot whenever the top 10 bid's or ask's price or size changes). <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKETDEPTH", "MARKETDEPTH_MAX=10");</code></pre>
Specify correlation id
Instantiate Request with the desired correlationId. The correlationId should be unique. <pre><code class="lang-">Request request(Request::Operation::GETRECENTTRADES, "bybit", "BTCUSDT", "cool correlation id");</code></pre> Instantiate Subscription with the desired correlationId. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "", "cool correlation id");</code></pre> This is used to match a particular request or subscription with its returned data. Within each Message there is a correlationIdList to identify the request or subscription that requested the data.
Multiple exchanges and/or instruments
Send a std::vector. <pre><code class="lang-">Request request1(Request::Operation::GETRECENT_TRADES, "bybit", "BTCUSDT", "cool correlation id for BTC"); request_1.appendParam(...); Request request2(Request::Operation::GETRECENT_TRADES, "binance", "ETH-USDT", "cool correlation id for ETH"); request_2.appendParam(...); std::vector<ccapi::Request> requests = {request1, request2}; session.sendRequest(requests);</code></pre> Subscribe a std::vector. <pre><code class="lang-">Subscription subscription1("bybit", "BTCUSDT", "MARKETDEPTH", "", "cool correlation id for bybit BTCUSDT"); Subscription subscription2("binance", "ETHUSDT", "MARKETDEPTH", "", "cool correlation id for binance ETHUSDT"); std::vector<ccapi::Subscription> subscriptions = {subscription1, subscription2}; session.subscribe(subscriptions);</code></pre>
Receive subscription events at periodic intervals
Instantiate Subscription with option CONFLATEINTERVALMILLISECONDS set to be the desired interval. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKETDEPTH", "CONFLATEINTERVAL_MILLISECONDS=1000");</code></pre>
Receive subscription events at periodic intervals including when the market depth snapshot hasn't changed
Instantiate Subscription with option CONFLATEINTERVALMILLISECONDS set to be the desired interval and CONFLATEGRACEPERIOD_MILLISECONDS to be the grace period for late events. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKETDEPTH", "CONFLATEINTERVALMILLISECONDS=1000&CONFLATEGRACEPERIODMILLISECONDS=0");</code></pre>
Receive subscription market depth updates
Instantiate Subscription with option MARKETDEPTHRETURN_UPDATE set to 1. This will return the order book updates instead of snapshots. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKETDEPTH", "MARKETDEPTHRETURNUPDATE=1&MARKETDEPTHMAX=2");</code></pre>
Receive subscription trade events
Instantiate Subscription with field TRADE. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "TRADE");</code></pre>
Receive subscription calculated-candlestick events at periodic intervals
Instantiate Subscription with field TRADE and option CONFLATEINTERVALMILLISECONDS set to be the desired interval and CONFLATEGRACEPERIOD_MILLISECONDS to be your network latency. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "TRADE", "CONFLATEINTERVALMILLISECONDS=5000&CONFLATEGRACEPERIOD_MILLISECONDS=0");</code></pre>
Receive subscription exchange-provided-candlestick events at periodic intervals
Instantiate Subscription with field CANDLESTICK and option CANDLESTICKINTERVALSECONDS set to be the desired interval. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "CANDLESTICK", "CANDLESTICKINTERVALSECONDS=60");</code></pre>
Send generic public requests
Instantiate Request with operation GENERICPUBLICREQUEST. Provide request parameters HTTPMETHOD, HTTPPATH, and optionally HTTPQUERYSTRING (query string parameter values should be url-encoded), HTTP_BODY. <pre><code class="lang-">Request request(Request::Operation::GENERICPUBLICREQUEST, "bybit", "", "Check Server Time"); request.appendParam({ {"HTTP_METHOD", "GET"}, {"HTTP_PATH", "/api/v5/public/time"}, });</code></pre>
Make generic public subscriptions
Instantiate Subscription with empty instrument, field GENERICPUBLICSUBSCRIPTION and options set to be the desired websocket payload. <pre><code class="lang-">Subscription subscription("bybit", "", "GENERICPUBLICSUBSCRIPTION", R"({"type":"subscribe","channels":[{"name":"status"}]})");</code></pre>
Send generic private requests
Instantiate Request with operation GENERICPRIVATEREQUEST. Provide request parameters HTTPMETHOD, HTTPPATH, and optionally HTTPQUERYSTRING (query string parameter values should be url-encoded), HTTP_BODY. <pre><code class="lang-">Request request(Request::Operation::GENERICPRIVATEREQUEST, "bybit", "", "close all positions"); request.appendParam({ {"HTTP_METHOD", "POST"}, {"HTTP_PATH", "/api/v5/trade/close-position"}, {"HTTP_BODY", R"({ "instId": "BTC-USDT-SWAP", "mgnMode": "cross" })"}, });</code></pre>
Simple Execution Management
Objective 1:
For a specific exchange and instrument, submit a simple limit order.
Code 1:
C++ / Python / Java / C# / Go / Javascript <pre><code class="lang-">#include "ccapicpp/ccapisession.h"
namespace ccapi {
Logger* Logger::logger = nullptr; // This line is needed.
class MyEventHandler : public EventHandler { public: void processEvent(const Event& event, Session* sessionPtr) override { std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl; } };
} / namespace ccapi /
using ::ccapi::MyEventHandler; using ::ccapi::Request; using ::ccapi::Session; using ::ccapi::SessionConfigs; using ::ccapi::SessionOptions; using ::ccapi::toString; using ::ccapi::UtilSystem;
int main(int argc, char** argv) { if (UtilSystem::getEnvAsString("BYBITAPIKEY").empty()) { std::cerr << "Please set environment variable BYBITAPIKEY" << std::endl; return EXIT_FAILURE; } if (UtilSystem::getEnvAsString("BYBITAPISECRET").empty()) { std::cerr << "Please set environment variable BYBITAPISECRET" << std::endl; return EXIT_FAILURE; }
SessionOptions sessionOptions; SessionConfigs sessionConfigs; MyEventHandler eventHandler; Session session(sessionOptions, sessionConfigs, &eventHandler); Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT"); request.appendParam({ {"SIDE", "BUY"}, {"QUANTITY", "0.0005"}, {"LIMIT_PRICE", "100000"}, }); session.sendRequest(request); std::thisthread::sleepfor(std::chrono::seconds(10)); session.stop(); std::cout << "Bye" << std::endl; return EXIT_SUCCESS; }</code></pre>
Output 1: <pre><code class="lang-console">Received an event: Event [ type = RESPONSE, messageList = [ Message [ type = CREATE_ORDER, recapType = UNKNOWN, time = 1970-01-01T00:00:00.000000000Z, timeReceived = 2021-05-25T03:47:15.599562000Z, elementList = [ Element [ nameValueMap = { CLIENTORDERID = wBgmzOJbbMTCLJlwTrIeiH, CUMULATIVEFILLEDPRICETIMESQUANTITY = 0, CUMULATIVEFILLEDQUANTITY = 0, INSTRUMENT = BTC-USDT, LIMIT_PRICE = 100000, ORDER_ID = 383781246, QUANTITY = 0.0005, SIDE = BUY, STATUS = live } ] ], correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ] ] ] ] Bye</code></pre>
- Request operation types: CREATEORDER
,CANCELORDER,GETORDER,GETOPENORDERS,CANCELOPENORDERS,GETACCOUNTS,GETACCOUNTBALANCES,GETACCOUNTPOSITIONS. - Request parameter names: SIDE
,QUANTITY,LIMITPRICE,ACCOUNTID,ACCOUNTTYPE,ORDERID,CLIENTORDERID,PARTYID,ORDERTYPE,LEVERAGE. Instead of these convenient names you can also choose to use arbitrary parameter names and they will be passed to the exchange's native API. See this example.
For a specific exchange and instrument, receive order updates.
Code 2:
C++ / Python / Java / C# / Go / Javascript <pre><code class="lang-">#include "ccapicpp/ccapisession.h"
namespace ccapi {
Logger* Logger::logger = nullptr; // This line is needed.
class MyEventHandler : public EventHandler { public: void processEvent(const Event& event, Session* sessionPtr) override { if (event.getType() == Event::Type::SUBSCRIPTION_STATUS) { std::cout << "Received an event of type SUBSCRIPTION_STATUS:\n" + event.toPrettyString(2, 2) << std::endl; auto message = event.getMessageList().at(0); if (message.getType() == Message::Type::SUBSCRIPTION_STARTED) { Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT"); request.appendParam({ {"SIDE", "BUY"}, {"LIMIT_PRICE", "20000"}, {"QUANTITY", "0.001"}, {"CLIENTORDERID", request.generateNextClientOrderId()}, }); sessionPtr->sendRequest(request); } } else if (event.getType() == Event::Type::SUBSCRIPTION_DATA) { std::cout << "Received an event of type SUBSCRIPTION_DATA:\n" + event.toPrettyString(2, 2) << std::endl; } } };
} / namespace ccapi /
using ::ccapi::MyEventHandler; using ::ccapi::Request; using ::ccapi::Session; using ::ccapi::SessionConfigs; using ::ccapi::SessionOptions; using ::ccapi::Subscription; using ::ccapi::UtilSystem;
int main(int argc, char** argv) { if (UtilSystem::getEnvAsString("BYBITAPIKEY").empty()) { std::cerr << "Please set environment variable BYBITAPIKEY" << std::endl; return EXIT_FAILURE; } if (UtilSystem::getEnvAsString("BYBITAPISECRET").empty()) { std::cerr << "Please set environment variable BYBITAPISECRET" << std::endl; return EXIT_FAILURE; }
SessionOptions sessionOptions; SessionConfigs sessionConfigs; MyEventHandler eventHandler; Session session(sessionOptions, sessionConfigs, &eventHandler); Subscription subscription("bybit", "BTCUSDT", "ORDER_UPDATE"); session.subscribe(subscription); std::thisthread::sleepfor(std::chrono::seconds(10)); session.stop(); std::cout << "Bye" << std::endl; return EXIT_SUCCESS; }</code></pre>
Output 2: <pre><code class="lang-console">Received an event of type SUBSCRIPTION_STATUS: Event [ type = SUBSCRIPTION_STATUS, messageList = [ Message [ type = SUBSCRIPTION_STARTED, recapType = UNKNOWN, time = 1970-01-01T00:00:00.000000000Z, timeReceived = 2021-05-25T04:22:25.906197000Z, elementList = [
], correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ] ] ] ] Received an event of type SUBSCRIPTION_DATA: Event [ type = SUBSCRIPTION_DATA, messageList = [ Message [ type = EXECUTIONMANAGEMENTEVENTSORDERUPDATE, recapType = UNKNOWN, time = 2021-05-25T04:22:26.653785000Z, timeReceived = 2021-05-25T04:22:26.407419000Z, elementList = [ Element [ nameValueMap = { CLIENTORDERID = , INSTRUMENT = BTC-USDT, LIMIT_PRICE = 20000, ORDER_ID = 6ca39186-be79-4777-97ab-1695fccd0ce4, QUANTITY = 0.001, SIDE = BUY, STATUS = live } ] ], correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ] ] ] ] Bye</code></pre>
- Subscription fields: ORDERUPDATE
,PRIVATETRADE,PRIVATETRADELITE,BALANCEUPDATE,POSITIONUPDATE.
Advanced Execution Management
Specify correlation id
Instantiate Request with the desired correlationId. The correlationId should be unique. <pre><code class="lang-">Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT", "cool correlation id");</code></pre> Instantiate Subscription with the desired correlationId. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "ORDER_UPDATE", "", "cool correlation id");</code></pre> This is used to match a particular request or subscription with its returned data. Within each Message there is a correlationIdList to identify the request or subscription that requested the data.
Multiple exchanges and/or instruments
Send a std::vector. <pre><code class="lang-">Request request1(Request::Operation::CREATEORDER, "bybit", "BTCUSDT", "cool correlation id for BTC"); request_1.appendParam(...); Request request2(Request::Operation::CREATEORDER, "bybit", "ETH-USDT", "cool correlation id for ETH"); request_2.appendParam(...); std::vector<ccapi::Request> requests = {request1, request2}; session.sendRequest(requests);</code></pre> Subscribe one Subscription per exchange with a comma separated string of instruments. <pre><code class="lang-">Subscription subscription("bybit", "BTC-USDT,ETH-USDT", "ORDER_UPDATE");</code></pre>
Multiple subscription fields
Subscribe one Subscription with a comma separated string of fields. <pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "ORDERUPDATE,PRIVATETRADE");</code></pre>
Make Session::sendRequest blocking
Instantiate Session without EventHandler argument, and pass a pointer to Queue as an additional argument.
<pre><code class="lang-">Session session(sessionOptions, sessionConfigs);
...
Queue<Event> eventQueue;
session.sendRequest(request, &eventQueue); // block until a response is received
std::vector<Event> eventList = eventQueue.purge();</code></pre>
Provide API credentials for an exchange
There are 3 ways to provide API credentials (listed with increasing priority).
- Set the relevent environment variables. Some exchanges might need additional credentials other than API keys and secrets: e.g.
OKXAPIPASSPHRASE, KUCOINAPIPASSPHRASE, BINANCEUSDSFUTURESWEBSOCKETORDERENTRYAPIKEY, BINANCEUSDSFUTURESWEBSOCKETORDERENTRYAPIPRIVATEKEYPATH, BINANCECOINFUTURESWEBSOCKETORDERENTRYAPIKEY, BINANCECOINFUTURESWEBSOCKETORDERENTRYAPIPRIVATEKEYPATH, BINANCEWEBSOCKETORDERENTRYAPIKEY, BINANCEWEBSOCKETORDERENTRYAPIPRIVATEKEYPATH. See section "exchange API credentials" in include/ccapicpp/ccapi_macro.h.- Provide credentials to Request
orSubscription.
Complex request parameters
Please follow the exchange's API documentations: e.g. https://bybit-exchange.github.io/docs/v5/order/create-order. <pre><code class="lang-">Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT"); request.appendParam({ {"isLeverage", "1"}, });</code></pre>Send request by Websocket API
For okx, cryptocom: <pre><code class="lang-">std::string websocketOrderEntrySubscriptionCorrelationId("any"); Subscription subscription("bybit", "", "ORDER_UPDATE", "", websocketOrderEntrySubscriptionCorrelationId); session.subscribe(subscription); ... Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT"); request.appendParam({ {"SIDE", "BUY"}, {"LIMIT_PRICE", "20000"}, {"QUANTITY", "0.001"}, }); session.sendRequestByWebsocket(websocketOrderEntrySubscriptionCorrelationId, request);</code></pre> For bybit, binance, binance-usds-futures, binance-coin-futures: <pre><code class="lang-">std::string websocketOrderEntrySubscriptionCorrelationId("any"); Subscription subscription1("bybit", "", "ORDERUPDATE"); Subscription subscription2("bybit", "", "WEBSOCKETORDER_ENTRY", "", websocketOrderEntrySubscriptionCorrelationId); std::vector<ccapi::Subscription> subscriptions = {subscription1, subscription2}; session.subscribe(subscriptions); ... Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT"); request.appendParam({ {"SIDE", "BUY"}, {"LIMIT_PRICE", "20000"}, {"QUANTITY", "0.001"}, }); session.sendRequestByWebsocket(websocketOrderEntrySubscriptionCorrelationId, request);</code></pre>Specify instrument type
Some exchanges (i.e. bybit) might need instrument type for Subscription. Use Subscription's setInstrumentType method.
<pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH");
subscription.setInstrumentType("spot");
session.subscribe(subscription);</code></pre>
FIX API
Objective:
For a specific exchange and instrument, submit a simple limit order.
Code:
C++ / Python / Java / C# / Go / Javascript <pre><code class="lang-">#include "ccapicpp/ccapisession.h"
namespace ccapi {
Logger* Logger::logger = nullptr; // This line is needed.
class MyEventHandler : public EventHandler { public: MyEventHandler(const std::string& fixSubscriptionCorrelationId) : fixSubscriptionCorrelationId(fixSubscriptionCorrelationId) {}
void processEvent(const Event& event, Session* sessionPtr) override { std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl; if (!willSendRequest) { sessionPtr->setTimer("id", 1000, nullptr, [this, sessionPtr]() { Request request(Request::Operation::FIX, "binance"); request.appendFixParam({ {35, "D"}, {11, request.generateNextClientOrderId()}, {55, "BTCUSDT"}, {54, "1"}, {44, "100000"}, {38, "0.0001"}, {40, "2"}, {59, "1"}, }); sessionPtr->sendRequestByFix(this->fixSubscriptionCorrelationId, request); }); willSendRequest = true; } }
private: std::string fixSubscriptionCorrelationId; bool willSendRequest{}; };
} / namespace ccapi /
using ::ccapi::MyEventHandler; using ::ccapi::Session; using ::ccapi::SessionConfigs; using ::ccapi::SessionOptions; using ::ccapi::Subscription; using ::ccapi::UtilSystem;
int main(int argc, char** argv) { if (UtilSystem::getEnvAsString("BINANCEFIXAPI_KEY").empty()) { std::cerr << "Please set environment variable BINANCEFIXAPI_KEY" << std::endl; return EXIT_FAILURE; } if (UtilSystem::getEnvAsString("BINANCEFIXAPIPRIVATEKEY_PATH").empty()) { std::cerr << "Please set environment variable BINANCEFIXAPIPRIVATEKEY_PATH" << std::endl; return EXIT_FAILURE; } SessionOptions sessionOptions; SessionConfigs sessionConfigs; std::string fixSubscriptionCorrelationId("any"); MyEventHandler eventHandler(fixSubscriptionCorrelationId); Session session(sessionOptions, sessionConfigs, &eventHandler); Subscription subscription("binance", "", "FIX", "", fixSubscriptionCorrelationId); session.subscribe(subscription); std::thisthread::sleepfor(std::chrono::seconds(10)); session.stop(); std::cout << "Bye" << std::endl; return EXIT_SUCCESS; }</code></pre> Output: <pre><code class="lang-console">Received an event: Event [ type = SESSION_STATUS, messageList = [ Message [ type = SESSIONCONNECTIONUP, recapType = UNKNOWN, time = 1970-01-01T00:00:00.000000000Z, timeReceived = 2025-08-08T18:50:06.816550779Z, elementList = [ Element [ tagValueList = [
], nameValueMap = { CONNECTION_ID = IF8j4HbdLP0, CONNECTION_URL = tcp+tls://fix-oe.binance.com:9000 } ] ], correlationIdList = [ any ], ] ] ] Received an event: Event [ type = AUTHORIZATION_STATUS, messageList = [ Message [ type = AUTHORIZATION_SUCCESS, recapType = UNKNOWN, time = 1970-01-01T00:00:00.000000000Z, timeReceived = 2025-08-08T18:50:06.819417404Z, elementList = [ Element [ tagValueList = [ (35, "A"), (98, "0"), (108, "60"), (25037, "e9ed8253-8f49-4a1b-bba9-718ff73991e5") ], nameValueMap = {
} ] ], correlationIdList = [ any ], ] ] ] Received an event: Event [ type = FIX, messageList = [ Message [ type = FIX, recapType = UNKNOWN, time = 1970-01-01T00:00:00.000000000Z, timeReceived = 2025-08-08T18:50:07.820112736Z, elementList = [ Element [ tagValueList = [ (35, "8"), (17, "100146443390"), (11, "x-XHKUG2CH-1754679007000"), (37, "47156106695"), (38, "0.00010000"), (40, "2"), (54, "1"), (55, "BTCUSDT"), (44, "100000.00000000"), (59, "1"), (60, "20250808-18:50:07.819027"), (25018, "20250808-18:50:07.819027"), (25001, "3"), (150, "0"), (14, "0.00000000"), (151, "0.00010000"), (25017, "0.00000000"), (1057, "Y"), (32, "0.00000000"), (39, "0"), (636, "Y"), (25023, "20250808-18:50:07.819027") ], nameValueMap = {
} ] ], correlationIdList = [ any ], ] ] ] Bye</code></pre>
More Advanced Topics
Handle events in "immediate" vs. "batching" mode
In general there are 2 ways to handle events.
- When a
Session is instantiated with an eventHandler argument, it will handle events in immediate mode. The processEvent method in the eventHandler will be invoked immediately when an Event is available, and the invocation will run on the thread where boost::asio::iocontext runs. When a Session is instantiated with an eventHandler and an eventDispatcher argument, it will also handle events in immediate mode. The processEvent method in the eventHandler will also be invoked immediately when an Event is available, but the invocation will run in the thread(s) provided by the eventDispatcher therefore not blocking the thread where boost::asio::iocontext runs. EventHandlers and/or EventDispatchers can be shared among different sessions. Otherwise, different sessions are independent from each other. An example can be found here. - When a
Session is instantiated without an eventHandler argument, it will handle events in batching mode. The events will be batched into an internal Queue and can be retrieved by <pre><code class="lang-">std::vector<Event> eventList = session.getEventQueue().purge();</code></pre> An example can be found here.
Thread safety
- The following methods are implemented to be thread-safe:
Session::sendRequest, Session::subscribe, Session::sendRequestByFix, Session::setTimer, all public methods in Queue.
If you choose to inject an external boost::asio::iocontext to ServiceContext, the boost::asio::iocontext has to run on a single thread to ensure thread safety.
Enable library logging
Extend a subclass, e.g.
MyLogger, from class Logger and override method logMessage. Assign a MyLogger pointer to Logger::logger. Add one of the following macros in the compiler command line: CCAPIENABLELOGTRACE, CCAPIENABLELOGDEBUG, CCAPIENABLELOGINFO, CCAPIENABLELOGWARN, CCAPIENABLELOGERROR, CCAPIENABLELOGFATAL. Enable logging if you'd like to inspect raw responses/messages from the exchange for troubleshooting purposes. <pre><code class="lang-">namespace ccapi {
class MyLogger final : public Logger { public: void logMessage(const std::string& severity, const std::string& threadId, const std::string& timeISO, const std::string& fileName, const std::string& lineNumber, const std::string& message) override { std::lock_guard<std::mutex> lock(m); std::cout << threadId << ": [" << timeISO << "] {" << fileName << ":" << lineNumber << "} " << severity << std::string(8, ' ') << message << std::endl; }
private: std::mutex m; };
MyLogger myLogger; Logger* Logger::logger = &myLogger;
} / namespace ccapi /</code></pre>
Set timer
To perform an asynchronous wait, use the utility method
setTimer in class Session. The handlers are invoked in the same threads as the processEvent method in the EventHandler class. The id of the timer should be unique. delayMilliseconds can be 0. <pre><code class="lang-">sessionPtr->setTimer( "id", 1000, [](const boost::system::error_code&) { std::cout << std::string("Timer error handler is triggered at ") + UtilTime::getISOTimestamp(UtilTime::now()) << std::endl; }, []() { std::cout << std::string("Timer success handler is triggered at ") + UtilTime::getISOTimestamp(UtilTime::now()) << std::endl; });</code></pre>
Heartbeat
To receive heartbeat events, instantiate a
Subscription object with field HEARTBEAT and subscribe it. <pre><code class="lang-">Subscription subscription("", "", "HEARTBEAT", "HEARTBEATINTERVALMILLISECONDS=1000"); session.subscribe(subscription);</code></pre>
Use multiple sessions
Multiple
session instances, each with their own SessionOptions and SessionConfigs, can share a common EventHandler. If no EventDispatcher is provided, thread safety must be maintained by using a shared ServiceContext. <pre><code class="lang-">Subscription subscription("", "", "HEARTBEAT", "HEARTBEATINTERVALMILLISECONDS=1000"); session.subscribe(subscription);</code></pre>
Override exchange urls
You can override exchange urls at compile time by using macros. See section "exchange REST urls", "exchange WS urls", and "exchange FIX urls" in include/ccapicpp/ccapimacro.h. You can also override exchange urls at runtime. See this example. These can be useful if you need to connect to test accounts (e.g. https://testnet.bybit.com/).
Connect to a proxy
Instantiate Subscription with the desired proxyUrl.
<pre><code class="lang-">Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "", "", {}, "172.30.0.146:9000");</code></pre>
Reduce build time
The Pimpl (Pointer to Implementation) idiom in C++ can significantly reduce build time. This reduction is achieved by minimizing compilation dependencies and isolating implementation details. See this example.
Performance Tuning
- Turn on compiler optimization flags (e.g.
cmake -DCMAKEBUILDTYPE=Release ...).
Enable link time optimization (e.g. in CMakeLists.txt set(CMAKEINTERPROCEDURALOPTIMIZATION TRUE)` before a target is created). Note that link time optimization is only applicable to static linking.
Known Issues and Workarounds
- Kraken invalid nonce errors. Give the API key a nonce window (https://support.kraken.com/hc/en-us/articles/360001148023-What-is-a-nonce-window-). We use unix timestamp with microsecond resolution as nonce and therefore a nonce window of 500000 translates to a tolerance of 0.5 second.