diff --git a/CHANGELOG.md b/CHANGELOG.md index b25a646..699b9aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## 0.18.0 - TBD ### Breaking changes +- Changed `FlagSet` to be more class-like: + - Added predicate methods and setters for each bit flag + - Improved string formatting + - Removed bitwise operators. Bitwise operations can be performed by first casting to a + `std::uint8_t` or calling the `Raw()` method - Changed format of `display_factor` and `price_ratio` to a fixed-precision decimal for `InstrumentDefMsg` and `InstrumentDefMsgV1` to match existing values and DBN crate - Changed format of `unit_of_measure_qty` to a fixed-precision decimal for diff --git a/cmake/SourcesAndHeaders.cmake b/cmake/SourcesAndHeaders.cmake index dc775dc..0c8d686 100644 --- a/cmake/SourcesAndHeaders.cmake +++ b/cmake/SourcesAndHeaders.cmake @@ -6,6 +6,14 @@ set(headers include/databento/dbn.hpp include/databento/dbn_decoder.hpp include/databento/dbn_file_store.hpp + include/databento/detail/file_stream.hpp + include/databento/detail/http_client.hpp + include/databento/detail/json_helpers.hpp + include/databento/detail/scoped_fd.hpp + include/databento/detail/scoped_thread.hpp + include/databento/detail/shared_channel.hpp + include/databento/detail/tcp_client.hpp + include/databento/detail/zstd_stream.hpp include/databento/enums.hpp include/databento/exceptions.hpp include/databento/fixed_price.hpp @@ -23,14 +31,6 @@ set(headers include/databento/symbology.hpp include/databento/timeseries.hpp include/databento/with_ts_out.hpp - include/databento/detail/file_stream.hpp - include/databento/detail/http_client.hpp - include/databento/detail/json_helpers.hpp - include/databento/detail/scoped_fd.hpp - include/databento/detail/scoped_thread.hpp - include/databento/detail/shared_channel.hpp - include/databento/detail/tcp_client.hpp - include/databento/detail/zstd_stream.hpp src/stream_op_helper.hpp ) @@ -40,10 +40,18 @@ set(sources src/datetime.cpp src/dbn.cpp src/dbn_decoder.cpp + src/dbn_file_store.cpp + src/detail/file_stream.cpp + src/detail/http_client.cpp + src/detail/json_helpers.cpp + src/detail/scoped_fd.cpp + src/detail/shared_channel.cpp + src/detail/tcp_client.cpp + src/detail/zstd_stream.cpp src/enums.cpp src/exceptions.cpp - src/dbn_file_store.cpp src/fixed_price.cpp + src/flag_set.cpp src/historical.cpp src/live.cpp src/live_blocking.cpp @@ -54,11 +62,4 @@ set(sources src/record.cpp src/symbol_map.cpp src/symbology.cpp - src/detail/file_stream.cpp - src/detail/http_client.cpp - src/detail/json_helpers.cpp - src/detail/scoped_fd.cpp - src/detail/shared_channel.cpp - src/detail/tcp_client.cpp - src/detail/zstd_stream.cpp ) diff --git a/include/databento/flag_set.hpp b/include/databento/flag_set.hpp index 83b5ab0..8647f20 100644 --- a/include/databento/flag_set.hpp +++ b/include/databento/flag_set.hpp @@ -1,8 +1,8 @@ #pragma once -#include #include #include +#include namespace databento { // Transparent wrapper around the bit flags used in several DBN record types. @@ -11,77 +11,92 @@ class FlagSet { using Repr = std::uint8_t; // Indicates it's the last message in the packet from the venue for a given // `instrument_id`. - static constexpr Repr kLast = 1 << 7; + static inline constexpr Repr kLast = 1 << 7; // Indicates a top-of-book message, not an individual order. - static constexpr Repr kTob = 1 << 6; + static inline constexpr Repr kTob = 1 << 6; // Indicates the message was sourced from a replay, such as a snapshot // server. - static constexpr Repr kSnapshot = 1 << 5; + static inline constexpr Repr kSnapshot = 1 << 5; // Indicates an aggregated price level message, not an individual order. - static constexpr Repr kMbp = 1 << 4; + static inline constexpr Repr kMbp = 1 << 4; // Indicates the `ts_recv` value is inaccurate due to clock issues or packet // reordering. - static constexpr Repr kBadTsRecv = 1 << 3; + static inline constexpr Repr kBadTsRecv = 1 << 3; // Indicates an unrecoverable gap was detected in the channel. - static constexpr Repr kMaybeBadBook = 1 << 2; + static inline constexpr Repr kMaybeBadBook = 1 << 2; friend std::ostream& operator<<(std::ostream&, FlagSet); - constexpr FlagSet() = default; + constexpr FlagSet() : repr_{0} {}; - constexpr FlagSet( // cppcheck-suppress noExplicitConstructor - std::uint8_t repr) - : repr_{repr} {} + explicit constexpr FlagSet(Repr repr) : repr_{repr} {} explicit constexpr operator std::uint8_t() const { return repr_; } - constexpr FlagSet operator~() const { - return FlagSet{static_cast(~repr_)}; - } + constexpr bool operator==(FlagSet rhs) const { return repr_ == rhs.repr_; } + constexpr bool operator!=(FlagSet rhs) const { return repr_ != rhs.repr_; } - constexpr FlagSet operator|(FlagSet rhs) const { - return FlagSet{static_cast(repr_ | rhs.repr_)}; + FlagSet Clear() { + repr_ = 0; + return *this; } - constexpr FlagSet operator&(FlagSet rhs) const { - return FlagSet{static_cast(repr_ & rhs.repr_)}; - } + constexpr Repr Raw() const { return repr_; } + void SetRaw(Repr raw) { repr_ = raw; } - constexpr FlagSet operator^(FlagSet rhs) const { - return FlagSet{static_cast(repr_ ^ rhs.repr_)}; + // Checks if any flags are set. + constexpr bool Any() const { return repr_ != 0; } + constexpr bool IsEmpty() const { return repr_ == 0; } + constexpr bool IsLast() const { return bits_.last; } + FlagSet SetLast() { + bits_.last = true; + return *this; } - - FlagSet operator|=(FlagSet rhs) { - repr_ = repr_ | rhs.repr_; + constexpr bool IsTob() const { return bits_.tob; } + FlagSet SetTob() { + bits_.tob = true; return *this; } - - FlagSet operator&=(FlagSet rhs) { - repr_ = repr_ & rhs.repr_; + constexpr bool IsSnapshot() const { return bits_.snapshot; } + FlagSet SetSnapshot() { + bits_.snapshot = true; return *this; } - - FlagSet operator^=(FlagSet rhs) { - repr_ = repr_ ^ rhs.repr_; + constexpr bool IsMbp() const { return bits_.mbp; } + FlagSet SetMbp() { + bits_.mbp = true; + return *this; + } + constexpr bool IsBadTsRecv() const { return bits_.bad_ts_recv; } + FlagSet SetBadTsRecv() { + bits_.bad_ts_recv = true; + return *this; + } + constexpr bool IsMaybeBadBook() const { return bits_.maybe_bad_book; } + FlagSet SetMaybeBadBook() { + bits_.maybe_bad_book = true; return *this; } - - constexpr bool operator==(FlagSet rhs) const { return repr_ == rhs.repr_; } - - constexpr bool operator!=(FlagSet rhs) const { return repr_ != rhs.repr_; } - - // Checks if any flags are set. - constexpr bool Any() const { return repr_ != 0; } private: - Repr repr_{}; + struct BitFlags { + bool reserved0 : 1; + bool reserved1 : 1; + bool maybe_bad_book : 1; + bool bad_ts_recv : 1; + bool mbp : 1; + bool snapshot : 1; + bool tob : 1; + bool last : 1; + }; + union { + BitFlags bits_; + Repr repr_; + }; }; -inline std::ostream& operator<<(std::ostream& stream, FlagSet flag) { - // print as binary - stream << "0b" << std::bitset<8>{flag.repr_}; - return stream; -} +std::ostream& operator<<(std::ostream& stream, FlagSet flag_set); +std::string ToString(FlagSet flags); static_assert(sizeof(FlagSet) == sizeof(std::uint8_t), "FlagSet must be a transparent wrapper around std::uint8_t"); diff --git a/src/flag_set.cpp b/src/flag_set.cpp new file mode 100644 index 0000000..2ee7e52 --- /dev/null +++ b/src/flag_set.cpp @@ -0,0 +1,41 @@ +#include "databento/flag_set.hpp" + +#include + +#include "stream_op_helper.hpp" + +namespace databento { +std::ostream& operator<<(std::ostream& stream, FlagSet flag_set) { + constexpr std::array, 6> + kFlagsAndNames = {{ + {&FlagSet::IsLast, "LAST"}, + {&FlagSet::IsTob, "TOB"}, + {&FlagSet::IsSnapshot, "SNAPSHOT"}, + {&FlagSet::IsMbp, "MBP"}, + {&FlagSet::IsBadTsRecv, "BAD_TS_RECV"}, + {&FlagSet::IsMaybeBadBook, "MAYBE_BAD_BOOK"}, + }}; + + bool has_written_flag = false; + for (const auto& pair : kFlagsAndNames) { + if ((flag_set.*pair.first)()) { + if (has_written_flag) { + stream << " | " << pair.second; + } else { + stream << pair.second; + has_written_flag = true; + } + } + } + // Cast to uint16_t to avoid being formatted as char + const auto raw = static_cast(flag_set.Raw()); + if (has_written_flag) { + stream << " (" << raw << ')'; + } else { + stream << raw; + } + return stream; +} + +std::string ToString(FlagSet flags) { return MakeString(flags); } +} // namespace databento diff --git a/test/src/dbn_decoder_tests.cpp b/test/src/dbn_decoder_tests.cpp index 72d354b..9175034 100644 --- a/test/src/dbn_decoder_tests.cpp +++ b/test/src/dbn_decoder_tests.cpp @@ -256,7 +256,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeMbo) { EXPECT_EQ(ch_mbo1.order_id, 647784973705); EXPECT_EQ(ch_mbo1.price, 3722750000000); EXPECT_EQ(ch_mbo1.size, 1); - EXPECT_EQ(ch_mbo1.flags, 128); + EXPECT_EQ(ch_mbo1.flags.Raw(), 128); EXPECT_EQ(ch_mbo1.channel_id, 0); EXPECT_EQ(ch_mbo1.action, Action::Cancel); EXPECT_EQ(ch_mbo1.side, Side::Ask); @@ -280,7 +280,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeMbo) { EXPECT_EQ(ch_mbo2.order_id, 647784973631); EXPECT_EQ(ch_mbo2.price, 3723000000000); EXPECT_EQ(ch_mbo2.size, 1); - EXPECT_EQ(ch_mbo2.flags, 128); + EXPECT_EQ(ch_mbo2.flags.Raw(), 128); EXPECT_EQ(ch_mbo2.channel_id, 0); EXPECT_EQ(ch_mbo2.action, Action::Cancel); EXPECT_EQ(ch_mbo2.side, Side::Ask); @@ -327,7 +327,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeMbp1) { EXPECT_EQ(ch_mbp1.size, 1); EXPECT_EQ(ch_mbp1.action, Action::Add); EXPECT_EQ(ch_mbp1.side, Side::Ask); - EXPECT_EQ(ch_mbp1.flags, 128); + EXPECT_EQ(ch_mbp1.flags.Raw(), 128); EXPECT_EQ(ch_mbp1.depth, 0); EXPECT_EQ(ch_mbp1.ts_recv.time_since_epoch().count(), 1609160400006136329); EXPECT_EQ(ch_mbp1.ts_in_delta.count(), 17214); @@ -356,7 +356,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeMbp1) { EXPECT_EQ(ch_mbp2.size, 1); EXPECT_EQ(ch_mbp2.action, Action::Add); EXPECT_EQ(ch_mbp2.side, Side::Ask); - EXPECT_EQ(ch_mbp2.flags, 128); + EXPECT_EQ(ch_mbp2.flags.Raw(), 128); EXPECT_EQ(ch_mbp2.depth, 0); EXPECT_EQ(ch_mbp2.ts_recv.time_since_epoch().count(), 1609160400006246513); EXPECT_EQ(ch_mbp2.ts_in_delta.count(), 18858); @@ -407,7 +407,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeMbp10) { EXPECT_EQ(ch_mbp1.size, 1); EXPECT_EQ(ch_mbp1.action, Action::Cancel); EXPECT_EQ(ch_mbp1.side, Side::Ask); - EXPECT_EQ(ch_mbp1.flags, 128); + EXPECT_EQ(ch_mbp1.flags.Raw(), 128); EXPECT_EQ(ch_mbp1.depth, 9); EXPECT_EQ(ch_mbp1.ts_recv.time_since_epoch().count(), 1609160400000704060); EXPECT_EQ(ch_mbp1.ts_in_delta.count(), 22993); @@ -448,7 +448,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeMbp10) { EXPECT_EQ(ch_mbp2.size, 1); EXPECT_EQ(ch_mbp2.action, Action::Cancel); EXPECT_EQ(ch_mbp2.side, Side::Bid); - EXPECT_EQ(ch_mbp2.flags, 128); + EXPECT_EQ(ch_mbp2.flags.Raw(), 128); EXPECT_EQ(ch_mbp2.depth, 1); EXPECT_EQ(ch_mbp2.ts_recv.time_since_epoch().count(), 1609160400000750544); EXPECT_EQ(ch_mbp2.ts_in_delta.count(), 20625); @@ -511,7 +511,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeCbbo) { EXPECT_EQ(ch_cbbo1.size, 1); EXPECT_EQ(ch_cbbo1.action, Action::Add); EXPECT_EQ(ch_cbbo1.side, Side::Ask); - EXPECT_EQ(ch_cbbo1.flags, 128); + EXPECT_EQ(ch_cbbo1.flags.Raw(), 128); EXPECT_EQ(ch_cbbo1.ts_recv.time_since_epoch().count(), 1609160400006136329); EXPECT_EQ(ch_cbbo1.ts_in_delta.count(), 17214); EXPECT_EQ(ch_cbbo1.sequence, 1170362); @@ -539,7 +539,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeCbbo) { EXPECT_EQ(ch_cbbo2.size, 1); EXPECT_EQ(ch_cbbo2.action, Action::Add); EXPECT_EQ(ch_cbbo2.side, Side::Ask); - EXPECT_EQ(ch_cbbo2.flags, 128); + EXPECT_EQ(ch_cbbo2.flags.Raw(), 128); EXPECT_EQ(ch_cbbo2.ts_recv.time_since_epoch().count(), 1609160400006246513); EXPECT_EQ(ch_cbbo2.ts_in_delta.count(), 18858); EXPECT_EQ(ch_cbbo2.sequence, 1170364); @@ -589,7 +589,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeTbbo) { EXPECT_EQ(ch_tbbo1.size, 5); EXPECT_EQ(ch_tbbo1.action, Action::Trade); EXPECT_EQ(ch_tbbo1.side, Side::Ask); - EXPECT_EQ(ch_tbbo1.flags, 129); + EXPECT_EQ(ch_tbbo1.flags.Raw(), 129); EXPECT_EQ(ch_tbbo1.depth, 0); EXPECT_EQ(ch_tbbo1.ts_recv.time_since_epoch().count(), 1609160400099150057); EXPECT_EQ(ch_tbbo1.ts_in_delta.count(), 19251); @@ -618,7 +618,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeTbbo) { EXPECT_EQ(ch_tbbo2.size, 21); EXPECT_EQ(ch_tbbo2.action, Action::Trade); EXPECT_EQ(ch_tbbo2.side, Side::Ask); - EXPECT_EQ(ch_tbbo2.flags, 129); + EXPECT_EQ(ch_tbbo2.flags.Raw(), 129); EXPECT_EQ(ch_tbbo2.depth, 0); EXPECT_EQ(ch_tbbo2.ts_recv.time_since_epoch().count(), 1609160400108142648); EXPECT_EQ(ch_tbbo2.ts_in_delta.count(), 20728); @@ -669,7 +669,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeTrades) { EXPECT_EQ(ch_trade1.size, 5); EXPECT_EQ(ch_trade1.action, Action::Trade); EXPECT_EQ(ch_trade1.side, Side::Ask); - EXPECT_EQ(ch_trade1.flags, 129); + EXPECT_EQ(ch_trade1.flags.Raw(), 129); EXPECT_EQ(ch_trade1.depth, 0); EXPECT_EQ(ch_trade1.ts_recv.time_since_epoch().count(), 1609160400099150057); EXPECT_EQ(ch_trade1.ts_in_delta.count(), 19251); @@ -692,7 +692,7 @@ TEST_P(DbnDecoderSchemaTests, TestDecodeTrades) { EXPECT_EQ(ch_trade2.size, 21); EXPECT_EQ(ch_trade2.action, Action::Trade); EXPECT_EQ(ch_trade2.side, Side::Ask); - EXPECT_EQ(ch_trade2.flags, 129); + EXPECT_EQ(ch_trade2.flags.Raw(), 129); EXPECT_EQ(ch_trade2.depth, 0); EXPECT_EQ(ch_trade2.ts_recv.time_since_epoch().count(), 1609160400108142648); EXPECT_EQ(ch_trade2.ts_in_delta.count(), 20728); diff --git a/test/src/flag_set_tests.cpp b/test/src/flag_set_tests.cpp index 9413c78..e6fd147 100644 --- a/test/src/flag_set_tests.cpp +++ b/test/src/flag_set_tests.cpp @@ -1,70 +1,62 @@ #include -#include - #include "databento/flag_set.hpp" namespace databento { namespace test { -TEST(FlagSetTests, TestBitwiseNot) { +TEST(FlagSetTests, TestBasic) { const FlagSet no_flags{}; - ASSERT_FALSE(no_flags.Any()); - const auto all_flags = ~no_flags; - ASSERT_TRUE(all_flags.Any()); - ASSERT_TRUE((all_flags & FlagSet::kLast).Any()); - ASSERT_TRUE((all_flags & FlagSet::kMbp).Any()); - ASSERT_TRUE((all_flags & FlagSet::kBadTsRecv).Any()); + EXPECT_FALSE(no_flags.Any()); + EXPECT_TRUE(no_flags.IsEmpty()); + const FlagSet all_flags{255}; + EXPECT_TRUE(all_flags.Any()); + EXPECT_FALSE(all_flags.IsEmpty()); + EXPECT_TRUE((all_flags.IsLast())); + EXPECT_TRUE((all_flags.IsMbp())); + EXPECT_TRUE((all_flags.IsBadTsRecv())); } -TEST(FlagSetTests, TestBitwiseOr) { - const FlagSet flag = FlagSet::kMbp; - const FlagSet no_flags{}; - ASSERT_NE(flag, no_flags); - ASSERT_EQ(flag, no_flags | FlagSet::kMbp); +TEST(FlagSetTests, TestAny) { + FlagSet flag{}; + ASSERT_FALSE(flag.Any()); + flag.SetBadTsRecv(); + ASSERT_TRUE(flag.Any()); } -TEST(FlagSetTests, TestBitwiseAnd) { - const auto flag = static_cast(0b10011000); - ASSERT_TRUE(flag.Any()); - ASSERT_TRUE((flag & FlagSet::kLast).Any()); - ASSERT_TRUE((flag & FlagSet::kMbp).Any()); - ASSERT_TRUE((flag & FlagSet::kBadTsRecv).Any()); +TEST(FlagSetTests, TestConversionOperator) { + constexpr FlagSet kFlagSet{FlagSet::kMbp | FlagSet::kTob}; + const auto raw = std::uint8_t{kFlagSet}; + ASSERT_EQ(raw, 0b01010000); } -TEST(FlagSetTests, TestBitwiseAndAssignment) { - FlagSet flag{}; - flag &= FlagSet::kLast; - ASSERT_FALSE((flag & FlagSet::kLast).Any()); - flag = ~flag & FlagSet::kLast; - ASSERT_TRUE((flag & FlagSet::kLast).Any()); +TEST(FlagSetTests, ToStringEmpty) { + constexpr FlagSet kTarget{}; + ASSERT_EQ(ToString(kTarget), "0"); } -TEST(FlagSetTests, TestBitwiseXor) { - FlagSet flag = ~FlagSet{}; - flag ^= FlagSet::kLast; - ASSERT_FALSE((flag & FlagSet::kLast).Any()); - ASSERT_TRUE((flag & FlagSet::kMbp).Any()); - ASSERT_TRUE((flag & FlagSet::kBadTsRecv).Any()); +TEST(FlagSetTests, ToStringOneSet) { + const auto target = FlagSet{}.SetMbp(); + ASSERT_EQ(ToString(target), "MBP (16)"); } -TEST(FlagSetTests, TestAny) { - FlagSet flag{}; - ASSERT_FALSE(flag.Any()); - flag = FlagSet::kBadTsRecv; - ASSERT_TRUE(flag.Any()); +TEST(FlagSetTests, ToStringThreeSet) { + const auto target = FlagSet{}.SetTob().SetSnapshot().SetMaybeBadBook(); + ASSERT_EQ(ToString(target), "TOB | SNAPSHOT | MAYBE_BAD_BOOK (100)"); } -TEST(FlagSetTests, TestToString) { - constexpr FlagSet kFlagSet = FlagSet::kMbp; - std::ostringstream ss; - ss << kFlagSet; - ASSERT_EQ(ss.str(), "0b00010000"); +TEST(FlagSetTests, ToStringReservedSet) { + constexpr FlagSet kTarget{255}; + ASSERT_EQ(ToString(kTarget), + "LAST | TOB | SNAPSHOT | MBP | BAD_TS_RECV | MAYBE_BAD_BOOK (255)"); } -TEST(FlagSetTests, TestConversionOperator) { - constexpr FlagSet kFlagSet = FlagSet::kMbp | FlagSet::kTob; - const auto raw = std::uint8_t{kFlagSet}; - ASSERT_EQ(raw, 0b01010000); +TEST(FlagSetTests, ConstantBitFieldEquivalence) { + EXPECT_EQ(FlagSet::kLast, FlagSet{}.SetLast().Raw()); + EXPECT_EQ(FlagSet::kTob, FlagSet{}.SetTob().Raw()); + EXPECT_EQ(FlagSet::kSnapshot, FlagSet{}.SetSnapshot().Raw()); + EXPECT_EQ(FlagSet::kMbp, FlagSet{}.SetMbp().Raw()); + EXPECT_EQ(FlagSet::kBadTsRecv, FlagSet{}.SetBadTsRecv().Raw()); + EXPECT_EQ(FlagSet::kMaybeBadBook, FlagSet{}.SetMaybeBadBook().Raw()); } } // namespace test } // namespace databento diff --git a/test/src/live_threaded_tests.cpp b/test/src/live_threaded_tests.cpp index ee5a558..ce5d97e 100644 --- a/test/src/live_threaded_tests.cpp +++ b/test/src/live_threaded_tests.cpp @@ -42,7 +42,7 @@ TEST_F(LiveThreadedTests, TestBasic) { 1, 2, 3, - 0, + {}, 4, Action::Add, Side::Bid, @@ -76,7 +76,7 @@ TEST_F(LiveThreadedTests, TestTimeoutRecovery) { 1, 2, 3, - 0, + {}, 4, Action::Add, Side::Bid, @@ -120,7 +120,7 @@ TEST_F(LiveThreadedTests, TestStop) { 1, 2, 3, - 0, + {}, 4, Action::Add, Side::Bid, diff --git a/test/src/record_tests.cpp b/test/src/record_tests.cpp index e1d3612..e257aca 100644 --- a/test/src/record_tests.cpp +++ b/test/src/record_tests.cpp @@ -60,7 +60,7 @@ TEST(RecordTests, TestMbp10MsgToString) { size = 10, action = Add, side = Bid, - flags = 0b00000000, + flags = 0, depth = 0, ts_recv = 2023-10-10T16:57:52.000020500Z, ts_in_delta = 100,