Skip to content

Commit 3ba4cec

Browse files
committed
Looping around tick data from multiple symbols
1 parent 16fb2c3 commit 3ba4cec

8 files changed

Lines changed: 101 additions & 71 deletions

File tree

include/models/priceData.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
// ---------------------------------------
66
#pragma once
77
#include <chrono>
8+
#include <string>
89

910
struct PriceData {
1011
double ask;
1112
double bid;
1213
std::chrono::system_clock::time_point timestamp;
14+
std::string symbol;
1315

1416
// Constructor for easy creation
15-
PriceData(double ask, double bid, const std::chrono::system_clock::time_point& ts)
16-
: ask(ask), bid(bid), timestamp(ts) {}
17+
PriceData(double ask, double bid, const std::chrono::system_clock::time_point& ts, const std::string& symbol)
18+
: ask(ask), bid(bid), timestamp(ts), symbol(symbol) {}
1719

18-
PriceData() : ask(0.0), bid(0.0), timestamp{} {}
20+
PriceData() : ask(0.0), bid(0.0), timestamp{}, symbol("") {}
1921
};

include/operations.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Backtesting Engine in C++
2+
//
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
4+
// This code is licensed under MIT license (see LICENSE.txt for details)
5+
// ---------------------------------------
6+
7+
#pragma once
8+
#include <vector>
9+
#include "models/priceData.hpp"
10+
11+
class Operations {
12+
13+
public:
14+
static void run(const std::vector<PriceData>& priceData);
15+
};

include/sqlManager.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111

1212
class SqlManager {
1313
public:
14-
static std::vector<PriceData> streamPriceData(const DatabaseConnection& db, int LAST_MONTHS = 1);
15-
static std::string getBaseQuery(int LAST_MONTHS = 1);
16-
private:
17-
static constexpr int STREAM_LIMIT = 200000;
14+
static std::vector<PriceData> streamPriceData(const DatabaseConnection& db, const std::vector<std::string>& symbols, int LAST_MONTHS = 1);
15+
static std::string getBaseQuery(const std::vector<std::string>& symbols, int LAST_MONTHS = 1);
16+
1817
};

source/databaseConnection.cpp

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,41 +46,6 @@ DatabaseConnection::DatabaseConnection(const std::string& endpoint, int port,
4646

4747
}
4848

