Skip to content

Commit d325ed7

Browse files
committed
feat: google tests, interfaces, coverage, github action, housekeeping, threading work
1 parent fcf1d9e commit d325ed7

33 files changed

Lines changed: 1009 additions & 394 deletions

.github/workflows/build.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ jobs:
3131
3232
- name: Build
3333
run: |
34-
conan install . --build=missing -s build_type=Release
35-
cmake --preset=conan-release
36-
cmake --build --preset=conan-release
34+
conan install . --build=missing -s build_type=Debug
35+
cmake --preset=conan-debug
36+
cmake --build --preset=conan-debug
37+
38+
- name: Test
39+
run: |
40+
ctest --test-dir build/Debug --output-on-failure

.vscode/launch.json

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"version": "0.2.0",
66
"configurations": [
77
{
8-
"name": "(gdb) Launch",
8+
"name": "(gdb) Trader",
99
"type": "cppdbg",
1010
"request": "launch",
1111
"program": "${workspaceFolder}/build/Debug/tradercpp",
@@ -30,6 +30,33 @@
3030
],
3131
"preLaunchTask": "build",
3232
"miDebuggerPath": "/usr/bin/gdb"
33+
},
34+
{
35+
"name": "(gdb) Unit Tests",
36+
"type": "cppdbg",
37+
"request": "launch",
38+
"program": "${workspaceFolder}/build/Debug/tests/unit_tests",
39+
"args": [],
40+
"stopAtEntry": false,
41+
"cwd": "${workspaceFolder}",
42+
"environment": [],
43+
"envFile": "${workspaceFolder}/.env",
44+
"externalConsole": false,
45+
"MIMode": "gdb",
46+
"setupCommands": [
47+
{
48+
"description": "Enable pretty-printing for gdb",
49+
"text": "-enable-pretty-printing",
50+
"ignoreFailures": true
51+
},
52+
{
53+
"description": "Set Disassembly Flavor to Intel",
54+
"text": "-gdb-set disassembly-flavor intel",
55+
"ignoreFailures": true
56+
}
57+
],
58+
"preLaunchTask": "build",
59+
"miDebuggerPath": "/usr/bin/gdb"
3360
}
3461
]
3562
}

CMakeLists.txt

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@ project(tradercpp)
44
set(CMAKE_CXX_STANDARD 23)
55
set(CMAKE_CXX_STANDARD_REQUIRED YES)
66

7-
# Define the source files
8-
file(GLOB_RECURSE SOURCES "src/*.cpp")
9-
10-
# Create the executable
11-
add_executable(tradercpp ${SOURCES})
12-
137
############################################
14-
158
# Conan-generated toolchain setup
9+
1610
include(${CMAKE_BINARY_DIR}/conan_toolchain.cmake OPTIONAL RESULT_VARIABLE _found_toolchain)
1711
if(_found_toolchain)
1812
message(STATUS "Conan toolchain included")
@@ -21,18 +15,46 @@ endif()
2115
# Find packages from Conan
2216
find_package(concurrentqueue REQUIRED)
2317
find_package(ftxui REQUIRED)
18+
find_package(GTest REQUIRED)
2419
find_package(OpenSSL REQUIRED)
2520
find_package(quickfix REQUIRED CONFIG)
2621
find_package(libsodium REQUIRED)
2722
find_package(spdlog REQUIRED)
2823

