An open API service indexing awesome lists of open source software.

https://github.com/crypto-chassis/ccapi

A header-only C++ library for interacting with crypto exchanges. Bindings for Python, Java, C#, Go, and Javascript are provided.
https://github.com/crypto-chassis/ccapi

algo-trading api arbitrage automated-trading bitcoin c-plus-plus crypto cryptocurrency csharp exchange execution fixprotocol go java javascript library market-data python strategy trading-bot

Last synced: 10 days ago
JSON representation

A header-only C++ library for interacting with crypto exchanges. Bindings for Python, Java, C#, Go, and Javascript are provided.

Awesome Lists containing this project

README

          

**Table of Contents** *generated with [DocToc](https://github.com/ktechhub/doctoc)*

- [ccapi](#ccapi)
- [Branches](#branches)
- [Build](#build)
- [C++](#c)
- [non-C++](#non-c)
- [Constants](#constants)
- [Examples](#examples)
- [Documentations](#documentations)
- [Simple Market Data](#simple-market-data)
- [Advanced Market Data](#advanced-market-data)
- [Complex request parameters](#complex-request-parameters)
- [Specify subscription market depth](#specify-subscription-market-depth)
- [Specify correlation id](#specify-correlation-id)
- [Multiple exchanges and/or instruments](#multiple-exchanges-andor-instruments)
- [Receive subscription events at periodic intervals](#receive-subscription-events-at-periodic-intervals)
- [Receive subscription events at periodic intervals including when the market depth snapshot hasn't changed](#receive-subscription-events-at-periodic-intervals-including-when-the-market-depth-snapshot-hasnt-changed)
- [Receive subscription market depth updates](#receive-subscription-market-depth-updates)
- [Receive subscription trade events](#receive-subscription-trade-events)
- [Receive subscription calculated-candlestick events at periodic intervals](#receive-subscription-calculated-candlestick-events-at-periodic-intervals)
- [Receive subscription exchange-provided-candlestick events at periodic intervals](#receive-subscription-exchange-provided-candlestick-events-at-periodic-intervals)
- [Send generic public requests](#send-generic-public-requests)
- [Make generic public subscriptions](#make-generic-public-subscriptions)
- [Send generic private requests](#send-generic-private-requests)
- [Simple Execution Management](#simple-execution-management)
- [Advanced Execution Management](#advanced-execution-management)
- [Specify correlation id](#specify-correlation-id-1)
- [Multiple exchanges and/or instruments](#multiple-exchanges-andor-instruments-1)
- [Multiple subscription fields](#multiple-subscription-fields)
- [Make Session::sendRequest blocking](#make-sessionsendrequest-blocking)
- [Provide API credentials for an exchange](#provide-api-credentials-for-an-exchange)
- [Complex request parameters](#complex-request-parameters-1)
- [Send request by Websocket API](#send-request-by-websocket-api)
- [Specify instrument type](#specify-instrument-type)
- [FIX API](#fix-api)
- [More Advanced Topics](#more-advanced-topics)
- [Handle events in "immediate" vs. "batching" mode](#handle-events-in-immediate-vs-batching-mode)
- [Thread safety](#thread-safety)
- [Enable library logging](#enable-library-logging)
- [Set timer](#set-timer)
- [Heartbeat](#heartbeat)
- [Use multiple sessions](#use-multiple-sessions)
- [Override exchange urls](#override-exchange-urls)
- [Connect to a proxy](#connect-to-a-proxy)
- [Reduce build time](#reduce-build-time)
- [Performance Tuning](#performance-tuning)
- [Known Issues and Workarounds](#known-issues-and-workarounds)

# ccapi
* 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:
* Market Data: ascendex, [binance](https://accounts.maxweb.academy/register?ref=1116718520), [binance-usds-futures](https://accounts.maxweb.academy/register?ref=1116718520), [binance-coin-futures](https://accounts.maxweb.academy/register?ref=1116718520), bitfinex, bitget, bitget-futures, bitmart, bitmex, bitstamp, [bybit](https://www.bybit.com/invite?ref=XNYP2K), coinbase, [cryptocom](https://crypto.com/exch/tqj4b8x48w), deribit, erisx (Cboe Digital), [gateio](https://www.gate.com/signup/VLUQXVFWAW?ref_type=103), [gateio-perpetual-futures](https://www.gate.com/signup/VLUQXVFWAW?ref_type=103), gemini, [huobi](https://www.htx.com/invite/en-us/1f?invite_code=rmw7d223), [huobi-usdt-swap](https://www.htx.com/invite/en-us/1f?invite_code=rmw7d223), [huobi-coin-swap](https://www.htx.com/invite/en-us/1f?invite_code=rmw7d223), kraken, kraken-futures, kucoin, kucoin-futures, mexc, mexc-futures, [okx](https://www.okx.com/join/47636709), whitebit.
* Execution Management: ascendex, [binance](https://accounts.maxweb.academy/register?ref=1116718520), [binance-usds-futures](https://accounts.maxweb.academy/register?ref=1116718520), [binance-coin-futures](https://accounts.maxweb.academy/register?ref=1116718520), bitfinex, bitget, bitget-futures, bitmart, bitmex, bitstamp, [bybit](https://www.bybit.com/invite?ref=XNYP2K), coinbase, [cryptocom](https://crypto.com/exch/tqj4b8x48w), deribit, erisx (Cboe Digital), [gateio](https://www.gate.com/signup/VLUQXVFWAW?ref_type=103), [gateio-perpetual-futures](https://www.gate.com/signup/VLUQXVFWAW?ref_type=103), gemini, [huobi](https://www.htx.com/invite/en-us/1f?invite_code=rmw7d223), [huobi-usdt-swap](https://www.htx.com/invite/en-us/1f?invite_code=rmw7d223), [huobi-coin-swap](https://www.htx.com/invite/en-us/1f?invite_code=rmw7d223), kraken, kraken-futures, kucoin, kucoin-futures, mexc, [okx](https://www.okx.com/join/47636709).
* FIX: [binance](https://accounts.maxweb.academy/register?ref=1116718520), coinbase, gemini.
* 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 `develop` branch may contain experimental features.
* The `master` branch 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:
* Define service enablement macro such as `CCAPI_ENABLE_SERVICE_MARKET_DATA`, `CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT`, `CCAPI_ENABLE_SERVICE_FIX`, etc. and exchange enablement macros such as `CCAPI_ENABLE_EXCHANGE_BYBIT`, etc. These macros can be found at the top of [`include/ccapi_cpp/ccapi_session.h`](include/ccapi_cpp/ccapi_session.h).
* Dependencies:
* boost https://archives.boost.io/release/1.87.0/source/boost_1_87_0.tar.gz (notice that its include directory is boost).
* rapidjson https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz (notice that its include directory is rapidjson/include).
* If you use FIX API, also need hffix https://github.com/jamesdbrock/hffix/archive/refs/tags/v1.4.1.tar.gz (notice that its include directory is hffix/include).
* Include directory for this library:
* include.
* Link libraries:
* OpenSSL: libssl.
* OpenSSL: libcrypto.
* If you need market data for huobi/huobi-usdt-swap/huobi-coin-swap or execution management for huobi-usdt-swap/huobi-coin-swap/bitmart, also link ZLIB.
* On Windows, also link ws2_32.
* Compiler flags:
* `-pthread` for GCC and MinGW.
* Tested platforms:
* macOS: Clang.
* Linux: GCC.
* Windows: MinGW.
* Troubleshoot:
* Try to remove all build artifacts and start from scratch (e.g. for cmake remove all the contents inside your build directory).
* "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_INCLUDE_DIR)". Try `cmake -DOPENSSL_ROOT_DIR=...`. On macOS, you might be missing headers for OpenSSL, `brew install openssl` and `cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl`. On Ubuntu, `sudo apt-get install libssl-dev`. On Windows, `vcpkg install openssl:x64-windows` and `cmake -DOPENSSL_ROOT_DIR=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.
* SWIG: On macOS, `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 -DBUILD_PYTHON=ON -DBUILD_VERSION=1.0.0 .. (Use -DBUILD_JAVA=ON if the target language is Java, -DBUILD_CSHARP=ON if the target language is C#, -DBUILD_GO=ON if the target language is Go, -DBUILD_JAVASCRIPT=ON if the target language is Javascript)
cmake --build .
```
* The packaged build artifacts are located in the `binding/build//packaging/` directory. SWIG generated raw files and build artifacts are located in the `binding/build//ccapi_binding_` directory.
* Python: If a virtual environment (managed by `venv` or `conda`) is active (i.e. the `activate` script 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:
* "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_INCLUDE_DIR)". Try `cmake -DOPENSSL_ROOT_DIR=...`. On macOS, you might be missing headers for OpenSSL, `brew install openssl` and `cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl`. On Ubuntu, `sudo apt-get install libssl-dev`. On Windows, `vcpkg install openssl:x64-windows` and `cmake -DOPENSSL_ROOT_DIR=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.
* "‘_PyObject_GC_UNTRACK’ was not declared in this scope". If you use Python >= 3.8, please use SWIG >= 4.0.
* Java:
* "Could NOT find JNI (missing: JAVA_INCLUDE_PATH JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH)". Check that the environment variable `JAVA_HOME` is correct.
* Javascript:
* "Check for node-gyp Program: not found". You can install node-gyp using npm: `npm install -g node-gyp`

## Constants
[`include/ccapi_cpp/ccapi_macro.h`](include/ccapi_cpp/ccapi_macro.h)

## Examples
[C++](example)
* Require CMake.
* CMake: https://cmake.org/download/.
* Run the following commands.
```
mkdir example/build
cd example/build
rm -rf * (if rebuild from scratch)
cmake ..
cmake --build . --target
```
* The executable is `example/build/src//`. Run it.

[Python](binding/python/example)
* 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](#non-c).
* Inside a concrete example directory (e.g. binding/python/example/market_data_simple_subscription), run
```
python3 main.py
```
* Troubleshoot:
* "Fatal Python error: Segmentation fault". If the macOS version is relatively new and the Python version is relatively old, please upgrade Python to a relatively new version.

[Java](binding/java/example)
* 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](#non-c).
* Inside a concrete example directory (e.g. binding/python/example/market_data_simple_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:
* "../Main.java:1: error: package com.cryptochassis.ccapi does not exist". Check that `javac`'s classpath includes `binding/build/java/packaging/1.0.0/ccapi-1.0.0.jar`.
* "Exception in thread "main" java.lang.UnsatisfiedLinkError: no ccapi_binding_java 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#](binding/csharp/example)
* 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](#non-c).
* Inside a concrete example directory (e.g. binding/csharp/example/market_data_simple_subscription), run
```
dotnet clean (if rebuild from scratch)
env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:../../../build/csharp/packaging/1.0.0" dotnet run --property:CcapiLibraryPath=../../../build/csharp/packaging/1.0.0/ccapi.dll -c Release
```
* Troubleshoot:
* "error CS0246: The type or namespace name 'ccapi' could not be found". Check that you aren't missing the ccapi assembly reference.
* "System.DllNotFoundException: Unable to load shared library 'ccapi_binding_csharp.so' or one of its dependencies.". Check that environment variable `LD_LIBRARY_PATH` includes `binding/build/csharp/packaging/1.0.0`.

[Go](binding/go/example)
* 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](#non-c).
* Inside a concrete example directory (e.g. binding/go/example/market_data_simple_subscription), run
```
go clean (if rebuild from scratch)
source ../../../build/go/packaging/1.0.0/export_compiler_options.sh (this step is important)
go build .
./main
```
* Troubleshoot:
* Some C/C++ header files not found. Check that you sourced the export_compiler_options.sh file which provides important environment variables needed by the cgo tool.

[Javascript](binding/javascript/example)
* 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](#non-c).
* Inside a concrete example directory (e.g. binding/javascript/example/market_data_simple_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++](example/src/market_data_simple_request/main.cpp) / [Python](binding/python/example/market_data_simple_request/main.py) / [Java](binding/java/example/market_data_simple_request/Main.java) / [C#](binding/csharp/example/market_data_simple_request/MainProgram.cs) / [Go](binding/go/example/market_data_simple_request/main.go) / [Javascript](binding/javascript/example/market_data_simple_request/index.js)
```
#include "ccapi_cpp/ccapi_session.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::GET_RECENT_TRADES, "bybit", "BTCUSDT");
request.appendParam({
{"LIMIT", "1"},
});
session.sendRequest(request);
std::this_thread::sleep_for(std::chrono::seconds(10));
session.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}

```

**Output 1:**
```console
Received an event:
Event [
type = RESPONSE,
messageList = [
Message [
type = GET_RECENT_TRADES,
recapType = UNKNOWN,
time = 2021-05-25T03:23:31.124000000Z,
timeReceived = 2021-05-25T03:23:31.239734000Z,
elementList = [
Element [
nameValueMap = {
IS_BUYER_MAKER = 1,
LAST_PRICE = 38270.71,
LAST_SIZE = 0.001,
TRADE_ID = 178766798
}
]
],
correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ]
]
]
]
Bye
```
* Request operation types: `GET_SERVER_TIME`, `GET_INSTRUMENT`, `GET_INSTRUMENTS`, `GET_BBOS`, `GET_RECENT_TRADES`, `GET_HISTORICAL_TRADES`, `GET_RECENT_CANDLESTICKS`, `GET_HISTORICAL_CANDLESTICKS`, `GET_RECENT_AGG_TRADES`, `GET_HISTORICAL_AGG_TRADES`(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list), ``.
* Request parameter names: `LIMIT`, `INSTRUMENT_TYPE`, `CANDLESTICK_INTERVAL_SECONDS`, `START_TIME_SECONDS`, `END_TIME_SECONDS`, `START_TRADE_ID`, `END_TRADE_ID`, `START_AGG_TRADE_ID`, `END_AGG_TRADE_ID`. 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](example/src/market_data_advanced_request/main.cpp).
* Message's `time` represents the exchange's reported timestamp. Its `timeReceived` represents the library's receiving timestamp. `time` can be retrieved by `getTime` method and `timeReceived` can be retrieved by `getTimeReceived` method. (For non-C++, please use `getTimeUnix` and `getTimeReceivedUnix` methods or `getTimeISO` and `getTimeReceivedISO` methods).

**Objective 2:**

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++](example/src/market_data_simple_subscription/main.cpp) / [Python](binding/python/example/market_data_simple_subscription/main.py) / [Java](binding/java/example/market_data_simple_subscription/Main.java) / [C#](binding/csharp/example/market_data_simple_subscription/MainProgram.cs) / [Go](binding/go/example/market_data_simple_subscription/main.go) / [Javascript](binding/javascript/example/market_data_simple_subscription/index.js)
```
#include "ccapi_cpp/ccapi_session.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& 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::this_thread::sleep_for(std::chrono::seconds(10));
session.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}

```

**Output 2:**
```console
Best bid and ask at 2020-07-27T23:56:51.884855000Z are:
{BID_PRICE=10995, BID_SIZE=0.22187803}
{ASK_PRICE=10995.44, ASK_SIZE=2}
Best bid and ask at 2020-07-27T23:56:51.935993000Z are:
...
```
* Subscription fields: `MARKET_DEPTH`, `TRADE`, `CANDLESTICK`, `AGG_TRADE`(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.
```
Request request(Request::Operation::GET_INSTRUMENTS, "bybit");
request.appendParam({
{"category", "linear"},
{"limit", "1000"},
});
```

#### Specify subscription market depth

Instantiate `Subscription` with option `MARKET_DEPTH_MAX` 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).
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "MARKET_DEPTH_MAX=10");
```

#### Specify correlation id

Instantiate `Request` with the desired correlationId. The `correlationId` should be unique.
```
Request request(Request::Operation::GET_RECENT_TRADES, "bybit", "BTCUSDT", "cool correlation id");
```
Instantiate `Subscription` with the desired correlationId.
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "", "cool correlation id");
```
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`.
```
Request request_1(Request::Operation::GET_RECENT_TRADES, "bybit", "BTCUSDT", "cool correlation id for BTC");
request_1.appendParam(...);
Request request_2(Request::Operation::GET_RECENT_TRADES, "binance", "ETH-USDT", "cool correlation id for ETH");
request_2.appendParam(...);
std::vector requests = {request_1, request_2};
session.sendRequest(requests);
```
Subscribe a `std::vector`.
```
Subscription subscription_1("bybit", "BTCUSDT", "MARKET_DEPTH", "", "cool correlation id for bybit BTCUSDT");
Subscription subscription_2("binance", "ETHUSDT", "MARKET_DEPTH", "", "cool correlation id for binance ETHUSDT");
std::vector subscriptions = {subscription_1, subscription_2};
session.subscribe(subscriptions);
```

#### Receive subscription events at periodic intervals

Instantiate `Subscription` with option `CONFLATE_INTERVAL_MILLISECONDS` set to be the desired interval.
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "CONFLATE_INTERVAL_MILLISECONDS=1000");
```

#### Receive subscription events at periodic intervals including when the market depth snapshot hasn't changed

Instantiate `Subscription` with option `CONFLATE_INTERVAL_MILLISECONDS` set to be the desired interval and `CONFLATE_GRACE_PERIOD_MILLISECONDS` to be the grace period for late events.
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "CONFLATE_INTERVAL_MILLISECONDS=1000&CONFLATE_GRACE_PERIOD_MILLISECONDS=0");
```

#### Receive subscription market depth updates

Instantiate `Subscription` with option `MARKET_DEPTH_RETURN_UPDATE` set to 1. This will return the order book updates instead of snapshots.
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "MARKET_DEPTH_RETURN_UPDATE=1&MARKET_DEPTH_MAX=2");
```

#### Receive subscription trade events

Instantiate `Subscription` with field `TRADE`.
```
Subscription subscription("bybit", "BTCUSDT", "TRADE");
```

#### Receive subscription calculated-candlestick events at periodic intervals

Instantiate `Subscription` with field `TRADE` and option `CONFLATE_INTERVAL_MILLISECONDS` set to be the desired interval and `CONFLATE_GRACE_PERIOD_MILLISECONDS` to be your network latency.
```
Subscription subscription("bybit", "BTCUSDT", "TRADE", "CONFLATE_INTERVAL_MILLISECONDS=5000&CONFLATE_GRACE_PERIOD_MILLISECONDS=0");
```

#### Receive subscription exchange-provided-candlestick events at periodic intervals

Instantiate `Subscription` with field `CANDLESTICK` and option `CANDLESTICK_INTERVAL_SECONDS` set to be the desired interval.
```
Subscription subscription("bybit", "BTCUSDT", "CANDLESTICK", "CANDLESTICK_INTERVAL_SECONDS=60");
```

#### Send generic public requests

Instantiate `Request` with operation `GENERIC_PUBLIC_REQUEST`. Provide request parameters `HTTP_METHOD`, `HTTP_PATH`, and optionally `HTTP_QUERY_STRING` (query string parameter values should be url-encoded), `HTTP_BODY`.
```
Request request(Request::Operation::GENERIC_PUBLIC_REQUEST, "bybit", "", "Check Server Time");
request.appendParam({
{"HTTP_METHOD", "GET"},
{"HTTP_PATH", "/api/v5/public/time"},
});
```

#### Make generic public subscriptions

Instantiate `Subscription` with empty instrument, field `GENERIC_PUBLIC_SUBSCRIPTION` and options set to be the desired websocket payload.
```
Subscription subscription("bybit", "", "GENERIC_PUBLIC_SUBSCRIPTION", R"({"type":"subscribe","channels":[{"name":"status"}]})");
```

#### Send generic private requests

Instantiate `Request` with operation `GENERIC_PRIVATE_REQUEST`. Provide request parameters `HTTP_METHOD`, `HTTP_PATH`, and optionally `HTTP_QUERY_STRING` (query string parameter values should be url-encoded), `HTTP_BODY`.
```
Request request(Request::Operation::GENERIC_PRIVATE_REQUEST, "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"
})"},
});
```

### Simple Execution Management

**Objective 1:**

For a specific exchange and instrument, submit a simple limit order.

**Code 1:**

[C++](example/src/execution_management_simple_request/main.cpp) / [Python](binding/python/example/execution_management_simple_request/main.py) / [Java](binding/java/example/execution_management_simple_request/Main.java) / [C#](binding/csharp/example/execution_management_simple_request/MainProgram.cs) / [Go](binding/go/example/execution_management_simple_request/main.go) / [Javascript](binding/javascript/example/execution_management_simple_request/index.js)
```
#include "ccapi_cpp/ccapi_session.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("BYBIT_API_KEY").empty()) {
std::cerr << "Please set environment variable BYBIT_API_KEY" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("BYBIT_API_SECRET").empty()) {
std::cerr << "Please set environment variable BYBIT_API_SECRET" << 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::this_thread::sleep_for(std::chrono::seconds(10));
session.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}

```

**Output 1:**
```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 = {
CLIENT_ORDER_ID = wBgmzOJbbMTCLJlwTrIeiH,
CUMULATIVE_FILLED_PRICE_TIMES_QUANTITY = 0,
CUMULATIVE_FILLED_QUANTITY = 0,
INSTRUMENT = BTC-USDT,
LIMIT_PRICE = 100000,
ORDER_ID = 383781246,
QUANTITY = 0.0005,
SIDE = BUY,
STATUS = live
}
]
],
correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ]
]
]
]
Bye
```
* Request operation types: `CREATE_ORDER`, `CANCEL_ORDER`, `GET_ORDER`, `GET_OPEN_ORDERS`, `CANCEL_OPEN_ORDERS`, `GET_ACCOUNTS`, `GET_ACCOUNT_BALANCES`, `GET_ACCOUNT_POSITIONS`.
* Request parameter names: `SIDE`, `QUANTITY`, `LIMIT_PRICE`, `ACCOUNT_ID`, `ACCOUNT_TYPE`, `ORDER_ID`, `CLIENT_ORDER_ID`, `PARTY_ID`, `ORDER_TYPE`, `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](example/src/execution_management_advanced_request/main.cpp).

**Objective 2:**

For a specific exchange and instrument, receive order updates.

**Code 2:**

[C++](example/src/execution_management_simple_subscription/main.cpp) / [Python](binding/python/example/execution_management_simple_subscription/main.py) / [Java](binding/java/example/execution_management_simple_subscription/Main.java) / [C#](binding/csharp/example/execution_management_simple_subscription/MainProgram.cs) / [Go](binding/go/example/execution_management_simple_subscription/main.go) / [Javascript](binding/javascript/example/execution_management_simple_subscription/index.js)
```
#include "ccapi_cpp/ccapi_session.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"},
{"CLIENT_ORDER_ID", 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("BYBIT_API_KEY").empty()) {
std::cerr << "Please set environment variable BYBIT_API_KEY" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("BYBIT_API_SECRET").empty()) {
std::cerr << "Please set environment variable BYBIT_API_SECRET" << 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::this_thread::sleep_for(std::chrono::seconds(10));
session.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}

```

**Output 2:**
```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 = EXECUTION_MANAGEMENT_EVENTS_ORDER_UPDATE,
recapType = UNKNOWN,
time = 2021-05-25T04:22:26.653785000Z,
timeReceived = 2021-05-25T04:22:26.407419000Z,
elementList = [
Element [
nameValueMap = {
CLIENT_ORDER_ID = ,
INSTRUMENT = BTC-USDT,
LIMIT_PRICE = 20000,
ORDER_ID = 6ca39186-be79-4777-97ab-1695fccd0ce4,
QUANTITY = 0.001,
SIDE = BUY,
STATUS = live
}
]
],
correlationIdList = [ 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE ]
]
]
]
Bye
```
* Subscription fields: `ORDER_UPDATE`, `PRIVATE_TRADE`, `PRIVATE_TRADE_LITE`, `BALANCE_UPDATE`, `POSITION_UPDATE`.

### Advanced Execution Management

#### Specify correlation id

Instantiate `Request` with the desired `correlationId`. The `correlationId` should be unique.
```
Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT", "cool correlation id");
```
Instantiate `Subscription` with the desired `correlationId`.
```
Subscription subscription("bybit", "BTCUSDT", "ORDER_UPDATE", "", "cool correlation id");
```
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`.
```
Request request_1(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT", "cool correlation id for BTC");
request_1.appendParam(...);
Request request_2(Request::Operation::CREATE_ORDER, "bybit", "ETH-USDT", "cool correlation id for ETH");
request_2.appendParam(...);
std::vector requests = {request_1, request_2};
session.sendRequest(requests);
```
Subscribe one `Subscription` per exchange with a comma separated string of instruments.
```
Subscription subscription("bybit", "BTC-USDT,ETH-USDT", "ORDER_UPDATE");
```

#### Multiple subscription fields

Subscribe one `Subscription` with a comma separated string of fields.
```
Subscription subscription("bybit", "BTCUSDT", "ORDER_UPDATE,PRIVATE_TRADE");
```

#### Make Session::sendRequest blocking
Instantiate `Session` without `EventHandler` argument, and pass a pointer to `Queue` as an additional argument.
```
Session session(sessionOptions, sessionConfigs);
...
Queue eventQueue;
session.sendRequest(request, &eventQueue); // block until a response is received
std::vector eventList = eventQueue.purge();
```

#### 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. `OKX_API_PASSPHRASE`, `KUCOIN_API_PASSPHRASE`, `BINANCE_USDS_FUTURES_WEBSOCKET_ORDER_ENTRY_API_KEY`, `BINANCE_USDS_FUTURES_WEBSOCKET_ORDER_ENTRY_API_PRIVATE_KEY_PATH`, `BINANCE_COIN_FUTURES_WEBSOCKET_ORDER_ENTRY_API_KEY`, `BINANCE_COIN_FUTURES_WEBSOCKET_ORDER_ENTRY_API_PRIVATE_KEY_PATH`, `BINANCE_WEBSOCKET_ORDER_ENTRY_API_KEY`, `BINANCE_WEBSOCKET_ORDER_ENTRY_API_PRIVATE_KEY_PATH`. See section "exchange API credentials" in [`include/ccapi_cpp/ccapi_macro.h`](include/ccapi_cpp/ccapi_macro.h).
* Provide credentials to `SessionConfigs`.
```
sessionConfigs.setCredential({
{"BYBIT_API_KEY", ...},
{"BYBIT_API_SECRET", ...}
});
```
* Provide credentials to `Request` or `Subscription`.
```
Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT", "", {
{"BYBIT_API_KEY", ...},
{"BYBIT_API_SECRET", ...}
});
```
```
Subscription subscription("bybit", "BTCUSDT", "ORDER_UPDATE", "", "", {
{"BYBIT_API_KEY", ...},
{"BYBIT_API_SECRET", ...}
});
```

#### Complex request parameters
Please follow the exchange's API documentations: e.g. https://bybit-exchange.github.io/docs/v5/order/create-order.
```
Request request(Request::Operation::CREATE_ORDER, "bybit", "BTCUSDT");
request.appendParam({
{"isLeverage", "1"},
});
```

#### Send request by Websocket API
For okx, cryptocom:
```
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);
```
For bybit, binance, binance-usds-futures, binance-coin-futures:
```
std::string websocketOrderEntrySubscriptionCorrelationId("any");
Subscription subscription_1("bybit", "", "ORDER_UPDATE");
Subscription subscription_2("bybit", "", "WEBSOCKET_ORDER_ENTRY", "", websocketOrderEntrySubscriptionCorrelationId);
std::vector subscriptions = {subscription_1, subscription_2};
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);
```

#### Specify instrument type
Some exchanges (i.e. bybit) might need instrument type for `Subscription`. Use `Subscription`'s `setInstrumentType` method.
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH");
subscription.setInstrumentType("spot");
session.subscribe(subscription);
```

### FIX API

**Objective:**

For a specific exchange and instrument, submit a simple limit order.

**Code:**

[C++](example/src/fix_simple/main.cpp) / [Python](binding/python/example/fix_simple/main.py) / [Java](binding/java/example/fix_simple/Main.java) / [C#](binding/csharp/example/fix_simple/MainProgram.cs) / [Go](binding/go/example/fix_simple/main.go) / [Javascript](binding/javascript/example/fix_simple/index.js)
```
#include "ccapi_cpp/ccapi_session.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("BINANCE_FIX_API_KEY").empty()) {
std::cerr << "Please set environment variable BINANCE_FIX_API_KEY" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("BINANCE_FIX_API_PRIVATE_KEY_PATH").empty()) {
std::cerr << "Please set environment variable BINANCE_FIX_API_PRIVATE_KEY_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::this_thread::sleep_for(std::chrono::seconds(10));
session.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}
```
**Output:**
```console
Received an event:
Event [
type = SESSION_STATUS,
messageList = [
Message [
type = SESSION_CONNECTION_UP,
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
```

### 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::io_context` 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::io_context` runs. `EventHandler`s and/or `EventDispatcher`s can be shared among different sessions. Otherwise, different sessions are independent from each other.
An example can be found [here](example/src/market_data_advanced_subscription/main.cpp).
* 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
```
std::vector eventList = session.getEventQueue().purge();
```
An example can be found [here](example/src/market_data_advanced_subscription/main.cpp).

#### 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::io_context` to `ServiceContext`, the `boost::asio::io_context` has to run on a single thread to ensure thread safety.

#### Enable library logging

[C++](example/src/enable_library_logging/main.cpp) / [Python](binding/python/example/enable_library_logging/main.py) / [Java](binding/java/example/enable_library_logging/Main.java) / [C#](binding/csharp/example/enable_library_logging/MainProgram.cs) / [Go](binding/go/example/enable_library_logging/main.go)

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: `CCAPI_ENABLE_LOG_TRACE`, `CCAPI_ENABLE_LOG_DEBUG`, `CCAPI_ENABLE_LOG_INFO`, `CCAPI_ENABLE_LOG_WARN`, `CCAPI_ENABLE_LOG_ERROR`, `CCAPI_ENABLE_LOG_FATAL`. Enable logging if you'd like to inspect raw responses/messages from the exchange for troubleshooting purposes.
```
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 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 */
```

#### Set timer

[C++](example/src/set_timer/main.cpp)

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.
```
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; });
```

#### Heartbeat

[C++](example/src/heartbeat/main.cpp)

To receive heartbeat events, instantiate a `Subscription` object with field `HEARTBEAT` and subscribe it.
```
Subscription subscription("", "", "HEARTBEAT", "HEARTBEAT_INTERVAL_MILLISECONDS=1000");
session.subscribe(subscription);
```

#### Use multiple sessions

[C++](example/src/use_multiple_sessions/main.cpp)

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`.
```
Subscription subscription("", "", "HEARTBEAT", "HEARTBEAT_INTERVAL_MILLISECONDS=1000");
session.subscribe(subscription);
```

#### 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/ccapi_cpp/ccapi_macro.h`](include/ccapi_cpp/ccapi_macro.h). You can also override exchange urls at runtime. See [this example](example/src/override_exchange_url_at_runtime/main.cpp). 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`.
```
Subscription subscription("bybit", "BTCUSDT", "MARKET_DEPTH", "", "", {}, "172.30.0.146:9000");
```

#### 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](example/src/reduce_build_time).

## Performance Tuning
* Turn on compiler optimization flags (e.g. `cmake -DCMAKE_BUILD_TYPE=Release ...`).
* Enable link time optimization (e.g. in CMakeLists.txt `set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)` before a target is created). Note that link time optimization is only applicable to static linking.
* Only enable the services and exchanges that you need.
* Handle events in ["batching" mode](#handle-events-in-immediate-vs-batching-mode) if your application (e.g. market data archiver) isn't latency sensitive.

## 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.