Skip to content

Commit f339be9

Browse files
committed
ADD: Add SymbolMap helpers to C++
1 parent 97122c9 commit f339be9

File tree

9 files changed

+162
-28
lines changed

9 files changed

+162
-28
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## 0.15.0 - TBD
44

5+
### Enhancements
6+
- Added `PitSymbolMap` helper for keeping track of symbology mappings in Live
7+
58
### Bug fixes
69
- Fixed misaligned read undefined behavior when decoding records
710

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,26 @@ Here is a simple program that fetches 10 seconds of trades for all ES mini futur
7878
```cpp
7979
#include <chrono>
8080
#include <databento/live.hpp>
81+
#include <databento/symbol_map.hpp>
8182
#include <iostream>
8283
#include <string>
8384
#include <thread>
84-
#include <unordered_map>
8585

8686
using namespace databento;
8787

8888
int main() {
89-
std::unordered_map<std::uint32_t, std::string> symbol_mappings;
89+
PitSymbolMap symbol_mappings;
9090

9191
auto client =
9292
LiveBuilder{}.SetKeyFromEnv().SetDataset("GLBX.MDP3").BuildThreaded();
9393

9494
auto handler = [&symbol_mappings](const Record& rec) {
95+
symbol_mappings.OnRecord(rec);
9596
if (rec.Holds<TradeMsg>()) {
9697
auto trade = rec.Get<TradeMsg>();
9798
std::cout << "Received trade for "
9899
<< symbol_mappings[trade.hd.instrument_id] << ':' << trade
99100
<< '\n';
100-
} else if (rec.Holds<SymbolMappingMsg>()) {
101-
auto mapping = rec.Get<SymbolMappingMsg>();
102-
symbol_mappings[mapping.hd.instrument_id] = mapping.STypeOutSymbol();
103101
}
104102
return KeepGoing::Continue;
105103
};

cmake/SourcesAndHeaders.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ set(headers
1919
include/databento/metadata.hpp
2020
include/databento/publishers.hpp
2121
include/databento/record.hpp
22+
include/databento/symbol_map.hpp
2223
include/databento/symbology.hpp
2324
include/databento/timeseries.hpp
2425
include/databento/with_ts_out.hpp
@@ -51,6 +52,7 @@ set(sources
5152
src/metadata.cpp
5253
src/publishers.cpp
5354
src/record.cpp
55+
src/symbol_map.cpp
5456
src/symbology.cpp
5557
src/detail/file_stream.cpp
5658
src/detail/http_client.cpp

example/live/readme.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,26 @@
33
// NOLINTBEGIN(google-build-using-namespace)
44
#include <chrono>
55
#include <databento/live.hpp>
6+
#include <databento/symbol_map.hpp>
67
#include <iostream>
78
#include <string>
89
#include <thread>
9-
#include <unordered_map>
1010

1111
using namespace databento;
1212

1313
int main() {
14-
std::unordered_map<std::uint32_t, std::string> symbol_mappings;
14+
PitSymbolMap symbol_mappings;
1515

1616
auto client =
1717
LiveBuilder{}.SetKeyFromEnv().SetDataset("GLBX.MDP3").BuildThreaded();
1818

1919
auto handler = [&symbol_mappings](const Record& rec) {
20+
symbol_mappings.OnRecord(rec);
2021
if (rec.Holds<TradeMsg>()) {
2122
auto trade = rec.Get<TradeMsg>();
2223
std::cout << "Received trade for "
2324
<< symbol_mappings[trade.hd.instrument_id] << ':' << trade
2425
<< '\n';
25-
} else if (rec.Holds<SymbolMappingMsg>()) {
26-
auto mapping = rec.Get<SymbolMappingMsg>();
27-
symbol_mappings[mapping.hd.instrument_id] = mapping.STypeOutSymbol();
2826
}
2927
return KeepGoing::Continue;
3028
};

example/live/simple.cpp

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
#include <csignal> // sig_atomic_t
22
#include <cstdint>
3+
#include <databento/constants.hpp>
4+
#include <databento/dbn.hpp>
5+
#include <databento/enums.hpp>
6+
#include <databento/live.hpp>
7+
#include <databento/live_threaded.hpp>
8+
#include <databento/log.hpp>
9+
#include <databento/record.hpp>
10+
#include <databento/symbol_map.hpp>
11+
#include <databento/with_ts_out.hpp>
312
#include <iostream>
413
#include <memory>
5-
#include <unordered_map>
6-
7-
#include "databento/constants.hpp"
8-
#include "databento/dbn.hpp"
9-
#include "databento/enums.hpp"
10-
#include "databento/live.hpp"
11-
#include "databento/live_threaded.hpp"
12-
#include "databento/log.hpp"
13-
#include "databento/record.hpp"
14-
#include "databento/with_ts_out.hpp"
1514

1615
static std::sig_atomic_t volatile gSignal;
1716

1817
int main() {
19-
std::unordered_map<std::uint32_t, std::string> symbol_mappings;
18+
databento::PitSymbolMap symbol_mappings;
2019
std::unique_ptr<databento::ILogReceiver> log_receiver{
2120
new databento::ConsoleLogReceiver{databento::LogLevel::Debug}};
2221

2322
auto client = databento::LiveBuilder{}
2423
.SetLogReceiver(log_receiver.get())
2524
.SetKeyFromEnv()
2625
.SetDataset(databento::dataset::kGlbxMdp3)
26+
.SetUpgradePolicy(databento::VersionUpgradePolicy::Upgrade)
2727
.BuildThreaded();
2828

2929
// Set up signal handler for Ctrl+C
@@ -44,7 +44,7 @@ int main() {
4444
case RType::Mbo: {
4545
auto ohlcv = rec.Get<databento::WithTsOut<databento::MboMsg>>();
4646
std::cout << "Received tick for "
47-
<< symbol_mappings.at(ohlcv.rec.hd.instrument_id)
47+
<< symbol_mappings[ohlcv.rec.hd.instrument_id]
4848
<< " with ts_out " << ohlcv.ts_out.time_since_epoch().count()
4949
<< ": " << ohlcv.rec << '\n';
5050
break;
@@ -56,9 +56,7 @@ int main() {
5656
}
5757
case RType::SymbolMapping: {
5858
auto mapping = rec.Get<databento::SymbolMappingMsg>();
59-
std::cout << "Received symbol mapping: " << mapping << '\n';
60-
symbol_mappings.emplace(mapping.hd.instrument_id,
61-
mapping.STypeInSymbol());
59+
symbol_mappings.OnSymbolMapping(mapping);
6260
break;
6361
}
6462
case RType::System: {

include/databento/symbol_map.hpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <string>
5+
#include <unordered_map>
6+
7+
#include "databento/compat.hpp"
8+
#include "databento/record.hpp"
9+
10+
namespace databento {
11+
// A point-in-time symbol map. Useful for working with live symbology or
12+
// a historical request over a single day or other situations where the
13+
// symbol mappings are known not to change.
14+
class PitSymbolMap {
15+
public:
16+
using Store = std::unordered_map<std::uint32_t, std::string>;
17+
18+
PitSymbolMap() = default;
19+
20+
bool IsEmpty() const { return map_.empty(); }
21+
std::size_t Size() const { return map_.size(); }
22+
const Store& Map() const { return map_; }
23+
Store& Map() { return map_; }
24+
Store::const_iterator Find(std::uint32_t instrument_id) const {
25+
return map_.find(instrument_id);
26+
}
27+
std::string& operator[](std::uint32_t instrument_id) {
28+
return map_[instrument_id];
29+
}
30+
void OnRecord(const Record& rec);
31+
template <typename SymbolMappingRec>
32+
void OnSymbolMapping(const SymbolMappingRec& symbol_mapping);
33+
34+
private:
35+
Store map_;
36+
};
37+
38+
// Forward declare explicit instantiation
39+
extern template void PitSymbolMap::OnSymbolMapping(
40+
const SymbolMappingMsgV1& symbol_mapping);
41+
extern template void PitSymbolMap::OnSymbolMapping(
42+
const SymbolMappingMsgV2& symbol_mapping);
43+
} // namespace databento

src/symbol_map.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include "databento/symbol_map.hpp"
2+
3+
#include "databento/compat.hpp"
4+
5+
using databento::PitSymbolMap;
6+
7+
template <typename SymbolMappingRec>
8+
void PitSymbolMap::OnSymbolMapping(const SymbolMappingRec& symbol_mapping) {
9+
const auto it = map_.find(symbol_mapping.hd.instrument_id);
10+
if (it == map_.end()) {
11+
map_.emplace(symbol_mapping.hd.instrument_id,
12+
symbol_mapping.STypeOutSymbol());
13+
} else {
14+
it->second = symbol_mapping.STypeOutSymbol();
15+
}
16+
}
17+
18+
void PitSymbolMap::OnRecord(const Record& record) {
19+
if (record.RType() == RType::SymbolMapping) {
20+
// Version compat
21+
if (record.Header().Size() >= sizeof(SymbolMappingMsgV2)) {
22+
OnSymbolMapping(record.Get<SymbolMappingMsgV2>());
23+
} else {
24+
OnSymbolMapping(record.Get<SymbolMappingMsgV1>());
25+
}
26+
}
27+
}
28+
29+
// Explicit instantiation
30+
template void PitSymbolMap::OnSymbolMapping(
31+
const SymbolMappingMsgV1& symbol_mapping);
32+
template void PitSymbolMap::OnSymbolMapping(
33+
const SymbolMappingMsgV2& symbol_mapping);

test/CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,25 @@ set(
2727
test_sources
2828
src/batch_tests.cpp
2929
src/datetime_tests.cpp
30-
src/dbn_tests.cpp
3130
src/dbn_decoder_tests.cpp
31+
src/dbn_tests.cpp
3232
src/file_stream_tests.cpp
3333
src/flag_set_tests.cpp
3434
src/historical_tests.cpp
3535
src/http_client_tests.cpp
36-
src/live_tests.cpp
3736
src/live_blocking_tests.cpp
37+
src/live_tests.cpp
3838
src/live_threaded_tests.cpp
3939
src/log_tests.cpp
4040
src/metadata_tests.cpp
41-
src/mock_lsg_server.cpp
4241
src/mock_http_server.cpp
42+
src/mock_lsg_server.cpp
4343
src/mock_tcp_server.cpp
4444
src/record_tests.cpp
4545
src/scoped_thread_tests.cpp
4646
src/shared_channel_tests.cpp
4747
src/stream_op_helper_tests.cpp
48+
src/symbol_map_tests.cpp
4849
src/symbology_tests.cpp
4950
src/tcp_client_tests.cpp
5051
src/zstd_stream_tests.cpp

test/src/symbol_map_tests.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <cstdint>
4+
#include <cstring>
5+
#include <string>
6+
#include <unordered_map>
7+
8+
#include "databento/compat.hpp"
9+
#include "databento/record.hpp"
10+
#include "databento/symbol_map.hpp"
11+
12+
namespace databento {
13+
namespace test {
14+
template <typename SM>
15+
SM GenMapping(std::uint32_t instrument_id, const char* stype_out_symbol) {
16+
SM res = {};
17+
res.hd = RecordHeader{sizeof(SM) / RecordHeader::kLengthMultiplier,
18+
RType::SymbolMapping,
19+
1,
20+
instrument_id,
21+
{}};
22+
std::strncpy(res.stype_out_symbol.data(), stype_out_symbol,
23+
res.stype_out_symbol.size());
24+
return res;
25+
}
26+
27+
TEST(PitSymbolMapTests, TestOnSymbolMapping) {
28+
PitSymbolMap target;
29+
target.OnSymbolMapping(GenMapping<SymbolMappingMsgV1>(1, "AAPL"));
30+
target.OnSymbolMapping(GenMapping<SymbolMappingMsgV2>(2, "TSLA"));
31+
target.OnSymbolMapping(GenMapping<SymbolMappingMsgV1>(3, "MSFT"));
32+
std::unordered_map<std::uint32_t, std::string> exp{
33+
{1, "AAPL"}, {2, "TSLA"}, {3, "MSFT"}};
34+
ASSERT_EQ(target.Map(), exp);
35+
target.OnSymbolMapping(GenMapping<SymbolMappingMsgV1>(10, "AAPL"));
36+
target.OnSymbolMapping(GenMapping<SymbolMappingMsgV2>(1, "MSFT"));
37+
ASSERT_EQ(target[1], "MSFT");
38+
}
39+
40+
TEST(PitSymbolMapTests, TestOnRecord) {
41+
PitSymbolMap target;
42+
auto sm1 = GenMapping<SymbolMappingMsgV1>(1, "AAPL");
43+
target.OnRecord(Record{&sm1.hd});
44+
auto sm2 = GenMapping<SymbolMappingMsgV2>(2, "TSLA");
45+
target.OnRecord(Record{&sm2.hd});
46+
sm1 = GenMapping<SymbolMappingMsgV1>(3, "MSFT");
47+
target.OnRecord(Record{&sm1.hd});
48+
std::unordered_map<std::uint32_t, std::string> exp{
49+
{1, "AAPL"}, {2, "TSLA"}, {3, "MSFT"}};
50+
ASSERT_EQ(target.Map(), exp);
51+
sm1 = GenMapping<SymbolMappingMsgV1>(10, "AAPL");
52+
target.OnRecord(Record{&sm1.hd});
53+
sm2 = GenMapping<SymbolMappingMsgV2>(1, "MSFT");
54+
target.OnRecord(Record{&sm2.hd});
55+
ASSERT_EQ(target[1], "MSFT");
56+
}
57+
} // namespace test
58+
} // namespace databento

0 commit comments

Comments
 (0)