- The return type of
EventHandler::processEvent
has been changed frombool
tovoid
. - Please read Handle events in "immediate" vs. "batching" mode.
Table of Contents generated with DocToc
- Some breaking changes introduced
- ccapi
- Branches
- Build
- 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
- FIX API
- More Advanced Topics
- Performance Tuning
- Known Issues and Workarounds
- Contributing
- 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: move semantics, regex optimization, locality of reference, lock contention minimization, etc.
- Supported exchanges:
- Market Data: ascendex, binance, binance-usds-futures, binance-coin-futures, bitfinex, bitget, bitget-futures, bitmart, bitmex, bitstamp, bybit, coinbase, cryptocom, deribit, erisx (Cboe Digital), gateio, gateio-perpetual-futures, gemini, huobi, huobi-usdt-swap, huobi-coin-swap, kraken, kraken-futures, kucoin, kucoin-futures, mexc, mexc-futures, okx, whitebit.
- Execution Management: ascendex, binance, binance-usds-futures, binance-coin-futures, bitfinex, bitget, bitget-futures, bitmart, bitmex, bitstamp, bybit, coinbase, cryptocom, deribit, erisx (Cboe Digital), gateio, gateio-perpetual-futures, gemini, huobi, huobi-usdt-swap, huobi-coin-swap, kraken, kraken-futures, kucoin, kucoin-futures, mexc, okx.
- FIX: coinbase, gemini.
- Join us on Discord https://discord.gg/b5EKcp9s8T and Medium https://cryptochassis.medium.com.
- The
develop
branch may contain experimental features. - The
master
branch represents the most recent stable release.
- 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 asCCAPI_ENABLE_EXCHANGE_OKX
, etc. These macros can be found at the top ofinclude/ccapi_cpp/ccapi_session.h
.
- Define service enablement macro such as
- 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
andcmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl
. On Ubuntu,sudo apt-get install libssl-dev
. On Windows,vcpkg install openssl:x64-windows
andcmake -DOPENSSL_ROOT_DIR=C:/vcpkg/installed/x64-windows-static
. - "Fatal error: can't write <a> bytes to section .text of <b>: 'File too big'". Try to add compiler flag
-Wa,-mbig-obj
. See assimp/assimp#2067. - "string table overflow at offset <a>". 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
.
- Require SWIG and CMake.
- SWIG: On macOS,
brew install SWIG
. On Linux,sudo apt-get install -y swig
. - CMake: https://cmake.org/download/.
- SWIG: On macOS,
- 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/<language>/packaging/<BUILD_VERSION>
directory. SWIG generated raw files and build artifacts are located in thebinding/build/<language>/ccapi_binding_<language>
directory. - Python: If a virtual environment (managed by
venv
orconda
) is active (i.e. theactivate
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
andcmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl
. On Ubuntu,sudo apt-get install libssl-dev
. On Windows,vcpkg install openssl:x64-windows
andcmake -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
orconda
) with Python 3. - "‘_PyObject_GC_UNTRACK’ was not declared in this scope". If you use Python >= 3.8, please use SWIG >= 4.0.
- "CMake Error at python/CMakeLists.txt:... (message): Require Python 3". Try to create and activate a virtual environment (managed by
- 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.
- "Could NOT find JNI (missing: JAVA_INCLUDE_PATH JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH)". Check that the environment variable
- Javascript:
- "Check for node-gyp Program: not found". You can install node-gyp using npm:
npm install -g node-gyp
- "Check for node-gyp Program: not found". You can install node-gyp using npm:
- "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
include/ccapi_cpp/ccapi_macro.h
- 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 <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/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 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/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 includesbinding/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
'sjava.library.path
property includesbinding/build/java/packaging/1.0.0
. See https://stackoverflow.com/questions/1403788/java-lang-unsatisfiedlinkerror-no-dll-in-java-library-path.
- "../Main.java:1: error: package com.cryptochassis.ccapi does not exist". Check that
- 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/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
includesbinding/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/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 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/market_data_simple_subscription), run
rm -rf node_modules (if rebuild from scratch)
npm install
node index.js
Objective 1:
For a specific exchange and instrument, get recents trades.
Code 1:
C++ / Python / Java / C# / Go / Javascript
#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.toStringPretty(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, "okx", "BTC-USDT");
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:
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. - Message's
time
represents the exchange's reported timestamp. ItstimeReceived
represents the library's receiving timestamp.time
can be retrieved bygetTime
method andtimeReceived
can be retrieved bygetTimeReceived
method. (For non-C++, please usegetTimeUnix
andgetTimeReceivedUnix
methods orgetTimeISO
andgetTimeReceivedISO
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++ / Python / Java / C# / Go / Javascript
#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.toStringPretty(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()) {
const std::map<std::string, 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("okx", "BTC-USDT", "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:
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).
Please follow the exchange's API documentations: e.g. https://www.okx.com/docs-v5/en/#order-book-trading-market-data-get-trades-history.
Request request(Request::Operation::GET_HISTORICAL_TRADES, "okx", "BTC-USDT");
request.appendParam({
{"before", "1"},
{"after", "3"},
{"limit", "1"},
});
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("okx", "BTC-USDT", "MARKET_DEPTH", "MARKET_DEPTH_MAX=10");
Instantiate Request
with the desired correlationId. The correlationId
should be unique.
Request request(Request::Operation::GET_RECENT_TRADES, "okx", "BTC-USDT", "cool correlation id");
Instantiate Subscription
with the desired correlationId.
Subscription subscription("okx", "BTC-USDT", "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.
Send a std::vector<Request>
.
Request request_1(Request::Operation::GET_RECENT_TRADES, "okx", "BTC-USDT", "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<ccapi::Request> requests = {request_1, request_2};
session.sendRequest(requests);
Subscribe a std::vector<Subscription>
.
Subscription subscription_1("okx", "BTC-USDT", "MARKET_DEPTH", "", "cool correlation id for okx BTC-USDT");
Subscription subscription_2("binance", "ETH-USDT", "MARKET_DEPTH", "", "cool correlation id for binance ETH-USDT");
std::vector<ccapi::Subscription> subscriptions = {subscription_1, subscription_2};
session.subscribe(subscriptions);
Instantiate Subscription
with option CONFLATE_INTERVAL_MILLISECONDS
set to be the desired interval.
Subscription subscription("okx", "BTC-USDT", "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("okx", "BTC-USDT", "MARKET_DEPTH", "CONFLATE_INTERVAL_MILLISECONDS=1000&CONFLATE_GRACE_PERIOD_MILLISECONDS=0");
Instantiate Subscription
with option MARKET_DEPTH_RETURN_UPDATE
set to 1. This will return the order book updates instead of snapshots.
Subscription subscription("okx", "BTC-USDT", "MARKET_DEPTH", "MARKET_DEPTH_RETURN_UPDATE=1&MARKET_DEPTH_MAX=2");
Instantiate Subscription
with field TRADE
.
Subscription subscription("okx", "BTC-USDT", "TRADE");
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("okx", "BTC-USDT", "TRADE", "CONFLATE_INTERVAL_MILLISECONDS=5000&CONFLATE_GRACE_PERIOD_MILLISECONDS=0");
Instantiate Subscription
with field CANDLESTICK
and option CANDLESTICK_INTERVAL_SECONDS
set to be the desired interval.
Subscription subscription("okx", "BTC-USDT", "CANDLESTICK", "CANDLESTICK_INTERVAL_SECONDS=60");
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, "okx", "", "Check Server Time");
request.appendParam({
{"HTTP_METHOD", "GET"},
{"HTTP_PATH", "/api/v5/public/time"},
});
Instantiate Subscription
with empty instrument, field GENERIC_PUBLIC_SUBSCRIPTION
and options set to be the desired websocket payload.
Subscription subscription("okx", "", "GENERIC_PUBLIC_SUBSCRIPTION", R"({"type":"subscribe","channels":[{"name":"status"}]})");
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, "okx", "", "close all positions");
request.appendParam({
{"HTTP_METHOD", "POST"},
{"HTTP_PATH", "/api/v5/trade/close-position"},
{"HTTP_BODY", R"({
"instId": "BTC-USDT-SWAP",
"mgnMode": "cross"
})"},
});
Objective 1:
For a specific exchange and instrument, submit a simple limit order.
Code 1:
C++ / Python / Java / C# / Go / Javascript
#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.toStringPretty(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("OKX_API_KEY").empty()) {
std::cerr << "Please set environment variable OKX_API_KEY" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("OKX_API_SECRET").empty()) {
std::cerr << "Please set environment variable OKX_API_SECRET" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("OKX_API_PASSPHRASE").empty()) {
std::cerr << "Please set environment variable OKX_API_PASSPHRASE" << std::endl;
return EXIT_FAILURE;
}
SessionOptions sessionOptions;
SessionConfigs sessionConfigs;
MyEventHandler eventHandler;
Session session(sessionOptions, sessionConfigs, &eventHandler);
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT");
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:
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.
Objective 2:
For a specific exchange and instrument, receive order updates.
Code 2:
C++ / Python / Java / C# / Go / Javascript
#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.toStringPretty(2, 2) << std::endl;
auto message = event.getMessageList().at(0);
if (message.getType() == Message::Type::SUBSCRIPTION_STARTED) {
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT");
request.appendParam({
{"SIDE", "BUY"},
{"LIMIT_PRICE", "20000"},
{"QUANTITY", "0.001"},
{"CLIENT_ORDER_ID", "6d4eb0fb"},
});
sessionPtr->sendRequest(request);
}
} else if (event.getType() == Event::Type::SUBSCRIPTION_DATA) {
std::cout << "Received an event of type SUBSCRIPTION_DATA:\n" + event.toStringPretty(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("OKX_API_KEY").empty()) {
std::cerr << "Please set environment variable OKX_API_KEY" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("OKX_API_SECRET").empty()) {
std::cerr << "Please set environment variable OKX_API_SECRET" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("OKX_API_PASSPHRASE").empty()) {
std::cerr << "Please set environment variable OKX_API_PASSPHRASE" << std::endl;
return EXIT_FAILURE;
}
SessionOptions sessionOptions;
SessionConfigs sessionConfigs;
MyEventHandler eventHandler;
Session session(sessionOptions, sessionConfigs, &eventHandler);
Subscription subscription("okx", "BTC-USDT", "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:
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
,BALANCE_UPDATE
,POSITION_UPDATE
.
Instantiate Request
with the desired correlationId. The correlationId
should be unique.
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT", "cool correlation id");
Instantiate Subscription
with the desired correlationId.
Subscription subscription("okx", "BTC-USDT", "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.
Send a std::vector<Request>
.
Request request_1(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT", "cool correlation id for BTC");
request_1.appendParam(...);
Request request_2(Request::Operation::CREATE_ORDER, "okx", "ETH-USDT", "cool correlation id for ETH");
request_2.appendParam(...);
std::vector<ccapi::Request> requests = {request_1, request_2};
session.sendRequest(requests);
Subscribe one Subscription
per exchange with a comma separated string of instruments.
Subscription subscription("okx", "BTC-USDT,ETH-USDT", "ORDER_UPDATE");
Subscribe one Subscription
with a comma separated string of fields.
Subscription subscription("okx", "BTC-USDT", "ORDER_UPDATE,PRIVATE_TRADE");
Instantiate Session
without EventHandler
argument, and pass a pointer to Queue<Event>
as an additional argument.
Session session(sessionOptions, sessionConfigs);
...
Queue<Event> eventQueue;
session.sendRequest(request, &eventQueue); // block until a response is received
std::vector<Event> eventList = eventQueue.purge();
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" ininclude/ccapi_cpp/ccapi_macro.h
. - Provide credentials to
SessionConfigs
.
sessionConfigs.setCredential({
{"OKX_API_KEY", ...},
{"OKX_API_SECRET", ...}
});
- Provide credentials to
Request
orSubscription
.
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT", "", {
{"OKX_API_KEY", ...},
{"OKX_API_SECRET", ...}
});
Subscription subscription("okx", "BTC-USDT", "ORDER_UPDATE", "", "", {
{"OKX_API_KEY", ...},
{"OKX_API_SECRET", ...}
});
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
. 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://www.okx.com/docs-v5/en/#overview-demo-trading-services).
Please follow the exchange's API documentations: e.g. https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-order.
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT");
request.appendParam({
{"tdMode", "cross"},
{"ccy", "USDT"},
});
For okx:
std::string websocketOrderEntrySubscriptionCorrelationId("any");
Subscription subscription("okx", "", "ORDER_UPDATE", "", websocketOrderEntrySubscriptionCorrelationId);
session.subscribe(subscription);
...
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT");
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<ccapi::Subscription> 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);
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);
Objective:
For a specific exchange and instrument, submit a simple limit order.
Code:
C++ / Python / Java / C# / Go / Javascript
#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::AUTHORIZATION_STATUS) {
std::cout << "Received an event of type AUTHORIZATION_STATUS:\n" + event.toStringPretty(2, 2) << std::endl;
auto message = event.getMessageList().at(0);
if (message.getType() == Message::Type::AUTHORIZATION_SUCCESS) {
Request request(Request::Operation::FIX, "okx", "", "same correlation id for subscription and request");
request.appendParamFix({
{35, "D"},
{11, "6d4eb0fb-2229-469f-873e-557dd78ac11e"},
{55, "BTC-USDT"},
{54, "1"},
{44, "20000"},
{38, "0.001"},
{40, "2"},
{59, "1"},
});
sessionPtr->sendRequestByFix(request);
}
} else if (event.getType() == Event::Type::FIX) {
std::cout << "Received an event of type FIX:\n" + event.toStringPretty(2, 2) << std::endl;
}
}
};
} /* 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("OKX_API_KEY").empty()) {
std::cerr << "Please set environment variable OKX_API_KEY" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("OKX_API_SECRET").empty()) {
std::cerr << "Please set environment variable OKX_API_SECRET" << std::endl;
return EXIT_FAILURE;
}
if (UtilSystem::getEnvAsString("OKX_API_PASSPHRASE").empty()) {
std::cerr << "Please set environment variable OKX_API_PASSPHRASE" << std::endl;
return EXIT_FAILURE;
}
SessionOptions sessionOptions;
SessionConfigs sessionConfigs;
MyEventHandler eventHandler;
Session session(sessionOptions, sessionConfigs, &eventHandler);
Subscription subscription("okx", "", "FIX", "", "same correlation id for subscription and request");
session.subscribeByFix(subscription);
std::this_thread::sleep_for(std::chrono::seconds(10));
session.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}
Output:
Received an event of type AUTHORIZATION_STATUS:
Event [
type = AUTHORIZATION_STATUS,
messageList = [
Message [
type = AUTHORIZATION_SUCCESS,
recapType = UNKNOWN,
time = 1970-01-01T00:00:00.000000000Z,
timeReceived = 2021-05-25T05:05:15.892366000Z,
elementList = [
Element [
tagValueMap = {
96 = 0srtt0WetUTYHiTpvyWnC+XKKHCzQQIJ/8G9lE4KVxM=,
98 = 0,
108 = 15,
554 = 26abh7of52i
}
]
],
correlationIdList = [ same correlation id for subscription and request ]
]
]
]
Received an event of type FIX:
Event [
type = FIX,
messageList = [
Message [
type = FIX,
recapType = UNKNOWN,
time = 1970-01-01T00:00:00.000000000Z,
timeReceived = 2021-05-25T05:05:15.984090000Z,
elementList = [
Element [
tagValueMap = {
11 = 6d4eb0fb-2229-469f-873e-557dd78ac11e,
17 = b7caec79-1bc8-460e-af28-6489cf12f45e,
20 = 0,
37 = 458acfe5-bdea-46d2-aa87-933cda84163f,
38 = 0.001,
39 = 0,
44 = 20000,
54 = 1,
55 = BTC-USDT,
60 = 20210525-05:05:16.008,
150 = 0
}
]
],
correlationIdList = [ same correlation id for subscription and request ]
]
]
]
Bye
In general there are 2 ways to handle events.
- When a
Session
is instantiated with aneventHandler
argument, it will handle events in immediate mode. TheprocessEvent
method in theeventHandler
will be invoked immediately when anEvent
is available, and the invocation will run on the thread whereboost::asio::io_context
runs. When aSession
is instantiated with aneventHandler
and aneventDispatcher
argument, it will also handle events in immediate mode. TheprocessEvent
method in theeventHandler
will also be invoked immediately when anEvent
is available, but the invocation will run in the thread(s) provided by theeventDispatcher
therefore not blocking the thread whereboost::asio::io_context
runs.EventHandler
s and/orEventDispatcher
s 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 aneventHandler
argument, it will handle events in batching mode. The events will be batched into an internalQueue<Event>
and can be retrieved by
std::vector<Event> eventList = session.getEventQueue().purge();
An example can be found here.
- The following methods are implemented to be thread-safe:
Session::sendRequest
,Session::subscribe
,Session::sendRequestByFix
,Session::subscribeByFix
,Session::setTimer
, all public methods inQueue
.
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<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 */
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; });
- 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. - Shorten constant strings used as key names in the returned
Element
(e.g. in CmakeLists.txtadd_compile_definitions(CCAPI_BEST_BID_N_PRICE="b")
). - Only enable the services and exchanges that you need.
- Handle events in "batching" mode if your application (e.g. market data archiver) isn't latency sensitive.
- 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.
- (Required) Create a new branch from the
develop
branch and submit a pull request to thedevelop
branch. - (Optional) C++ code style: https://google.github.io/styleguide/cppguide.html. See file .clang-format.
- (Optional) Commit message format: https://conventionalcommits.org.