Skip to content

Commit 41e449f

Browse files
committed
feat: first working UI with binance connectivity :)
1 parent ea7839a commit 41e449f

12 files changed

Lines changed: 365 additions & 161 deletions

File tree

binance/fixconfig

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ ResetSeqNumFlag=Y
1010
EncryptMethod=0
1111
CheckLatency=N
1212
ORDER_TIMEOUT=30000
13-
FileStorePath=qf_files
14-
FileLogPath=qf_logs
1513
UseDataDictionary=Y
1614
DataDictionary=binance/spot-fix-md.xml
1715
SocketConnectHost=127.0.0.1
1816
SocketConnectPort=5001
17+
FileStorePath=qf_files
18+
FileLogPath=qf_logs
19+
FileLogHeartbeats=Y
20+
FileLogMessages=Y
21+
FileLogEvent=Y
1922

2023
[SESSION]
2124
BeginString=FIX.4.4

src/binance/Auth.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
namespace Binance {
1919

20+
// static member function
2021
std::vector<unsigned char> Auth::getSeedFromPem(const std::string& pemPath) {
2122
FILE* fp = fopen(pemPath.c_str(), "r");
2223
if (!fp) throw std::runtime_error("Failed to open PEM file");
@@ -35,6 +36,7 @@ std::vector<unsigned char> Auth::getSeedFromPem(const std::string& pemPath) {
3536
return seed;
3637
}
3738

39+
// static member function
3840
std::string Auth::signPayload(const std::string& payload, const std::vector<unsigned char>& seed) {
3941
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
4042
unsigned char sk[crypto_sign_SECRETKEYBYTES]; // 64 bytes

src/binance/Config.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Binance {
88

9+
// static member function
910
Config Config::fromEnv() {
1011
auto getEnvOrThrow = [](const char* key) -> std::string {
1112
if (const char* val = std::getenv(key)) {

src/binance/Config.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ namespace Binance {
88
/// @brief Binance config parameters, fetched from env
99
struct Config{
1010
public:
11-
const std::string apiKey;
12-
const std::string privateKeyPath;
13-
const std::string fixConfigPath;
14-
11+
const std::string apiKey, privateKeyPath, fixConfigPath;
1512
static Config fromEnv();
1613
};
1714

src/binance/FixApp.cpp

Lines changed: 11 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44
#include <format>
55
#include <iostream>
66
#include <iomanip>
7-
#include <map>
8-
#include <ranges>
97
#include <stdexcept>
108
#include <string>
11-
#include <tuple>
129
#include <utility>
1310
#include <vector>
1411
#include "Auth.h"
@@ -95,161 +92,35 @@ void FixApp::toApp(FIX::Message& msg, const FIX::SessionID& sessionId) noexcept(
9592
const FIX::Header& header = msg.getHeader();
9693
FIX::MsgType msgType;
9794
header.getField(msgType);
98-
std::cout << std::format("toApp, session Id [{}], type [{}], message [{}]",
99-
sessionId.toString(), msgType.getString(), replaceSoh(msg.toString())) << std::endl;
95+
// std::cout << std::format("toApp, session Id [{}], type [{}], message [{}]",
96+
// sessionId.toString(), msgType.getString(), replaceSoh(msg.toString())) << std::endl;
10097
};
10198
void FixApp::fromAdmin(const FIX::Message& msg, const FIX::SessionID& sessionId) noexcept(false) {
10299
const FIX::Header& header = msg.getHeader();
103100
FIX::MsgType msgType;
104101
header.getField(msgType);
105-
std::cout << std::format("fromAdmin, session Id [{}], type [{}], message [{}]",
106-
sessionId.toString(), msgType.getString(), replaceSoh(msg.toString())) << std::endl;
102+
// std::cout << std::format("fromAdmin, session Id [{}], type [{}], message [{}]",
103+
// sessionId.toString(), msgType.getString(), replaceSoh(msg.toString())) << std::endl;
107104
};
108105
void FixApp::fromApp(const FIX::Message& msg, const FIX::SessionID& sessionId) noexcept(false) {
109106
FIX::MessageCracker::crack(msg, sessionId);
110107
// std::cout << std::format("fromApp, session Id [{}], message [{}]",
111108
// sessionId.toString(), replaceSoh(msg.toString())) << std::endl;
112109
}
113110

114-
115-
void FixApp::printBook() {
116-
// Column headers
117-
std::cout << std::left
118-
<< std::setw(10) << "BID_SZ"
119-
<< std::setw(10) << "BID"
120-
<< std::setw(10) << "ASK"
121-
<< std::setw(10) << "ASK_SZ"
122-
<< "\n";
123-
// Divider
124-
std::cout << std::string(40, '-') << "\n";
125-
126-
// print top 10x
127-
auto data = std::vector<std::tuple<double, double, double, double>>(10, std::make_tuple(0, 0, 0, 0));
128-
129-
// bids
130-
int i = 0;
131-
for (auto&& [px, sz] : std::views::reverse(bidMap_)) {
132-
std::get<0>(data[i]) = sz;
133-
std::get<1>(data[i]) = px;
134-
i++;
135-
if (i > 9)
136-
break;
137-
}
138-
139-
// offers
140-
i = 0;
141-
for (const auto& [px, sz] : offerMap_) {
142-
std::get<2>(data[i]) = px;
143-
std::get<3>(data[i]) = sz;
144-
i++;
145-
if (i > 9)
146-
break;
147-
}
148-
149-
for (auto item: data) {
150-
std::cout << std::left
151-
<< std::setw(10) << std::get<0>(item)
152-
<< std::setw(10) << std::get<1>(item)
153-
<< std::setw(10) << std::get<2>(item)
154-
<< std::setw(10) << std::get<3>(item)
155-
<< "\n";
156-
}
157-
158-
std::cout << std::flush;
159-
}
160111
void FixApp::onMessage(const FIX44::MarketDataSnapshotFullRefresh& m, const FIX::SessionID& sessionID) {
161-
FIX::Symbol symbol;
162-
m.get(symbol);
163-
std::cout << std::format("MD snapshot message, symbol [{}]", symbol.getString()) << std::endl;
164-
std::string symbolValue = symbol.getValue();
165-
if (symbolValue != "BTCUSDT") {
166-
std::cout << std::format("wrong symbol, skipping snapshot. value [{}]", symbolValue) << std::endl;
167-
return;
168-
}
169-
bidMap_.clear();
170-
offerMap_.clear();
171-
FIX::NoMDEntries noMDEntries;
172-
m.get(noMDEntries);
173-
int numEntries = noMDEntries.getValue();
174-
for (int i = 1; i <= numEntries; i++) {
175-
FIX44::MarketDataSnapshotFullRefresh::NoMDEntries group;
176-
m.getGroup(i, group);
177-
FIX::MDEntryType entryType;
178-
FIX::MDEntryPx px;
179-
FIX::MDEntrySize sz;
180-
group.get(entryType);
181-
group.get(px);
182-
group.get(sz);
183-
184-
if (entryType == FIX::MDEntryType_BID) {
185-
bidMap_[px.getValue()] = sz.getValue();
186-
} else if (entryType == FIX::MDEntryType_OFFER) {
187-
offerMap_[px.getValue()] = sz.getValue();
188-
} else {
189-
std::cout << std::format("unknown bid/offer type [{}]", entryType.getString()) << std::endl;
190-
}
191-
}
192-
193-
printBook();
112+
auto msgPtr = std::make_shared<FIX44::MarketDataSnapshotFullRefresh>(m);
113+
queue.enqueue(msgPtr);
194114
}
195-
void FixApp::onMessage(const FIX44::MarketDataIncrementalRefresh& message, const FIX::SessionID& sessionID) {
196-
FIX::NoMDEntries noMDEntries;
197-
message.get(noMDEntries);
198-
int numEntries = noMDEntries.getValue();
199-
for (int i = 1; i <= numEntries; i++) {
200-
FIX44::MarketDataIncrementalRefresh::NoMDEntries group;
201-
message.getGroup(i, group);
202-
203-
FIX::Symbol symbol;
204-
if (group.isSetField(FIX::FIELD::Symbol)) {
205-
group.get(symbol);
206-
}
207-
// std::string symbolValue = symbol.getValue();
208-
// if (symbolValue != "BTCUSDT") {
209-
// std::cout << std::format("wrong symbol, skipping increment. value [{}]", symbolValue) << std::endl;
210-
// continue;
211-
// }
212-
213-
FIX::MDUpdateAction action;
214-
FIX::MDEntryType entryType;
215-
FIX::MDEntryPx px;
216-
group.get(action);
217-
group.get(entryType);
218-
group.get(px);
219-
220-
if (entryType != FIX::MDEntryType_BID && entryType != FIX::MDEntryType_OFFER) {
221-
std::cout << std::format("unknown entry type, skipping. value [{}]", entryType.getString()) << std::endl;
222-
continue;
223-
}
224-
225-
if (action.getValue() == FIX::MDUpdateAction_NEW || action.getValue() == FIX::MDUpdateAction_CHANGE) {
226-
if (group.isSetField(FIX::FIELD::MDEntrySize)) {
227-
FIX::MDEntrySize sz;
228-
group.get(sz);
229-
if (entryType == FIX::MDEntryType_BID) {
230-
bidMap_[px.getValue()] = sz.getValue();
231-
} else if (entryType == FIX::MDEntryType_OFFER) {
232-
offerMap_[px.getValue()] = sz.getValue();
233-
}
234-
}
235-
} else if (action.getValue() == FIX::MDUpdateAction_DELETE) {
236-
if (entryType == FIX::MDEntryType_BID) {
237-
bidMap_.erase(px.getValue());
238-
} else if (entryType == FIX::MDEntryType_OFFER) {
239-
offerMap_.erase(px.getValue());
240-
}
241-
} else {
242-
std::cout << std::format("unknown action. value [{}]", action.getValue()) << std::endl;
243-
}
244-
}
245-
246-
printBook();
115+
void FixApp::onMessage(const FIX44::MarketDataIncrementalRefresh& m, const FIX::SessionID& sessionID) {
116+
auto msgPtr = std::make_shared<FIX44::MarketDataIncrementalRefresh>(m);
117+
queue.enqueue(msgPtr);
247118
}
248119

249120

250121
// PUBLIC
251122

252-
FixApp::FixApp(std::string apiKey, std::string privatePemPath) : bidMap_{}, offerMap_{} {
123+
FixApp::FixApp(std::string apiKey, std::string privatePemPath) {
253124
apiKey_ = std::move(apiKey);
254125
privatePemPath_ = std::move(privatePemPath);
255126

@@ -271,7 +142,7 @@ void FixApp::subscribeToDepth(const FIX::SessionID& sessionId) {
271142
FIX::SubscriptionRequestType_SNAPSHOT_PLUS_UPDATES));
272143

273144
// Set market depth
274-
marketDataRequest.set(FIX::MarketDepth(5)); // 1 = Top of book
145+
marketDataRequest.set(FIX::MarketDepth(15)); // 1 = Top of book
275146

276147
// Create NoMDEntryTypes group for requesting BID and OFFER
277148
FIX44::MarketDataRequest::NoMDEntryTypes entryTypeGroup;

src/binance/FixApp.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
#include <quickfix/Application.h>
55
#include <quickfix/SessionID.h>
6+
#include <quickfix/fix44/Message.h>
67
#include <quickfix/MessageCracker.h>
7-
#include <map>
8+
#include "concurrentqueue.h"
89

910
namespace Binance {
1011

@@ -14,14 +15,15 @@ class FixApp final : public FIX::Application, public FIX::MessageCracker {
1415
FixApp(std::string apiKey, std::string privatePemPath);
1516
~FixApp() override = default;
1617
void subscribeToDepth(const FIX::SessionID& sessionId);
17-
/// @brief print top ten order book levels
18-
void printBook();
18+
19+
// TODO: performance implication of a polymorphic queue
20+
// TODO: perhaps better to run two queues, or something else
21+
// TODO: we will have one of these per instrument
22+
moodycamel::ConcurrentQueue<std::shared_ptr<FIX44::Message>> queue;
1923

2024
private:
2125
// TODO: no need to persist access tokens for lifetime of app
2226
std::string apiKey_, privatePemPath_;
23-
std::map<double, double> bidMap_;
24-
std::map<double, double> offerMap_;
2527

2628
void onCreate(const FIX::SessionID&) override;
2729
void onLogon(const FIX::SessionID&) override;

src/binance/Init.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,22 @@ Init::Init(std::unique_ptr<FixApp> fixApp,
1515
std::unique_ptr<FIX::FileStoreFactory> fileStoreFactory,
1616
std::unique_ptr<FIX::SessionSettings> settings,
1717
std::unique_ptr<FIX::FileLogFactory> fileLogFactory)
18-
: app_(std::move(fixApp)),
18+
: app(std::move(fixApp)),
1919
store_(std::move(fileStoreFactory)),
2020
settings_(std::move(settings)),
2121
log_(std::move(fileLogFactory)),
22-
initiator_(*app_, *store_, *settings_, *log_) {}
22+
initiator_(*app, *store_, *settings_, *log_) {}
2323

24+
// static member function
2425
Init Init::fromConf(Config& conf) {
2526
auto app = std::make_unique<FixApp>(conf.apiKey, conf.privateKeyPath);
2627
auto settings = std::make_unique<FIX::SessionSettings>(conf.fixConfigPath);
2728
auto storeFactory = std::make_unique<FIX::FileStoreFactory>(*settings);
2829
auto logFactory = std::make_unique<FIX::FileLogFactory>(*settings);
2930

31+
// hand over object ownership to the instance being created (by the static function)
32+
33+
// TODO: is this being moved twice, because of the std::move() calls in the initializer list?
3034
return {std::move(app),
3135
std::move(storeFactory),
3236
std::move(settings),

src/binance/Init.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,18 @@ namespace Binance {
1313
/// @brief Binance DI container
1414
class Init final {
1515
public:
16-
Init(
17-
std::unique_ptr<FixApp> fixApp,
16+
Init(std::unique_ptr<FixApp> fixApp,
1817
std::unique_ptr<FIX::FileStoreFactory> fileStoreFactory,
1918
std::unique_ptr<FIX::SessionSettings> settings,
20-
std::unique_ptr<FIX::FileLogFactory> fileLogFactory);
19+
std::unique_ptr<FIX::FileLogFactory> fileLogFactory);
2120
~Init();
2221
static Init fromConf(Config& conf);
2322
void start();
2423
void stop();
24+
std::unique_ptr<FixApp> app;
2525

2626
private:
2727
// TODO: rename
28-
std::unique_ptr<FixApp> app_;
2928
std::unique_ptr<FIX::FileStoreFactory> store_;
3029
std::unique_ptr<FIX::SessionSettings> settings_;
3130
std::unique_ptr<FIX::FileLogFactory> log_;

src/main.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
1-
#include <iostream>
1+
#include <chrono>
22
#include <format>
3+
#include <iostream>
4+
#include <string>
5+
#include <thread>
6+
#include "concurrentqueue.h"
37
#include "binance/Config.h"
48
#include "binance/Init.h"
9+
#include "ui/TableApp.h"
510

611
int main() {
712
std::cout << std::format("hello") << std::endl;
813

14+
// BINANCE MARKET DATA GENERATOR
915
auto bConf = Binance::Config::fromEnv();
1016
auto binance = Binance::Init::fromConf(bConf);
1117
binance.start();
1218

13-
std::cout << "Press any key to quit..." << std::endl;
14-
std::cin.get();
19+
// UI APP
20+
UI::TableApp app = UI::TableApp(binance.app->queue);
21+
app.start();
1522

23+
if (app.thread_exception) {
24+
std::rethrow_exception(app.thread_exception);
25+
}
1626
binance.stop();
17-
1827
std::cout << std::format("goodbye") << std::endl;
1928
}

src/ui/BidAsk.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef UIBIDASK_H
2+
#define UIBIDASK_H
3+
4+
#include <cmath>
5+
6+
namespace UI {
7+
8+
/// @brief Binance config parameters, fetched from env
9+
struct BidAsk{
10+
public:
11+
double bid_sz = NAN;
12+
double bid_px = NAN;
13+
double ask_px = NAN;
14+
double ask_sz = NAN;
15+
};
16+
17+
}
18+
19+
#endif // UIBIDASK_H

0 commit comments

Comments
 (0)