49-
std::vector<PriceData> DatabaseConnection::executeQuery(const std::string& query) const {
50-
std::vector<PriceData> results;
51-
52-
try {
53-
pqxx::connection conn(this->connection_string);
54-
55-
if (!conn.is_open()) {
56-
throw std::invalid_argument("Failed to open database connection");
57-
}
58-
59-
std::cout << "Connected to database successfully!" << std::endl;
60-
61-
pqxx::work txn(conn);
62-
pqxx::result result = txn.exec(query);
63-
64-
// Convert results to PriceData objects
65-
for (const auto& row : result) {
66-
double ask = row[0].as<double>();
67-
double bid = row[1].as<double>();
68-
std::string timestamp_str = row[2].as<std::string>();
69-
70-
auto timestamp = Utilities::parseTimestamp(timestamp_str);
71-
72-
results.emplace_back(ask, bid, timestamp);
73-
}
74-
75-
txn.commit();
76-
77-
} catch (const std::exception& e) {
78-
std::cerr << "Error: " << e.what() << std::endl;
79-
}
80-
81-
return results;
82-
}
83-
8449
std::vector<PriceData> DatabaseConnection::streamQuery(const std::string& query) const {
8550
pqxx::connection conn(this->connection_string);
8651
pqxx::nontransaction txn(conn);
@@ -91,11 +56,12 @@ std::vector<PriceData> DatabaseConnection::streamQuery(const std::string& query)
9156
for (int i = 0; i < (int)result.size(); ++i) {
9257
const auto& row = result[i];
9358
double ask, bid;
94-
auto sv1 = row[0].view();
95-
auto sv2 = row[1].view();
59+
auto symbol = row[0].view();
60+
auto sv1 = row[1].view();
61+
auto sv2 = row[2].view();
9662
std::from_chars(sv1.data(), sv1.data() + sv1.size(), ask);
9763
std::from_chars(sv2.data(), sv2.data() + sv2.size(), bid);
98-
results[i] = PriceData(ask, bid, fastParseTimestamp(row[2].c_str()));
64+
results[i] = PriceData(ask, bid, fastParseTimestamp(row[3].c_str()), std::string(symbol));
9965
}
10066

10167
return results;

source/main.cpp

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "tradeManager.hpp"
2424
#include "jsonParser.hpp"
2525
#include "sqlManager.hpp"
26+
#include "operations.hpp"
2627

2728
using json = nlohmann::json;
2829

@@ -35,29 +36,11 @@ int main(int argc, const char * argv[]) {
3536

3637
JsonParser::parseConfigurationFromBase64(argv[2]);
3738

38-
std::vector<PriceData> ticks = SqlManager::streamPriceData(db, 1);
39+
std::vector<std::string> symbols = {"AUSIDXAUD", "EURUSD"};
40+
std::vector<PriceData> ticks = SqlManager::streamPriceData(db, symbols, 1);
3941
printf("Total ticks streamed: %zu\n", ticks.size());
4042

41-
// print first tick
42-
auto time_t = std::chrono::system_clock::to_time_t(ticks[0].timestamp);
43-
struct tm tm = {};
44-
if (localtime_r(&time_t, &tm) == nullptr) {
45-
std::cerr << "Error: failed to convert timestamp" << std::endl;
46-
}
47-
char buffer[20];
48-
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
49-
printf("First tick: ask=%.4f, bid=%.4f timestamp=%s\n", ticks[0].ask, ticks[0].bid, buffer);
50-
51-
auto tradeManager = TradeManager::getInstance();
52-
53-
std::string tradeId = tradeManager->openTrade(ticks[0].ask, 100000, true);
54-
std::cout << "Opened trade: " << tradeId << std::endl;
55-
56-
size_t openTrades = tradeManager->reviewAccount();
57-
std::cout << "Number of open trades: " << openTrades << std::endl;
58-
59-
bool closed = tradeManager->closeTrade(tradeId);
60-
std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl;
43+
Operations::run(ticks);
6144

6245
return 0;
6346

source/operations.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Backtesting Engine in C++
2+
//
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
4+
// This code is licensed under MIT license (see LICENSE.txt for details)
5+
// ---------------------------------------
6+
7+
#include "operations.hpp"
8+
// std headers
9+
#include <iostream>
10+
#include <vector>
11+
#include <memory>
12+
#include <string>
13+
#include <iomanip>
14+
#include "tradeManager.hpp"
15+
16+
void Operations::run(const std::vector<PriceData>& ticks) {
17+
18+
// Loop aroudn every tick
19+
// Example output:
20+
// symbol=AUSIDXAUD, ask=8602.4000, bid=8599.4000 timestamp=2026-03-12 18:39:01.076
21+
// symbol=AUSIDXAUD, ask=8602.9000, bid=8599.9000 timestamp=2026-03-12 18:39:01.584
22+
// symbol=EURUSD, ask=1.1513, bid=1.1512 timestamp=2026-03-12 18:39:01.644
23+
// symbol=AUSIDXAUD, ask=8602.4000, bid=8599.4000 timestamp=2026-03-12 18:39:01.770
24+
// symbol=AUSIDXAUD, ask=8601.9000, bid=8598.9000 timestamp=2026-03-12 18:39:01.982
25+
26+
for (const auto& tick : ticks) {
27+
// (void)tick;
28+
29+
// print first tick
30+
auto time_t = std::chrono::system_clock::to_time_t(tick.timestamp);
31+
struct tm tm = {};
32+
if (localtime_r(&time_t, &tm) == nullptr) {
33+
std::cerr << "Error: failed to convert timestamp" << std::endl;
34+
}
35+
char buffer[20];
36+
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
37+
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(tick.timestamp.time_since_epoch()) % 1000;
38+
printf("symbol=%s, ask=%.4f, bid=%.4f timestamp=%s.%03lld\n", tick.symbol.c_str(), tick.ask, tick.bid, buffer,
39+
static_cast<long long>(ms.count()));
40+
}
41+
42+
auto tradeManager = TradeManager::getInstance();
43+
44+
// std::string tradeId = tradeManager->openTrade(ticks[0].ask, 100000, true);
45+
// std::cout << "Opened trade: " << tradeId << std::endl;
46+
47+
// size_t openTrades = tradeManager->reviewAccount();
48+
// std::cout << "Number of open trades: " << openTrades << std::endl;
49+
50+
// bool closed = tradeManager->closeTrade(tradeId);
51+
// std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl;
52+
}

source/sqlManager.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
// Backtesting Engine in C++
22
//
3-
// (c) 2025 Ryan McCaffery | https://mccaffers.com
3+
// (c) 2026 Ryan McCaffery | https://mccaffers.com
44
// This code is licensed under MIT license (see LICENSE.txt for details)
55
// ---------------------------------------
66
#include "sqlManager.hpp"
77
#include <string>
88
#include <vector>
99

10-
std::string SqlManager::getBaseQuery(int LAST_MONTHS) {
11-
return "SELECT * FROM EURUSD WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now()) LIMIT " + std::to_string(STREAM_LIMIT);
10+
std::string SqlManager::getBaseQuery(const std::vector<std::string>& symbols, int LAST_MONTHS) {
11+
if (symbols.empty()) {
12+
return "";
13+
}
14+
15+
std::string query;
16+
for (size_t i = 0; i < symbols.size(); ++i) {
17+
if (i > 0) {
18+
query += " UNION ALL ";
19+
}
20+
query += "SELECT '" + symbols[i] + "' as symbol, * FROM '" + symbols[i] + "' WHERE timestamp >= dateadd('M', -" + std::to_string(LAST_MONTHS) + ", now())";
21+
}
22+
query += " ORDER BY timestamp";
23+
24+
return query;
1225
}
1326

14-
std::vector<PriceData> SqlManager::streamPriceData(const DatabaseConnection& db, int LAST_MONTHS) {
15-
std::string query = getBaseQuery(LAST_MONTHS);
27+
std::vector<PriceData> SqlManager::streamPriceData(const DatabaseConnection& db, const std::vector<std::string>& symbols, int LAST_MONTHS) {
28+
std::string query = getBaseQuery(symbols, LAST_MONTHS);
1629
std::cout << "Executing query: " << query << std::endl;
1730
return db.streamQuery(query);
1831
}

0 commit comments

Comments
 (0)