Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8795192
add subscription proxyUrl
cryptochassis Sep 22, 2025
9892199
update README.md
cryptochassis Sep 22, 2025
3b76d45
fix reconnection in boost::asio code
fedeitc Sep 22, 2025
ba4fe90
Merge pull request #548 from fedeitc/asio-reconnet-fix
cryptochassis Sep 23, 2025
5ccc674
fix processing kraken MD subscriptions
geseq Sep 29, 2025
805236f
fix processing kraken spot MD subscriptions
geseq Sep 30, 2025
7168a06
Merge pull request #549 from geseq/fix-kraken
cryptochassis Oct 1, 2025
dccd511
fix bugs in proxy urls
cryptochassis Oct 1, 2025
d4fb51c
fix port for hostHttpHeaderValue when using proxy
cryptochassis Oct 1, 2025
6667d2c
Merge branch 'develop' of https://github.com/crypto-chassis/ccapi int…
cryptochassis Oct 1, 2025
f3dfb32
Update README.md
cryptochassis Oct 8, 2025
271dda5
fix normalizeDecimalString preserve trailing zeroes of exponent
fedeitc Oct 13, 2025
7cf18e6
huobi deriv de-dupe createEvent code already in extractOrderInfo
fedeitc Oct 13, 2025
f1aa9dc
huobi deriv use orders topic for ORDER_UPDATE and PRIVATE_TRADE
fedeitc Oct 13, 2025
75360bd
fix huobi derivatives set lever_rate only when no ORDER_LEVERAGE
fedeitc Oct 13, 2025
b85da3e
Merge pull request #551 from fedeitc/huobi-swap-one-topic
cryptochassis Oct 13, 2025
85402e7
Merge pull request #547 from crypto-chassis/proxy
cryptochassis Oct 13, 2025
2e87306
draft
cryptochassis Oct 31, 2025
cf68e23
update README.md
cryptochassis Oct 31, 2025
091ac6e
Merge pull request #552 from crypto-chassis/example-how-to-reduce-bui…
cryptochassis Oct 31, 2025
b9e749b
refactor: include inflate stream only when needed
x-mass Nov 16, 2025
25f8dde
Merge pull request #553 from x-mass/develop
cryptochassis Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
- [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)
- [Override exchange urls](#override-exchange-urls)
- [Complex request parameters](#complex-request-parameters-1)
- [Send request by Websocket API](#send-request-by-websocket-api)
- [Specify instrument type](#specify-instrument-type)
Expand All @@ -44,6 +43,9 @@
- [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)

Expand All @@ -60,6 +62,7 @@
* 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 [email protected].
* 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.
Expand Down Expand Up @@ -700,11 +703,11 @@ Bye

#### Specify correlation id

Instantiate `Request` with the desired correlationId. The `correlationId` should be unique.
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.
Instantiate `Subscription` with the desired `correlationId`.
```
Subscription subscription("okx", "BTC-USDT", "ORDER_UPDATE", "", "cool correlation id");
```
Expand Down Expand Up @@ -767,9 +770,6 @@ Subscription subscription("okx", "BTC-USDT", "ORDER_UPDATE", "", "", {
});
```

#### 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://www.okx.com/docs-v5/en/#overview-demo-trading-services) or connect to an IP address (e.g. ws://172.30.0.146:9000).

#### Complex request parameters
Please follow the exchange's API documentations: e.g. https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-order.
```
Expand Down Expand Up @@ -1073,7 +1073,17 @@ Subscription subscription("", "", "HEARTBEAT", "HEARTBEAT_INTERVAL_MILLISECONDS=
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://www.okx.com/docs-v5/en/#overview-demo-trading-services).

#### Connect to a proxy
Instantiate `Subscription` with the desired `proxyUrl`.
```
Subscription subscription("okx", "BTC-USDT", "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 ...`).
Expand Down
1 change: 1 addition & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,4 @@ add_subdirectory(src/override_exchange_url_at_runtime)
add_subdirectory(src/test_order_latency)
add_subdirectory(src/test_cpu_usage)
add_subdirectory(src/use_multiple_sessions)
add_subdirectory(src/reduce_build_time)
6 changes: 6 additions & 0 deletions example/src/reduce_build_time/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
set(NAME reduce_build_time)
project(${NAME})
add_compile_definitions(CCAPI_ENABLE_SERVICE_MARKET_DATA)
add_compile_definitions(CCAPI_ENABLE_EXCHANGE_OKX)
add_executable(${NAME} main.cpp my_session.cpp)
add_dependencies(${NAME} boost rapidjson)
38 changes: 38 additions & 0 deletions example/src/reduce_build_time/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "ccapi_cpp/ccapi_event_handler.h"
#include "my_session.h"

namespace ccapi {

Logger* Logger::logger = nullptr; // This line is needed.

class MyEventHandler : public EventHandler {
public:
void processEvent(const Event& event, Session*) override { std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl; }

void setMySessionPtr(MySession* mySessionPtr) { this->mySessionPtr = mySessionPtr; }

private:
MySession* mySessionPtr{nullptr};
};

} /* namespace ccapi */

using ::ccapi::MyEventHandler;
using ::ccapi::MySession;
using ::ccapi::SessionConfigs;
using ::ccapi::SessionOptions;
using ::ccapi::Subscription;

int main(int argc, char** argv) {
SessionOptions sessionOptions;
SessionConfigs sessionConfigs;
MyEventHandler eventHandler;
MySession mySession(sessionOptions, sessionConfigs, &eventHandler);
eventHandler.setMySessionPtr(&mySession);
Subscription subscription("okx", "BTC-USDT", "MARKET_DEPTH");
mySession.subscribe(subscription);
std::this_thread::sleep_for(std::chrono::seconds(10));
mySession.stop();
std::cout << "Bye" << std::endl;
return EXIT_SUCCESS;
}
14 changes: 14 additions & 0 deletions example/src/reduce_build_time/my_session.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "my_session.h"

#include "ccapi_cpp/ccapi_session.h"

namespace ccapi {

MySession::MySession(const SessionOptions& sessionOptions, const SessionConfigs& sessionConfigs, EventHandler* eventHandler)
: ccapiSessionPtr(new Session(sessionOptions, sessionConfigs, eventHandler)) {}

void MySession::subscribe(Subscription& subscription) { this->ccapiSessionPtr->subscribe(subscription); }

void MySession::stop() { this->ccapiSessionPtr->stop(); }

} // namespace ccapi
25 changes: 25 additions & 0 deletions example/src/reduce_build_time/my_session.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef EXAMPLE_SRC_REDUCE_BUILD_TIME_MY_SESSION_H_
#define EXAMPLE_SRC_REDUCE_BUILD_TIME_MY_SESSION_H_

#include "ccapi_cpp/ccapi_session_configs.h"
#include "ccapi_cpp/ccapi_session_options.h"
#include "ccapi_cpp/ccapi_subscription.h"

namespace ccapi {

class EventHandler;
class Session; // forward declaration instead of including full header!

class MySession {
public:
explicit MySession(const SessionOptions& sessionOptions, const SessionConfigs& sessionConfigs, EventHandler* eventHandler);
void subscribe(Subscription& subscription);
void stop();

private:
Session* ccapiSessionPtr{nullptr};
};

} // namespace ccapi

#endif // EXAMPLE_SRC_REDUCE_BUILD_TIME_MY_SESSION_H_
6 changes: 3 additions & 3 deletions include/ccapi_cpp/ccapi_inflate_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ class InflateStream {
return boost::system::error_code();
}

boost::system::error_code decompress(uint8_t const *buf, size_t len, std::string &out) {
boost::system::error_code decompress(uint8_t const* buf, size_t len, std::string& out) {
if (!this->initialized) {
CCAPI_LOGGER_ERROR("decompress error");
return boost::system::error_code();
}

this->istate.avail_in = len;
this->istate.next_in = const_cast<unsigned char *>(buf);
this->istate.next_in = const_cast<unsigned char*>(buf);
do {
this->istate.avail_out = this->decompressBufferSize;
this->istate.next_out = this->buffer.get();
Expand All @@ -74,7 +74,7 @@ class InflateStream {
CCAPI_LOGGER_ERROR("decompress error");
return boost::system::error_code();
}
out.append(reinterpret_cast<char *>(this->buffer.get()), this->decompressBufferSize - this->istate.avail_out);
out.append(reinterpret_cast<char*>(this->buffer.get()), this->decompressBufferSize - this->istate.avail_out);
} while (this->istate.avail_out == 0);
return boost::system::error_code();
}
Expand Down
8 changes: 0 additions & 8 deletions include/ccapi_cpp/ccapi_macro.h
Original file line number Diff line number Diff line change
Expand Up @@ -1160,10 +1160,6 @@
#define CCAPI_HUOBI_USDT_SWAP_SUBSCRIBE_ORDER_DATA_TOPIC "orders_cross"
#endif

#ifndef CCAPI_HUOBI_USDT_SWAP_SUBSCRIBE_MATCH_ORDER_DATA_TOPIC
#define CCAPI_HUOBI_USDT_SWAP_SUBSCRIBE_MATCH_ORDER_DATA_TOPIC "matchOrders_cross"
#endif

#ifndef CCAPI_HUOBI_COIN_SWAP_URL_WS_BASE
#define CCAPI_HUOBI_COIN_SWAP_URL_WS_BASE "wss://api.hbdm.com"
#endif
Expand All @@ -1172,10 +1168,6 @@
#define CCAPI_HUOBI_COIN_SWAP_SUBSCRIBE_ORDER_DATA_TOPIC "orders"
#endif

#ifndef CCAPI_HUOBI_COIN_SWAP_SUBSCRIBE_MATCH_ORDER_DATA_TOPIC
#define CCAPI_HUOBI_COIN_SWAP_SUBSCRIBE_MATCH_ORDER_DATA_TOPIC "matchOrders"
#endif

#ifndef CCAPI_OKX_URL_WS_BASE
#define CCAPI_OKX_URL_WS_BASE "wss://ws.okx.com:8443"
#endif
Expand Down
14 changes: 10 additions & 4 deletions include/ccapi_cpp/ccapi_subscription.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ namespace ccapi {
class Subscription {
public:
explicit Subscription(const std::string& exchange = "", const std::string& instrument = "", const std::string& field = "", const std::string& options = "",
const std::string& correlationId = "", const std::map<std::string, std::string>& credential = {})
: exchange(exchange), instrument(instrument), field(field), correlationId(correlationId), credential(credential) {
const std::string& correlationId = "", const std::map<std::string, std::string>& credential = {}, const std::string& proxyUrl = "")
: exchange(exchange), instrument(instrument), field(field), correlationId(correlationId), credential(credential), proxyUrl(proxyUrl) {
auto originalInstrumentSet = UtilString::splitToSet(instrument, ",");
std::copy_if(originalInstrumentSet.begin(), originalInstrumentSet.end(), std::inserter(this->instrumentSet, this->instrumentSet.end()),
[](const std::string& value) { return !value.empty(); });
Expand Down Expand Up @@ -68,8 +68,8 @@ class Subscription {
}
std::string output = "Subscription [exchange = " + exchange + ", marginType = " + marginType + ", instrumentType = " + instrumentType +
", instrument = " + instrument + ", field = " + field + ", optionMap = " + ccapi::toString(optionMap) +
", correlationId = " + correlationId + ", credential = " + ccapi::toString(shortCredential) + ", serviceName = " + serviceName +
", timeSent = " + UtilTime::getISOTimestamp(timeSent) + "]";
", correlationId = " + correlationId + ", credential = " + ccapi::toString(shortCredential) + ", proxyUrl = " + proxyUrl +
", serviceName = " + serviceName + ", timeSent = " + UtilTime::getISOTimestamp(timeSent) + "]";
return output;
}

Expand All @@ -89,6 +89,8 @@ class Subscription {

const std::map<std::string, std::string>& getCredential() const { return credential; }

const std::string& getProxyUrl() const { return proxyUrl; }

const std::string& getServiceName() const { return serviceName; }

const std::set<std::string>& getInstrumentSet() const { return instrumentSet; }
Expand Down Expand Up @@ -131,11 +133,14 @@ class Subscription {

void setField(const std::string& field) { this->field = field; }

void setProxyUrl(const std::string& proxyUrl) { this->proxyUrl = proxyUrl; }

void setTimeSent(TimePoint timeSent) { this->timeSent = timeSent; }

void setInstrumentType(const std::string& instrumentType) { this->instrumentType = instrumentType; }

void setMarginType(const std::string& marginType) { this->marginType = marginType; }

enum class Status {
UNKNOWN,
SUBSCRIBING,
Expand Down Expand Up @@ -180,6 +185,7 @@ class Subscription {
std::map<std::string, std::string> optionMap;
std::string correlationId;
std::map<std::string, std::string> credential;
std::string proxyUrl;
std::string serviceName;
std::set<std::string> instrumentSet;
std::set<std::string> fieldSet;
Expand Down
20 changes: 10 additions & 10 deletions include/ccapi_cpp/ccapi_url.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Url {
return output;
}

static std::string urlEncode(const std::string &value) {
static std::string urlEncode(const std::string& value) {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
Expand All @@ -50,7 +50,7 @@ class Url {
return escaped.str();
}

static std::string urlDecode(const std::string &value) {
static std::string urlDecode(const std::string& value) {
std::string ret;
char ch;
int i, ii;
Expand All @@ -67,18 +67,18 @@ class Url {
return (ret);
}

static std::map<std::string, std::string> convertQueryStringToMap(const std::string &input) {
static std::map<std::string, std::string> convertQueryStringToMap(const std::string& input) {
std::map<std::string, std::string> output;
for (const auto &x : UtilString::split(input, "&")) {
for (const auto& x : UtilString::split(input, "&")) {
auto y = UtilString::split(x, "=");
output.insert(std::make_pair(y.at(0), Url::urlDecode(y.at(1))));
}
return output;
}

static std::string convertMapToQueryString(const std::map<std::string, std::string> &input) {
static std::string convertMapToQueryString(const std::map<std::string, std::string>& input) {
std::string output;
for (const auto &x : input) {
for (const auto& x : input) {
output += x.first;
output += "=";
output += x.second;
Expand All @@ -90,10 +90,10 @@ class Url {
return output;
}

static std::string convertMapToFormUrlEncoded(const std::map<std::string, std::string> &input) {
static std::string convertMapToFormUrlEncoded(const std::map<std::string, std::string>& input) {
std::string output;
int i = 0;
for (const auto &x : input) {
for (const auto& x : input) {
output += Url::urlEncode(x.first);
output += "=";
output += Url::urlEncode(x.second);
Expand All @@ -104,9 +104,9 @@ class Url {
return output;
}

static std::map<std::string, std::string> convertFormUrlEncodedToMap(const std::string &input) {
static std::map<std::string, std::string> convertFormUrlEncodedToMap(const std::string& input) {
std::map<std::string, std::string> output;
for (const auto &x : UtilString::split(input, "&")) {
for (const auto& x : UtilString::split(input, "&")) {
auto y = UtilString::split(x, "=");
output.insert(std::make_pair(Url::urlDecode(y.at(0)), Url::urlDecode(y.at(1))));
}
Expand Down
4 changes: 2 additions & 2 deletions include/ccapi_cpp/ccapi_util_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class UtilString {
}

static std::string normalizeDecimalString(const std::string& original) {
if (original.find('.') != std::string::npos) {
if (original.find('.') != std::string::npos && original.find_first_of("Ee") == std::string::npos) {
std::string str(original);
rtrimInPlace(str, "0");
rtrimInPlace(str, ".");
Expand All @@ -303,7 +303,7 @@ class UtilString {
static std::string_view normalizeDecimalStringView(std::string_view input) {
// Quick check for dot
size_t dotPos = input.find('.');
if (dotPos == std::string_view::npos) return input;
if (dotPos == std::string_view::npos || input.find_first_of("Ee") != std::string::npos) return input;

size_t end = input.size();

Expand Down
10 changes: 6 additions & 4 deletions include/ccapi_cpp/ccapi_ws_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class WsConnection {
WsConnection& operator=(const WsConnection&) = delete;

WsConnection(const std::string& url, const std::string& group, const std::vector<Subscription>& subscriptionList,
const std::map<std::string, std::string>& credential)
: url(url), group(group), subscriptionList(subscriptionList), credential(credential) {
const std::map<std::string, std::string>& credential, const std::string& proxyUrl = "")
: url(url), group(group), subscriptionList(subscriptionList), credential(credential), proxyUrl(proxyUrl) {
std::map<std::string, std::string> shortCredential;
for (const auto& x : credential) {
shortCredential.insert(std::make_pair(x.first, UtilString::firstNCharacter(x.second, CCAPI_CREDENTIAL_DISPLAY_LENGTH)));
Expand Down Expand Up @@ -51,8 +51,9 @@ class WsConnection {
streamPtr);
std::string output = "WsConnection [longId = " + longId + ", id = " + id + ", url = " + url + ", group = " + group +
", subscriptionList = " + ccapi::toString(subscriptionList) + ", credential = " + ccapi::toString(shortCredential) +
", status = " + statusToString(status) + ", headers = " + ccapi::toString(headers) + ", streamPtr = " + oss.str() +
", remoteCloseCode = " + std::to_string(remoteCloseCode) + ", remoteCloseReason = " + std::string(remoteCloseReason.reason.c_str()) +
", proxyUrl = " + proxyUrl + ", status = " + statusToString(status) + ", headers = " + ccapi::toString(headers) +
", streamPtr = " + oss.str() + ", remoteCloseCode = " + std::to_string(remoteCloseCode) +
", remoteCloseReason = " + std::string(remoteCloseReason.reason.c_str()) +
", hostHttpHeaderValue = " + ccapi::toString(hostHttpHeaderValue) + ", path = " + ccapi::toString(path) +
", host = " + ccapi::toString(host) + ", port = " + ccapi::toString(port) + ", isSecure = " + ccapi::toString(isSecure) + "]";
return output;
Expand Down Expand Up @@ -145,6 +146,7 @@ class WsConnection {
Status status{Status::UNKNOWN};
std::map<std::string, std::string> headers;
std::map<std::string, std::string> credential;
std::string proxyUrl;
std::variant<std::shared_ptr<beast::websocket::stream<beast::ssl_stream<beast::tcp_stream>>>, std::shared_ptr<beast::websocket::stream<beast::tcp_stream>>>
streamPtr;
beast::websocket::close_code remoteCloseCode{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ class ExecutionManagementService : public Service {
}

const auto& fieldSet = subscription.getFieldSet();
const auto& proxyUrl = subscription.getProxyUrl();

if (fieldSet.find(CCAPI_EM_WEBSOCKET_ORDER_ENTRY) != fieldSet.end()) {
auto wsConnectionPtr = std::make_shared<WsConnection>(that->baseUrlWsOrderEntry, "", std::vector<Subscription>{subscription}, credential);
auto wsConnectionPtr = std::make_shared<WsConnection>(that->baseUrlWsOrderEntry, "", std::vector<Subscription>{subscription}, credential, proxyUrl);
that->setWsConnectionStream(wsConnectionPtr);
CCAPI_LOGGER_WARN("about to subscribe with new wsConnectionPtr " + toString(*wsConnectionPtr));
that->prepareConnect(wsConnectionPtr);
} else {
auto wsConnectionPtr = std::make_shared<WsConnection>(that->baseUrlWs, "", std::vector<Subscription>{subscription}, credential);
auto wsConnectionPtr = std::make_shared<WsConnection>(that->baseUrlWs, "", std::vector<Subscription>{subscription}, credential, proxyUrl);
that->setWsConnectionStream(wsConnectionPtr);
CCAPI_LOGGER_WARN("about to subscribe with new wsConnectionPtr " + toString(*wsConnectionPtr));
that->prepareConnect(wsConnectionPtr);
Expand Down
Loading
Loading