29-
# Link them to the executable
30-
target_link_libraries(tradercpp PRIVATE
31-
concurrentqueue::concurrentqueue
32-
ftxui::ftxui
33-
OpenSSL::SSL
34-
OpenSSL::Crypto
35-
quickfix::quickfix
36-
libsodium::libsodium
37-
spdlog::spdlog
24+
############################################
25+
# Collect source files
26+
27+
file(GLOB_RECURSE ALL_SOURCES "src/*.cpp")
28+
# Separate main.cpp from the rest of the sources
29+
list(FILTER ALL_SOURCES EXCLUDE REGEX ".*main\\.cpp$")
30+
set(SOURCES ${ALL_SOURCES})
31+
32+
############################################
33+
# Create core logic library
34+
add_library(traderlib STATIC ${SOURCES})
35+
36+
# TODO: MAKE CONDITIONAL
37+
# Add coverage flags to the traderlib target
38+
target_compile_options(traderlib PRIVATE -O0 -g --coverage)
39+
target_link_libraries(traderlib PRIVATE --coverage)
40+
41+
# Link dependencies to the library (so both exe and tests get them)
42+
target_link_libraries(traderlib PUBLIC
43+
concurrentqueue::concurrentqueue
44+
ftxui::ftxui
45+
OpenSSL::SSL
46+
OpenSSL::Crypto
47+
quickfix::quickfix
48+
libsodium::libsodium
49+
spdlog::spdlog
3850
)
51+
52+
############################################
53+
# Create main executable
54+
add_executable(tradercpp "src/main.cpp")
55+
target_link_libraries(tradercpp PRIVATE traderlib)
56+
57+
############################################
58+
# Enable `ctest`
59+
enable_testing()
60+
add_subdirectory(tests)

Makefile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ help: Makefile
1313
@echo " Choose a command to run:"
1414
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
1515

16-
## withenv: 😭 `make` executes every line as a new shell. this is a workaround. `make withenv RECIPE=init`
16+
## withenv: 😭 `make` executes every line as a new shell. this is a workaround => `make withenv RECIPE=init`
1717
.PHONY: withenv
1818
withenv:
1919
test -e .env || cp .env.example .env
@@ -34,6 +34,13 @@ init:
3434
build-debug:
3535
cmake --build --preset=conan-debug
3636

37+
## test: 🧪 run google-test
38+
.PHONY: test
39+
test:
40+
cmake --build --preset=conan-debug
41+
ctest --test-dir build/Debug --output-on-failure
42+
lcov --gcov-tool gcov --capture --directory . --output-file lcov.info
43+
3744
## build-release: 🔨🔨 compile (prod)
3845
.PHONY: build-release
3946
build-release:

README.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,22 @@ a proof-of-concept, showcasing some c++ coding combined with some fintech concep
77

88
## REQUIREMENTS
99
- C++20
10-
- Conan
11-
- CMake
12-
- a local proxy (e.g. stunnel) for TLS encryption
10+
- `Conan` (and a conan profile)
11+
- `CMake`
12+
- a local proxy for TLS encryption (e.g. `stunnel`)
13+
- a Binance account, with an Ed25519 token that has FIX read permissions enabled
14+
- `lcov`
1315

1416
## BUILD AND RUN
1517
(NB: this app uses `make` as a task runner, but it's not essential)
16-
1. `make init`
17-
2. `make build-debug`
18-
3. `make withenv RECIPE=run-debug`
18+
1. copy `.env.example` to `.env`, and set your public/private keys
19+
2. run an SSL tunnel (`stunnel binance/stunnel_prod.conf`)
20+
3. `make init`
21+
4. `make build-debug`
22+
5. `make withenv RECIPE=run-debug`
23+
24+
## TEST
25+
`make test`
1926

2027
## HELP
2128
`make`
@@ -28,29 +35,32 @@ a proof-of-concept, showcasing some c++ coding combined with some fintech concep
2835
- ✅ subscribe to price updates
2936
- create a basic trading signal (e.g. standard deviations)
3037
- fire an order
31-
- test in the Binance test enviroment
38+
- test in the Binance test environment
3239

3340

3441
## NON-FUNCTIONAL
3542
- ✅ basic cpp app to start with
3643
- ✅ makefile and build chain
3744
- ✅ package management
3845
- ✅ debugging
39-
- UI can come later (perhaps explore curses)
40-
- logging
41-
- fast
46+
- ✅ UI
47+
- ✅ publish messages to thread-safe queue
48+
- ✅ consume messages from thread-safe queue on a worker thread
49+
- interrupt/ctrl+c signal
50+
- 60fps limit
51+
- ✅ logging
52+
- ✅ fast
4253
- structured
4354
- basic schema (severity, correlationId)
44-
- dependency injection
45-
- single-threaded to start with, then re-architect (and mermaid diagram)
46-
- demo video (https://asciinema.org)
55+
- ✅ dependency injection
56+
- ✅ single-threaded to start with, then re-architect (and mermaid diagram)
4757
- nix virtual environment
4858
- decimal type
4959
- sparse arrays
5060
- release binaries on github
5161
- ccache.dev
5262
- zeromq + protobufs?
53-
- interrupt/ctrl+c signal
63+
- valgrind/cachegrind
5464

5565
# STANDARDS
5666
- high unit-test coverage + badge
@@ -69,6 +79,7 @@ a proof-of-concept, showcasing some c++ coding combined with some fintech concep
6979
- grafana+tempo via docker-compose
7080
- conventional commits
7181
- automated semantic versioning
82+
- memory-mapped files
7283

7384
# CREDITS
74-
- https://github.com/binance/binance-fix-connector-python
85+
- https://github.com/binance/binance-fix-connector-python

conanfile.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ openssl/3.5.2
55
ftxui/6.0.2
66
concurrentqueue/1.0.4
77
spdlog/1.15.3
8+
gtest/1.17.0
89

910
[generators]
1011
CMakeToolchain

src/binance/Auth.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,23 @@
1717

1818
namespace Binance {
1919

20-
// static member function
21-
std::vector<unsigned char> Auth::getSeedFromPem(const std::string& pemPath) {
22-
FILE* fp = fopen(pemPath.c_str(), "r");
20+
Auth::Auth(std::string& apiKey, std::string& privatePemPath) : apiKey_(apiKey), privatePemPath_(privatePemPath) {
21+
if (sodium_init() < 0)
22+
throw std::runtime_error("libsodium failed to initialize");
23+
}
24+
25+
const std::string& Auth::getApiKey() const {
26+
return apiKey_;
27+
}
28+
29+
std::string Auth::signPayload(const std::string& payload) {
30+
const std::vector<unsigned char> seed = getSeedFromPem();
31+
const std::string signature = signPayload(payload, seed);
32+
return signature;
33+
}
34+
35+
std::vector<unsigned char> Auth::getSeedFromPem() const {
36+
FILE* fp = fopen(privatePemPath_.c_str(), "r");
2337
if (!fp) throw std::runtime_error("Failed to open PEM file");
2438
EVP_PKEY* pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr);
2539
fclose(fp);
@@ -36,7 +50,7 @@ std::vector<unsigned char> Auth::getSeedFromPem(const std::string& pemPath) {
3650
return seed;
3751
}
3852

39-
// static member function
53+
// static
4054
std::string Auth::signPayload(const std::string& payload, const std::vector<unsigned char>& seed) {
4155
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
4256
unsigned char sk[crypto_sign_SECRETKEYBYTES]; // 64 bytes
@@ -61,4 +75,13 @@ std::string Auth::signPayload(const std::string& payload, const std::vector<unsi
6175
return b64;
6276
}
6377

78+
void Auth::clearKeys()
79+
{
80+
// logon successful, nullify access keys
81+
std::ranges::fill(apiKey_, 0);
82+
apiKey_.clear();
83+
std::ranges::fill(privatePemPath_, 0);
84+
privatePemPath_.clear();
85+
}
86+
6487
}

src/binance/Auth.h

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
#ifndef BINANCEAUTH_H
22
#define BINANCEAUTH_H
33

4-
#include <vector>
54
#include <string>
5+
#include <vector>
6+
#include "IAuth.h"
67

78
namespace Binance {
89

910
/// @brief Binance's proprietary FIX authentication mechanism - helper functions
10-
class Auth final {
11+
class Auth final : public IAuth {
1112
public:
12-
/// Fetch a 32-byte Ed25519 seed from a private key PEM file (using OpenSSL)
13-
///
14-
/// @param pemPath path to a private-key PEM file
15-
/// @return 32-byte Ed25519 seed
16-
static std::vector<unsigned char> getSeedFromPem(const std::string& pemPath);
17-
13+
Auth(std::string& apiKey, std::string& privatePemPath);
14+
std::string signPayload(const std::string& payload) override;
1815
/// Generate a payload signature using a private key.
1916
/// Expand the key to a 64-byte secret key (using libsodium), sign a payload, output base64 signature.
20-
///
2117
/// @param payload payload to be signed
2218
/// @param seed a 32-byte Ed25519 seed
2319
/// @return base64 payload signature
2420
static std::string signPayload(const std::string& payload, const std::vector<unsigned char>& seed);
21+
const std::string& getApiKey() const override;
22+
void clearKeys() override;
23+
24+
private:
25+
std::string& apiKey_;
26+
std::string& privatePemPath_;
27+
/// Fetch a 32-byte Ed25519 seed from a private key PEM file (using OpenSSL)
28+
/// @return 32-byte Ed25519 seed
29+
std::vector<unsigned char> getSeedFromPem() const;
2530
};
2631

2732
}

src/binance/Config.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
#include <stdexcept>
55
#include <string>
66
#include <vector>
7-
#include "Config.h"
87
#include "spdlog/spdlog.h"
8+
#include "Config.h"
99

1010
namespace Binance {
1111

@@ -20,6 +20,8 @@ std::string getEnvOrThrow(const char* key) {
2020
throw std::runtime_error(std::format("envvar not defined, key [{}]", key));
2121
};
2222

23+
// TODO: for keys, use `std::vector<unsigned char>` instead of string
24+
2325
/// @brief load Binance configuration from env
2426
/// (static member function)
2527
/// @return Config object

src/binance/Config.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ namespace Binance {
99
/// @brief Binance config parameters, fetched from env
1010
struct Config{
1111
public:
12-
const std::string apiKey, privateKeyPath, fixConfigPath;
12+
// TODO: MAKE UNIQUE POINTERS
13+
std::string apiKey, privateKeyPath;
14+
const std::string fixConfigPath;
1315
const std::vector<std::string> symbols;
1416
//
1517
static Config fromEnv();

0 commit comments

Comments
 (0)