Skip to content

Commit 3c2691f

Browse files
Merge pull request #535 from crypto-chassis/develop
Release
2 parents fe94c83 + 9d3d6d6 commit 3c2691f

File tree

121 files changed

+1174
-705
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+1174
-705
lines changed

README.md

Lines changed: 115 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Notice
2-
* New release has about 20% reduction in cpu usage.
2+
* Small breaking change: renamed `toStringPretty` to `toPrettyString`.
3+
* Added FIX API support for binance.
34

45
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
56

@@ -48,6 +49,7 @@
4849
* [Thread safety](#thread-safety)
4950
* [Enable library logging](#enable-library-logging)
5051
* [Set timer](#set-timer)
52+
* [Heartbeat](#heartbeat)
5153
* [Performance Tuning](#performance-tuning)
5254
* [Known Issues and Workarounds](#known-issues-and-workarounds)
5355

@@ -56,11 +58,6 @@
5658
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
5759

5860

59-
60-
61-
62-
63-
6461
# ccapi
6562
* 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).
6663
* Bindings for other languages such as Python, Java, C#, Go, and Javascript are provided.
@@ -69,7 +66,7 @@
6966
* Supported exchanges:
7067
* Market Data: ascendex, [binance](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), [binance-usds-futures](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), [binance-coin-futures](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), 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, huobi-usdt-swap, huobi-coin-swap, kraken, kraken-futures, kucoin, kucoin-futures, mexc, mexc-futures, [okx](https://www.okx.com/join/47636709), whitebit.
7168
* Execution Management: ascendex, [binance](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), [binance-usds-futures](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), [binance-coin-futures](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), 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, huobi-usdt-swap, huobi-coin-swap, kraken, kraken-futures, kucoin, kucoin-futures, mexc, [okx](https://www.okx.com/join/47636709).
72-
* FIX: coinbase, gemini.
69+
* FIX: [binance](https://www.marketwebb.net/activity/referral-entry/CPA?ref=CPA_00WFM0HU96), coinbase, gemini.
7370
* Join us on Discord https://discord.gg/b5EKcp9s8T and Medium https://cryptochassis.medium.com.
7471

7572
## Branches
@@ -232,7 +229,7 @@ Logger* Logger::logger = nullptr; // This line is needed.
232229
class MyEventHandler : public EventHandler {
233230
public:
234231
void processEvent(const Event& event, Session* sessionPtr) override {
235-
std::cout << "Received an event:\n" + event.toStringPretty(2, 2) << std::endl;
232+
std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl;
236233
}
237234
};
238235
@@ -311,7 +308,7 @@ class MyEventHandler : public EventHandler {
311308
public:
312309
void processEvent(const Event& event, Session* sessionPtr) override {
313310
if (event.getType() == Event::Type::SUBSCRIPTION_STATUS) {
314-
std::cout << "Received an event of type SUBSCRIPTION_STATUS:\n" + event.toStringPretty(2, 2) << std::endl;
311+
std::cout << "Received an event of type SUBSCRIPTION_STATUS:\n" + event.toPrettyString(2, 2) << std::endl;
315312
} else if (event.getType() == Event::Type::SUBSCRIPTION_DATA) {
316313
for (const auto& message : event.getMessageList()) {
317314
std::cout << std::string("Best bid and ask at ") + UtilTime::getISOTimestamp(message.getTime()) + " are:" << std::endl;
@@ -505,7 +502,7 @@ Logger* Logger::logger = nullptr; // This line is needed.
505502
class MyEventHandler : public EventHandler {
506503
public:
507504
void processEvent(const Event& event, Session* sessionPtr) override {
508-
std::cout << "Received an event:\n" + event.toStringPretty(2, 2) << std::endl;
505+
std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl;
509506
}
510507
};
511508
@@ -605,7 +602,7 @@ class MyEventHandler : public EventHandler {
605602
public:
606603
void processEvent(const Event& event, Session* sessionPtr) override {
607604
if (event.getType() == Event::Type::SUBSCRIPTION_STATUS) {
608-
std::cout << "Received an event of type SUBSCRIPTION_STATUS:\n" + event.toStringPretty(2, 2) << std::endl;
605+
std::cout << "Received an event of type SUBSCRIPTION_STATUS:\n" + event.toPrettyString(2, 2) << std::endl;
609606
auto message = event.getMessageList().at(0);
610607
if (message.getType() == Message::Type::SUBSCRIPTION_STARTED) {
611608
Request request(Request::Operation::CREATE_ORDER, "okx", "BTC-USDT");
@@ -618,7 +615,7 @@ class MyEventHandler : public EventHandler {
618615
sessionPtr->sendRequest(request);
619616
}
620617
} else if (event.getType() == Event::Type::SUBSCRIPTION_DATA) {
621-
std::cout << "Received an event of type SUBSCRIPTION_DATA:\n" + event.toStringPretty(2, 2) << std::endl;
618+
std::cout << "Received an event of type SUBSCRIPTION_DATA:\n" + event.toPrettyString(2, 2) << std::endl;
622619
}
623620
}
624621
};
@@ -843,61 +840,66 @@ For a specific exchange and instrument, submit a simple limit order.
843840
[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)
844841
```
845842
#include "ccapi_cpp/ccapi_session.h"
843+
846844
namespace ccapi {
847845
848846
Logger* Logger::logger = nullptr; // This line is needed.
847+
849848
class MyEventHandler : public EventHandler {
850849
public:
850+
MyEventHandler(const std::string& fixSubscriptionCorrelationId) : fixSubscriptionCorrelationId(fixSubscriptionCorrelationId) {}
851+
851852
void processEvent(const Event& event, Session* sessionPtr) override {
852-
if (event.getType() == Event::Type::AUTHORIZATION_STATUS) {
853-
std::cout << "Received an event of type AUTHORIZATION_STATUS:\n" + event.toStringPretty(2, 2) << std::endl;
854-
auto message = event.getMessageList().at(0);
855-
if (message.getType() == Message::Type::AUTHORIZATION_SUCCESS) {
856-
Request request(Request::Operation::FIX, "okx", "", "same correlation id for subscription and request");
857-
request.appendParamFix({
853+
std::cout << "Received an event:\n" + event.toPrettyString(2, 2) << std::endl;
854+
if (!willSendRequest) {
855+
sessionPtr->setTimer("id", 1000, nullptr, [this, sessionPtr]() {
856+
Request request(Request::Operation::FIX, "binance");
857+
request.appendFixParam({
858858
{35, "D"},
859-
{11, "6d4eb0fb-2229-469f-873e-557dd78ac11e"},
860-
{55, "BTC-USDT"},
859+
{11, request.generateNextClientOrderId()},
860+
{55, "BTCUSDT"},
861861
{54, "1"},
862-
{44, "20000"},
863-
{38, "0.001"},
862+
{44, "100000"},
863+
{38, "0.0001"},
864864
{40, "2"},
865865
{59, "1"},
866866
});
867-
sessionPtr->sendRequestByFix(request);
868-
}
869-
} else if (event.getType() == Event::Type::FIX) {
870-
std::cout << "Received an event of type FIX:\n" + event.toStringPretty(2, 2) << std::endl;
867+
sessionPtr->sendRequestByFix(this->fixSubscriptionCorrelationId, request);
868+
});
869+
willSendRequest = true;
871870
}
872871
}
872+
873+
private:
874+
std::string fixSubscriptionCorrelationId;
875+
bool willSendRequest{};
873876
};
874877
875878
} /* namespace ccapi */
879+
876880
using ::ccapi::MyEventHandler;
877881
using ::ccapi::Session;
878882
using ::ccapi::SessionConfigs;
879883
using ::ccapi::SessionOptions;
880884
using ::ccapi::Subscription;
881885
using ::ccapi::UtilSystem;
886+
882887
int main(int argc, char** argv) {
883-
if (UtilSystem::getEnvAsString("OKX_API_KEY").empty()) {
884-
std::cerr << "Please set environment variable OKX_API_KEY" << std::endl;
888+
if (UtilSystem::getEnvAsString("BINANCE_FIX_API_KEY").empty()) {
889+
std::cerr << "Please set environment variable BINANCE_FIX_API_KEY" << std::endl;
885890
return EXIT_FAILURE;
886891
}
887-
if (UtilSystem::getEnvAsString("OKX_API_SECRET").empty()) {
888-
std::cerr << "Please set environment variable OKX_API_SECRET" << std::endl;
889-
return EXIT_FAILURE;
890-
}
891-
if (UtilSystem::getEnvAsString("OKX_API_PASSPHRASE").empty()) {
892-
std::cerr << "Please set environment variable OKX_API_PASSPHRASE" << std::endl;
892+
if (UtilSystem::getEnvAsString("BINANCE_FIX_API_PRIVATE_KEY_PATH").empty()) {
893+
std::cerr << "Please set environment variable BINANCE_FIX_API_PRIVATE_KEY_PATH" << std::endl;
893894
return EXIT_FAILURE;
894895
}
895896
SessionOptions sessionOptions;
896897
SessionConfigs sessionConfigs;
897-
MyEventHandler eventHandler;
898+
std::string fixSubscriptionCorrelationId("any");
899+
MyEventHandler eventHandler(fixSubscriptionCorrelationId);
898900
Session session(sessionOptions, sessionConfigs, &eventHandler);
899-
Subscription subscription("okx", "", "FIX", "", "same correlation id for subscription and request");
900-
session.subscribeByFix(subscription);
901+
Subscription subscription("binance", "", "FIX", "", fixSubscriptionCorrelationId);
902+
session.subscribe(subscription);
901903
std::this_thread::sleep_for(std::chrono::seconds(10));
902904
session.stop();
903905
std::cout << "Bye" << std::endl;
@@ -906,56 +908,97 @@ int main(int argc, char** argv) {
906908
```
907909
**Output:**
908910
```console
909-
Received an event of type AUTHORIZATION_STATUS:
911+
Received an event:
912+
Event [
913+
type = SESSION_STATUS,
914+
messageList = [
915+
Message [
916+
type = SESSION_CONNECTION_UP,
917+
recapType = UNKNOWN,
918+
time = 1970-01-01T00:00:00.000000000Z,
919+
timeReceived = 2025-08-08T18:50:06.816550779Z,
920+
elementList = [
921+
Element [
922+
tagValueList = [
923+
924+
],
925+
nameValueMap = {
926+
CONNECTION_ID = IF8j4HbdLP0,
927+
CONNECTION_URL = tcp+tls://fix-oe.binance.com:9000
928+
}
929+
]
930+
],
931+
correlationIdList = [ any ],
932+
]
933+
]
934+
]
935+
Received an event:
910936
Event [
911937
type = AUTHORIZATION_STATUS,
912938
messageList = [
913939
Message [
914940
type = AUTHORIZATION_SUCCESS,
915941
recapType = UNKNOWN,
916942
time = 1970-01-01T00:00:00.000000000Z,
917-
timeReceived = 2021-05-25T05:05:15.892366000Z,
943+
timeReceived = 2025-08-08T18:50:06.819417404Z,
918944
elementList = [
919945
Element [
920-
tagValueMap = {
921-
96 = 0srtt0WetUTYHiTpvyWnC+XKKHCzQQIJ/8G9lE4KVxM=,
922-
98 = 0,
923-
108 = 15,
924-
554 = 26abh7of52i
946+
tagValueList = [
947+
(35, "A"),
948+
(98, "0"),
949+
(108, "60"),
950+
(25037, "e9ed8253-8f49-4a1b-bba9-718ff73991e5")
951+
],
952+
nameValueMap = {
953+
925954
}
926955
]
927956
],
928-
correlationIdList = [ same correlation id for subscription and request ]
957+
correlationIdList = [ any ],
929958
]
930959
]
931960
]
932-
Received an event of type FIX:
961+
Received an event:
933962
Event [
934963
type = FIX,
935964
messageList = [
936965
Message [
937966
type = FIX,
938967
recapType = UNKNOWN,
939968
time = 1970-01-01T00:00:00.000000000Z,
940-
timeReceived = 2021-05-25T05:05:15.984090000Z,
969+
timeReceived = 2025-08-08T18:50:07.820112736Z,
941970
elementList = [
942971
Element [
943-
tagValueMap = {
944-
11 = 6d4eb0fb-2229-469f-873e-557dd78ac11e,
945-
17 = b7caec79-1bc8-460e-af28-6489cf12f45e,
946-
20 = 0,
947-
37 = 458acfe5-bdea-46d2-aa87-933cda84163f,
948-
38 = 0.001,
949-
39 = 0,
950-
44 = 20000,
951-
54 = 1,
952-
55 = BTC-USDT,
953-
60 = 20210525-05:05:16.008,
954-
150 = 0
972+
tagValueList = [
973+
(35, "8"),
974+
(17, "100146443390"),
975+
(11, "x-XHKUG2CH-1754679007000"),
976+
(37, "47156106695"),
977+
(38, "0.00010000"),
978+
(40, "2"),
979+
(54, "1"),
980+
(55, "BTCUSDT"),
981+
(44, "100000.00000000"),
982+
(59, "1"),
983+
(60, "20250808-18:50:07.819027"),
984+
(25018, "20250808-18:50:07.819027"),
985+
(25001, "3"),
986+
(150, "0"),
987+
(14, "0.00000000"),
988+
(151, "0.00010000"),
989+
(25017, "0.00000000"),
990+
(1057, "Y"),
991+
(32, "0.00000000"),
992+
(39, "0"),
993+
(636, "Y"),
994+
(25023, "20250808-18:50:07.819027")
995+
],
996+
nameValueMap = {
997+
955998
}
956999
]
9571000
],
958-
correlationIdList = [ same correlation id for subscription and request ]
1001+
correlationIdList = [ any ],
9591002
]
9601003
]
9611004
]
@@ -976,7 +1019,7 @@ std::vector<Event> eventList = session.getEventQueue().purge();
9761019
An example can be found [here](example/src/market_data_advanced_subscription/main.cpp).
9771020

9781021
#### Thread safety
979-
* The following methods are implemented to be thread-safe: `Session::sendRequest`, `Session::subscribe`, `Session::sendRequestByFix`, `Session::subscribeByFix`, `Session::setTimer`, all public methods in `Queue`.
1022+
* The following methods are implemented to be thread-safe: `Session::sendRequest`, `Session::subscribe`, `Session::sendRequestByFix`, `Session::setTimer`, all public methods in `Queue`.
9801023
* 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.
9811024

9821025
#### Enable library logging
@@ -1007,7 +1050,7 @@ Logger* Logger::logger = &myLogger;
10071050

10081051
#### Set timer
10091052

1010-
[C++](example/src/utility_set_timer/main.cpp)
1053+
[C++](example/src/set_timer/main.cpp)
10111054

10121055
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.
10131056
```
@@ -1019,6 +1062,16 @@ sessionPtr->setTimer(
10191062
[]() { std::cout << std::string("Timer success handler is triggered at ") + UtilTime::getISOTimestamp(UtilTime::now()) << std::endl; });
10201063
```
10211064

1065+
#### Heartbeat
1066+
1067+
[C++](example/src/heartbeat/main.cpp)
1068+
1069+
To receive heartbeat events, instantiate a `Subscription` object with field `HEARTBEAT` and subscribe it.
1070+
```
1071+
Subscription subscription("", "", "HEARTBEAT", "HEARTBEAT_INTERVAL_MILLISECONDS=1000");
1072+
session.subscribe(subscription);
1073+
```
1074+
10221075
## Performance Tuning
10231076
* Turn on compiler optimization flags (e.g. `cmake -DCMAKE_BUILD_TYPE=Release ...`).
10241077
* 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.

binding/java/example/execution_management_simple_request/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class Main {
1010
static class MyEventHandler extends EventHandler {
1111
@Override
1212
public void processEvent(Event event, Session session) {
13-
System.out.println(String.format("Received an event:\n%s", event.toStringPretty(2, 2)));
13+
System.out.println(String.format("Received an event:\n%s", event.toPrettyString(2, 2)));
1414
}
1515
}
1616
public static void main(String[] args) {

binding/java/example/execution_management_simple_subscription/Main.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ static class MyEventHandler extends EventHandler {
1414
@Override
1515
public void processEvent(Event event, Session session) {
1616
if (event.getType() == Event.Type.SUBSCRIPTION_STATUS) {
17-
System.out.println(String.format("Received an event of type SUBSCRIPTION_STATUS:\n%s", event.toStringPretty(2, 2)));
17+
System.out.println(String.format("Received an event of type SUBSCRIPTION_STATUS:\n%s", event.toPrettyString(2, 2)));
1818
var message = event.getMessageList().get(0);
1919
if (message.getType() == Message.Type.SUBSCRIPTION_STARTED) {
2020
var request = new Request(Request.Operation.CREATE_ORDER, "okx", "BTC-USDT");
@@ -26,7 +26,7 @@ public void processEvent(Event event, Session session) {
2626
session.sendRequest(request);
2727
}
2828
} else if (event.getType() == Event.Type.SUBSCRIPTION_DATA) {
29-
System.out.println(String.format("Received an event of type SUBSCRIPTION_DATA:\n%s", event.toStringPretty(2, 2)));
29+
System.out.println(String.format("Received an event of type SUBSCRIPTION_DATA:\n%s", event.toPrettyString(2, 2)));
3030
}
3131
}
3232
}

binding/java/example/fix_simple/Main.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ static class MyEventHandler extends EventHandler {
1515
@Override
1616
public void processEvent(Event event, Session session) {
1717
if (event.getType() == Event.Type.AUTHORIZATION_STATUS) {
18-
System.out.println(String.format("Received an event of type AUTHORIZATION_STATUS:\n%s", event.toStringPretty(2, 2)));
18+
System.out.println(String.format("Received an event of type AUTHORIZATION_STATUS:\n%s", event.toPrettyString(2, 2)));
1919
var message = event.getMessageList().get(0);
2020
if (message.getType() == Message.Type.AUTHORIZATION_SUCCESS) {
2121
var request = new Request(Request.Operation.FIX, "coinbase", "", "same correlation id for subscription and request");
@@ -28,11 +28,11 @@ public void processEvent(Event event, Session session) {
2828
param.add(new PairIntString(38, "0.001"));
2929
param.add(new PairIntString(40, "2"));
3030
param.add(new PairIntString(59, "1"));
31-
request.appendParamFix(param);
31+
request.appendFixParam(param);
3232
session.sendRequestByFix(request);
3333
}
3434
} else if (event.getType() == Event.Type.FIX) {
35-
System.out.println(String.format("Received an event of type FIX:\n%s", event.toStringPretty(2, 2)));
35+
System.out.println(String.format("Received an event of type FIX:\n%s", event.toPrettyString(2, 2)));
3636
}
3737
}
3838
}
@@ -55,7 +55,7 @@ public static void main(String[] args) {
5555
var config = new SessionConfigs();
5656
var session = new Session(option, config, eventHandler);
5757
var subscription = new Subscription("coinbase", "", "FIX", "", "same correlation id for subscription and request");
58-
session.subscribeByFix(subscription);
58+
session.subscribe(subscription);
5959
try {
6060
Thread.sleep(10000);
6161
} catch (InterruptedException e) {

binding/java/example/market_data_simple_request/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class Main {
1010
static class MyEventHandler extends EventHandler {
1111
@Override
1212
public void processEvent(Event event, Session session) {
13-
System.out.println(String.format("Received an event:\n%s", event.toStringPretty(2, 2)));
13+
System.out.println(String.format("Received an event:\n%s", event.toPrettyString(2, 2)));
1414
}
1515
}
1616
public static void main(String[] args) {

0 commit comments

Comments
 (0)