Skip to content

Commit ddc5a2c

Browse files
committed
ADD: Add C++ symbol map
1 parent b747d47 commit ddc5a2c

23 files changed

+674
-121
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Changelog
22

3+
## 0.20.0 - TBD
4+
5+
This release improves historical symbology support with the new `TsSymbolMap` class that
6+
handles mapping historical records to a text symbol. To support this class, several types
7+
for date fields were changed from strings or ints to `date::year_month_day`.
8+
9+
### Enhancements
10+
- Added `TsSymbolMap` to support historical symbology where mappings change between days
11+
- Added `PitSymbol` map constructor from `Metadata` and a `date::year_month_day`
12+
- Added `Metadata::CreateSymbolMap` and `Metadata::CreateSymbolMapForDate` methods for
13+
creating symbology maps from historical metadata
14+
- Added `SymbologyResolution::CreateSymbolMap` method for creating a symbology map from
15+
a symbology resolution response
16+
17+
### Breaking changes
18+
- Added new dependency on [Howard Hinnant's date library](https://howardhinnant.github.io/date/date.html)
19+
- Removed type `StrMappingInterval`. `MappingInterval` is now also used in `SymbologyResolution`.
20+
- Changed type of `start_date` and `end_date` in `MappingInterval` to `date::year_month_day`
21+
- Added `stype_in` and `stype_out` fields to `SymbologyResolution` to support creating
22+
a `TsSymbolMap`
23+
324
## 0.19.1 - 2024-06-25
425

526
### Enhancements

CMakeLists.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,38 @@ else()
181181
# Ignore compiler warnings in headers
182182
add_system_include_property(httplib)
183183
endif()
184+
# date
185+
if(${PROJECT_NAME_UPPERCASE}_USE_EXTERNAL_DATE)
186+
# Check if date target already exists
187+
if(TARGET date::date)
188+
get_target_property(DATE_SOURCE_DIR date::date SOURCE_DIR)
189+
message(STATUS "date::date already available as a target at ${DATE_SOURCE_DIR}")
190+
get_target_property(DATE_INCLUDE_DIRS date::date INTERFACE_INCLUDE_DIRECTORIES)
191+
if(DATE_INCLUDE_DIRS)
192+
message(STATUS "date::date include directories: ${DATE_INCLUDE_DIRS}")
193+
endif()
194+
else()
195+
find_package(date REQUIRED)
196+
endif()
197+
else()
198+
set(date_version 3.0.1)
199+
if(CMAKE_VERSION VERSION_LESS 3.24)
200+
FetchContent_Declare(
201+
date_src
202+
URL https://github.com/HowardHinnant/date/archive/refs/tags/v${date_version}.tar.gz
203+
)
204+
else()
205+
# DOWNLOAD_EXTRACT_TIMESTAMP added in 3.24
206+
FetchContent_Declare(
207+
date_src
208+
URL https://github.com/HowardHinnant/date/archive/refs/tags/v${date_version}.tar.gz
209+
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
210+
)
211+
endif()
212+
FetchContent_MakeAvailable(date_src)
213+
# Ignore compiler warnings in headers
214+
add_system_include_property(date)
215+
endif()
184216
# openSSL
185217
find_package(OpenSSL REQUIRED)
186218
if(OPENSSL_FOUND)
@@ -204,6 +236,7 @@ endif()
204236
target_link_libraries(
205237
${PROJECT_NAME}
206238
PUBLIC
239+
date::date
207240
httplib::httplib
208241
nlohmann_json::nlohmann_json
209242
OpenSSL::Crypto

README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ You'll need to ensure the following dependencies are installed:
6464
- [Zstandard (zstd)](https://github.com/facebook/zstd)
6565
- [nlohmann\_json (header-only)](https://github.com/nlohmann/json)
6666
- [cpp-httplib (header-only)](https://github.com/yhirose/cpp-httplib)
67+
- [date (header-only)](https://github.com/HowardHinnant/date)
6768
- [dirent (Windows-only, header-only)](https://github.com/tronkko/dirent)
6869

69-
By default, cpp-httplib and nlohmann\_json are downloaded by CMake as part of the build process.
70+
By default, date, cpp-httplib and nlohmann\_json are downloaded by CMake as part of the build process.
7071
If you would like to use a local version of these libraries, enable the CMake flag
71-
`DATABENTO_ENABLE_EXTERNAL_HTTPLIB` or `DATABENTO_ENABLE_EXTERNAL_JSON`.
72+
`DATABENTO_ENABLE_EXTERNAL_DATE`, `DATABENTO_ENABLE_EXTERNAL_HTTPLIB`, or `DATABENTO_ENABLE_EXTERNAL_JSON` respectively.
7273

7374
#### Ubuntu
7475

@@ -126,26 +127,32 @@ To run this program, set the `DATABENTO_API_KEY` environment variable with an ac
126127

127128
### Historical
128129

129-
Here is a simple program that fetches 10 minutes worth of historical trades for the entire CME Globex market:
130+
Here is a simple program that fetches 10 minutes worth of historical trades for two CME futures:
130131

131132
```cpp
132-
#include <databento/constants.hpp>
133+
#include <databento/dbn.hpp>
133134
#include <databento/historical.hpp>
135+
#include <databento/symbol_map.hpp>
134136
#include <iostream>
135137

136138
using namespace databento;
137139

138140
int main() {
139-
auto client = HistoricalBuilder{}.SetKeyFromEnv().Build();
140-
auto print_trades = [](const Record& record) {
141+
auto client = HistoricalBuilder{}.SetKey("$YOUR_API_KEY").Build();
142+
TsSymbolMap symbol_map;
143+
auto decode_symbols = [&symbol_map](const Metadata& metadata) {
144+
symbol_map = metadata.CreateSymbolMap();
145+
};
146+
auto print_trades = [&symbol_map](const Record& record) {
141147
const auto& trade_msg = record.Get<TradeMsg>();
142-
std::cout << trade_msg << '\n';
148+
std::cout << "Received trade for " << symbol_map.At(trade_msg) << ": "
149+
<< trade_msg << '\n';
143150
return KeepGoing::Continue;
144151
};
145-
client.TimeseriesGetRange("GLBX.MDP3",
146-
{"2022-06-10T14:30", "2022-06-10T14:40"},
147-
kAllSymbols, Schema::Trades, SType::RawSymbol,
148-
SType::InstrumentId, {}, {}, print_trades);
152+
client.TimeseriesGetRange(
153+
"GLBX.MDP3", {"2022-06-10T14:30", "2022-06-10T14:40"}, kAllSymbols,
154+
Schema::Trades, SType::RawSymbol, SType::InstrumentId, {}, decode_symbols,
155+
print_trades);
149156
}
150157
```
151158

example/historical/readme.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
// Duplicate of the example usage code from the README.md to ensure
22
// it compiles and to be able to clang-format it.
33
// NOLINTBEGIN(google-build-using-namespace)
4-
#include <databento/constants.hpp>
4+
#include <databento/dbn.hpp>
55
#include <databento/historical.hpp>
6+
#include <databento/symbol_map.hpp>
67
#include <iostream>
78

89
using namespace databento;
910

1011
int main() {
1112
auto client = HistoricalBuilder{}.SetKey("$YOUR_API_KEY").Build();
12-
auto print_trades = [](const Record& record) {
13+
TsSymbolMap symbol_map;
14+
auto decode_symbols = [&symbol_map](const Metadata& metadata) {
15+
symbol_map = metadata.CreateSymbolMap();
16+
};
17+
auto print_trades = [&symbol_map](const Record& record) {
1318
const auto& trade_msg = record.Get<TradeMsg>();
14-
std::cout << trade_msg << '\n';
19+
std::cout << "Received trade for " << symbol_map.At(trade_msg) << ": "
20+
<< trade_msg << '\n';
1521
return KeepGoing::Continue;
1622
};
17-
client.TimeseriesGetRange("GLBX.MDP3",
18-
{"2022-06-10T14:30", "2022-06-10T14:40"},
19-
kAllSymbols, Schema::Trades, SType::RawSymbol,
20-
SType::InstrumentId, {}, {}, print_trades);
23+
client.TimeseriesGetRange(
24+
"GLBX.MDP3", {"2022-06-10T14:30", "2022-06-10T14:40"}, {"ESM2", "NQZ2"},
25+
Schema::Trades, SType::RawSymbol, SType::InstrumentId, {}, decode_symbols,
26+
print_trades);
2127
}
2228
// NOLINTEND(google-build-using-namespace)

example/historical/symbology_resolve.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ int main(int argc, char* argv[]) {
1616
const auto stype_out = databento::FromString<databento::SType>(argv[3]);
1717

1818
std::vector<std::string> symbols;
19-
for (int i = 6; i < argc; ++i) {
19+
for (int i = 5; i < argc; ++i) {
2020
symbols.emplace_back(argv[i]);
2121
}
2222

include/databento/dbn.hpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include <date/date.h>
4+
35
#include <cstdint>
46
#include <ostream>
57
#include <string>
@@ -9,13 +11,15 @@
911
#include "databento/enums.hpp"
1012

1113
namespace databento {
14+
// Forward declare
15+
class PitSymbolMap;
16+
class TsSymbolMap;
17+
1218
struct MappingInterval {
13-
// The start date of the interval (inclusive) as
14-
// YYYYMMDD e.g. 2022-10-08 is represented as 20221008
15-
std::uint32_t start_date;
16-
// The end date of the interval (exclusive) as
17-
// YYYYMMDD e.g. 2022-10-08 is represented as 20221008
18-
std::uint32_t end_date;
19+
// The start date of the interval (inclusive).
20+
date::year_month_day start_date;
21+
// The end date of the interval (exclusive).
22+
date::year_month_day end_date;
1923
std::string symbol;
2024
};
2125

@@ -69,6 +73,16 @@ struct Metadata {
6973
// Symbol mappings containing a native symbol and its mapping intervals.
7074
std::vector<SymbolMapping> mappings;
7175

76+
// Creates a symbology mapping from instrument ID to text symbol for the given
77+
// date.
78+
//
79+
// This method is useful when working with a historical request over a single
80+
// day or in other situations where you're sure the mappings don't change
81+
// during the time range of the request. Otherwise, `SymbolMap()` is
82+
// recommmended.
83+
PitSymbolMap CreateSymbolMapForDate(date::year_month_day date) const;
84+
// Creates a symbology mapping from instrument ID and date to text symbol.
85+
TsSymbolMap CreateSymbolMap() const;
7286
// Upgrades the metadata according to `upgrade_policy` if necessary.
7387
void Upgrade(VersionUpgradePolicy upgrade_policy);
7488
};

include/databento/detail/json_helpers.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <date/date.h>
34
#include <nlohmann/json.hpp>
45

56
#include <map> // multimap
@@ -89,6 +90,10 @@ template <>
8990
std::vector<std::string> ParseAt(const std::string& endpoint,
9091
const nlohmann::json& json,
9192
const std::string& key);
93+
template <>
94+
date::year_month_day ParseAt(const std::string& endpoint,
95+
const nlohmann::json& json,
96+
const std::string& key);
9297

9398
} // namespace detail
9499
} // namespace databento

include/databento/record.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <cstring> // strncmp
77
#include <string>
88
#include <tuple> // tie
9+
#include <type_traits>
910

1011
#include "databento/constants.hpp" // kSymbolCstrLen
1112
#include "databento/datetime.hpp" // UnixNanos
@@ -40,6 +41,15 @@ struct RecordHeader {
4041
}
4142
};
4243

44+
// Type trait helper for templated functions accepting DBN records.
45+
template <typename, typename = std::void_t<>>
46+
struct has_header : std::false_type {};
47+
template <typename T>
48+
struct has_header<T, std::void_t<decltype(std::declval<T>().hd)>>
49+
: std::is_same<decltype(std::declval<T>().hd), RecordHeader> {};
50+
template <typename T>
51+
constexpr bool has_header_v = has_header<T>::value;
52+
4353
class Record {
4454
public:
4555
explicit Record(RecordHeader* record) : record_{record} {}

include/databento/symbol_map.hpp

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,101 @@
11
#pragma once
2+
#include <date/date.h>
23

34
#include <cstdint>
5+
#include <map>
6+
#include <memory>
47
#include <string>
58
#include <unordered_map>
9+
#include <utility>
610

711
#include "databento/compat.hpp"
812
#include "databento/record.hpp"
913

1014
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.
15+
// Forward declare
16+
struct Metadata;
17+
18+
/// A timeseries symbol map. Useful for working with historical
19+
/// data.
20+
class TsSymbolMap {
21+
public:
22+
// UTC date and instrument ID to text symbol.
23+
using Store = std::map<std::pair<date::year_month_day, std::uint32_t>,
24+
std::shared_ptr<const std::string>>;
25+
26+
TsSymbolMap() = default;
27+
explicit TsSymbolMap(const Metadata& metadata);
28+
29+
bool IsEmpty() const { return map_.empty(); }
30+
std::size_t Size() const { return map_.size(); }
31+
const Store& Map() const { return map_; }
32+
Store& Map() { return map_; }
33+
Store::const_iterator Find(date::year_month_day date,
34+
std::uint32_t instrument_id) const {
35+
return map_.find(std::make_pair(date, instrument_id));
36+
}
37+
template <typename R>
38+
Store::const_iterator Find(const R& rec) const {
39+
static_assert(
40+
has_header_v<R>,
41+
"must be a DBN record struct with an `hd` RecordHeader field");
42+
date::year_month_day index_date{
43+
date::sys_days{date::floor<date::days>(rec.IndexTs())}};
44+
return map_.find(std::make_pair(index_date, rec.hd.instrument_id));
45+
}
46+
const std::string& At(date::year_month_day date,
47+
std::uint32_t instrument_id) const {
48+
return *map_.at(std::make_pair(date, instrument_id));
49+
}
50+
template <typename R>
51+
const std::string& At(const R& rec) const {
52+
static_assert(
53+
has_header_v<R>,
54+
"must be a DBN record struct with an `hd` RecordHeader field");
55+
date::year_month_day index_date{
56+
date::sys_days{date::floor<date::days>(rec.IndexTs())}};
57+
return *map_.at(std::make_pair(index_date, rec.hd.instrument_id));
58+
}
59+
void Insert(std::uint32_t instrument_id, date::year_month_day start_date,
60+
date::year_month_day end_date,
61+
const std::shared_ptr<const std::string>& symbol);
62+
63+
private:
64+
Store map_;
65+
};
66+
67+
// A point-in-time symbol map. Useful for working with live
68+
// symbology or a historical request over a single day or other
69+
// situations where the symbol mappings are known not to change.
1470
class PitSymbolMap {
1571
public:
72+
// Instrument ID to text symbol
1673
using Store = std::unordered_map<std::uint32_t, std::string>;
1774

1875
PitSymbolMap() = default;
76+
PitSymbolMap(const Metadata& metadata, date::year_month_day date);
1977

2078
bool IsEmpty() const { return map_.empty(); }
2179
std::size_t Size() const { return map_.size(); }
2280
const Store& Map() const { return map_; }
2381
Store& Map() { return map_; }
82+
Store::const_iterator Find(const Record& rec) const {
83+
return map_.find(rec.Header().instrument_id);
84+
}
2485
Store::const_iterator Find(std::uint32_t instrument_id) const {
2586
return map_.find(instrument_id);
2687
}
27-
std::string& operator[](std::uint32_t instrument_id) {
88+
template <typename R>
89+
const std::string& At(const R& rec) const {
90+
static_assert(
91+
has_header_v<R>,
92+
"must be a DBN record struct with an `hd` RecordHeader field");
93+
return map_.at(rec.hd.instrument_id);
94+
}
95+
const std::string& At(const Record& rec) const {
96+
return map_.at(rec.Header().instrument_id);
97+
}
98+
const std::string& operator[](std::uint32_t instrument_id) {
2899
return map_[instrument_id];
29100
}
30101
void OnRecord(const Record& rec);

0 commit comments

Comments
 (0)