From e2375e2a019202cc3b4c770f061ff11c4a02e226 Mon Sep 17 00:00:00 2001 From: Carter Green Date: Wed, 3 Jul 2024 08:17:57 -0500 Subject: [PATCH 1/4] ADD: Add separate `BboMsg` to DBN --- CHANGELOG.md | 15 ++- python/python/databento_dbn/_lib.pyi | 128 +++++++++++++++++++- python/src/lib.rs | 3 +- rust/dbn/src/compat.rs | 4 +- rust/dbn/src/decode/dbn/async.rs | 8 +- rust/dbn/src/decode/dbn/sync.rs | 56 ++------- rust/dbn/src/decode/dbz.rs | 4 +- rust/dbn/src/enums.rs | 3 +- rust/dbn/src/lib.rs | 8 +- rust/dbn/src/python/record.rs | 168 ++++++++++++++++++++++++--- rust/dbn/src/record.rs | 92 +++++++++++++-- rust/dbn/src/record/impl_default.rs | 38 ++++-- rust/dbn/src/record/methods.rs | 22 +++- 13 files changed, 440 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa4600..fcd098e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.19.0 - TBD + +### Enhancements +- Added `BboMsg` record struct for future `bbo-1m` and `bbo-1s` schemas + +### Breaking changes +- Added `Default` trait implementation for `Mbp1Msg` due to it no longer needing + to support multiple `rtype` values. The `default_for_schema` function has been removed +- Changed `Bbo1sMsg` and `Bbo1mMsg` to be aliases for `BboMsg` +- Changed the default value of the `side` fields to `Side::None` + ## 0.18.3 - 2024-07-02 ### Bug fixes @@ -78,8 +89,8 @@ ## 0.17.0 - 2024-04-01 ### Enhancements -- Added new record type `CbboMsg`, new rtypes and schema types for `Cbbo`, `Cbbo1s`, - `Cbbo1m`, `Tcbbo`, `Bbo1s`, and `Bbo1m` +- Added new record type `CbboMsg`, new rtypes and schema types for `Cbbo`, `Cbbo1S`, + `Cbbo1M`, `Tcbbo`, `Bbo1S`, and `Bbo1M` - Added `Volatility` and `Delta` `StatType` variants - Added `Undefined` and `TimeProRata` `MatchAlgorithm` variants - Exported more enums to Python: diff --git a/python/python/databento_dbn/_lib.pyi b/python/python/databento_dbn/_lib.pyi index 44791a2..c7373b1 100644 --- a/python/python/databento_dbn/_lib.pyi +++ b/python/python/databento_dbn/_lib.pyi @@ -26,6 +26,7 @@ _DBNRecord = Union[ Metadata, MBOMsg, MBP1Msg, + BBOMsg, CBBOMsg, MBP10Msg, OHLCVMsg, @@ -1721,6 +1722,127 @@ class MBP1Msg(Record, _MBPBase): """ +class BBOMsg(Record): + """ + Subsampled market by price with a known book depth of 1. + """ + + @property + def pretty_price(self) -> float: + """ + The price of the last trade as a float. + + Returns + ------- + float + + See Also + -------- + price + + """ + + @property + def price(self) -> int: + """ + The price of the last trade expressed as a signed integer where every 1 unit + corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001. + + Returns + ------- + int + + See Also + -------- + pretty_price + + """ + + @property + def size(self) -> int: + """ + The quantity of the last trade. + + Returns + ------- + int + + """ + + @property + def side(self) -> str: + """ + The side that initiated the last trade. Can be `A`sk for a sell order (or sell + aggressor in a trade), `B`id for a buy order (or buy aggressor in a trade), or + `N`one where no side is specified by the original source. + + Returns + ------- + str + + """ + + @property + def flags(self) -> int: + """ + A bit field indicating event end, message characteristics, and data quality. + + Returns + ------- + int + + """ + + @property + def pretty_ts_recv(self) -> dt.datetime: + """ + The capture-server-received timestamp as a datetime or + `pandas.Timestamp`, if available. + + Returns + ------- + datetime.datetime + + """ + + @property + def ts_recv(self) -> int: + """ + The capture-server-received timestamp expressed as number of + nanoseconds since the UNIX epoch. + + Returns + ------- + int + + """ + + @property + def sequence(self) -> int: + """ + The message sequence number assigned at the venue of the last update. + + Returns + ------- + int + + """ + + @property + def levels(self) -> list[BidAskPair]: + """ + The top of the order book. + + Returns + ------- + list[BidAskPair] + + Notes + ----- + BBOMsg contains 1 level of BidAskPair. + + """ + class CBBOMsg(Record): """ Consolidated best bid and offer implementation. @@ -1816,8 +1938,7 @@ class CBBOMsg(Record): @property def pretty_ts_recv(self) -> dt.datetime: """ - The capture-server-received timestamp as a datetime or - `pandas.Timestamp`, if available. + The interval timestamp as a datetime or `pandas.Timestamp` if available. Returns ------- @@ -1828,8 +1949,7 @@ class CBBOMsg(Record): @property def ts_recv(self) -> int: """ - The capture-server-received timestamp expressed as number of - nanoseconds since the UNIX epoch. + The interval timestamp expressed as number of nanoseconds since the UNIX epoch. Returns ------- diff --git a/python/src/lib.rs b/python/src/lib.rs index d2f0b53..3b6ff88 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -6,7 +6,7 @@ use dbn::{ compat::{ErrorMsgV1, InstrumentDefMsgV1, SymbolMappingMsgV1, SystemMsgV1}, flags, python::{DBNError, EnumIterator}, - Action, BidAskPair, CbboMsg, Compression, ConsolidatedBidAskPair, Encoding, ErrorMsg, + Action, BboMsg, BidAskPair, CbboMsg, Compression, ConsolidatedBidAskPair, Encoding, ErrorMsg, ImbalanceMsg, InstrumentClass, InstrumentDefMsg, MatchAlgorithm, MboMsg, Mbp10Msg, Mbp1Msg, Metadata, OhlcvMsg, RType, RecordHeader, SType, Schema, SecurityUpdateAction, Side, StatMsg, StatType, StatUpdateAction, StatusAction, StatusMsg, StatusReason, SymbolMappingMsg, SystemMsg, @@ -54,6 +54,7 @@ fn databento_dbn(_py: Python<'_>, m: &Bound) -> PyResult<()> { checked_add_class::(m)?; checked_add_class::(m)?; checked_add_class::(m)?; + checked_add_class::(m)?; checked_add_class::(m)?; // PyClass enums checked_add_class::(m)?; diff --git a/rust/dbn/src/compat.rs b/rust/dbn/src/compat.rs index a0a6c23..0256b23 100644 --- a/rust/dbn/src/compat.rs +++ b/rust/dbn/src/compat.rs @@ -644,7 +644,7 @@ mod tests { use time::OffsetDateTime; use type_layout::{Field, TypeLayout}; - use crate::{Mbp1Msg, Record, Schema, MAX_RECORD_LEN}; + use crate::{Mbp1Msg, Record, MAX_RECORD_LEN}; use super::*; @@ -727,7 +727,7 @@ mod tests { let rec = Mbp1Msg { price: 1_250_000_000, side: b'A' as c_char, - ..Mbp1Msg::default_for_schema(Schema::Mbp1) + ..Mbp1Msg::default() }; let orig = WithTsOut::new(rec, OffsetDateTime::now_utc().unix_timestamp_nanos() as u64); let mut compat_buffer = [0; MAX_RECORD_LEN]; diff --git a/rust/dbn/src/decode/dbn/async.rs b/rust/dbn/src/decode/dbn/async.rs index ca50825..7b6a014 100644 --- a/rust/dbn/src/decode/dbn/async.rs +++ b/rust/dbn/src/decode/dbn/async.rs @@ -672,8 +672,8 @@ mod tests { #[case::mbo(Schema::Mbo, MboMsg::default())] #[case::trades(Schema::Trades, TradeMsg::default())] #[case::tbbo(Schema::Cbbo, CbboMsg::default_for_schema(Schema::Cbbo))] - #[case::tbbo(Schema::Tbbo, TbboMsg::default_for_schema(Schema::Tbbo))] - #[case::mbp1(Schema::Mbp1, Mbp1Msg::default_for_schema(Schema::Mbp1))] + #[case::tbbo(Schema::Tbbo, TbboMsg::default())] + #[case::mbp1(Schema::Mbp1, Mbp1Msg::default())] #[case::mbp10(Schema::Mbp10, Mbp10Msg::default())] #[case::ohlcv1d(Schema::Ohlcv1D, OhlcvMsg::default_for_schema(Schema::Ohlcv1D))] #[case::ohlcv1h(Schema::Ohlcv1H, OhlcvMsg::default_for_schema(Schema::Ohlcv1H))] @@ -716,8 +716,8 @@ mod tests { #[case::mbo(Schema::Mbo, MboMsg::default())] #[case::trades(Schema::Trades, TradeMsg::default())] #[case::cbbo(Schema::Cbbo, CbboMsg::default_for_schema(Schema::Cbbo))] - #[case::tbbo(Schema::Tbbo, TbboMsg::default_for_schema(Schema::Tbbo))] - #[case::mbp1(Schema::Mbp1, Mbp1Msg::default_for_schema(Schema::Mbp1))] + #[case::tbbo(Schema::Tbbo, TbboMsg::default())] + #[case::mbp1(Schema::Mbp1, Mbp1Msg::default())] #[case::mbp10(Schema::Mbp10, Mbp10Msg::default())] #[case::ohlcv1d(Schema::Ohlcv1D, OhlcvMsg::default_for_schema(Schema::Ohlcv1D))] #[case::ohlcv1h(Schema::Ohlcv1H, OhlcvMsg::default_for_schema(Schema::Ohlcv1H))] diff --git a/rust/dbn/src/decode/dbn/sync.rs b/rust/dbn/src/decode/dbn/sync.rs index 8a51b6a..4b8b38f 100644 --- a/rust/dbn/src/decode/dbn/sync.rs +++ b/rust/dbn/src/decode/dbn/sync.rs @@ -736,18 +736,8 @@ mod tests { #[rstest] #[case::uncompressed_mbo_v1(1, Schema::Mbo, Compression::None, MboMsg::default())] #[case::uncompressed_trades_v1(1, Schema::Trades, Compression::None, TradeMsg::default())] - #[case::uncompressed_tbbo_v1( - 1, - Schema::Tbbo, - Compression::None, - TbboMsg::default_for_schema(Schema::Tbbo) - )] - #[case::uncompressed_mbp1_v1( - 1, - Schema::Mbp1, - Compression::None, - Mbp1Msg::default_for_schema(Schema::Tbbo) - )] + #[case::uncompressed_tbbo_v1(1, Schema::Tbbo, Compression::None, TbboMsg::default())] + #[case::uncompressed_mbp1_v1(1, Schema::Mbp1, Compression::None, Mbp1Msg::default())] #[case::uncompressed_mbp10_v1(1, Schema::Mbp10, Compression::None, Mbp10Msg::default())] #[case::uncompressed_ohlcv1d_v1( 1, @@ -793,18 +783,8 @@ mod tests { )] #[case::zstd_mbo_v1(1, Schema::Mbo, Compression::ZStd, MboMsg::default())] #[case::zstd_trades_v1(1, Schema::Trades, Compression::ZStd, TradeMsg::default())] - #[case::zstd_tbbo_v1( - 1, - Schema::Tbbo, - Compression::ZStd, - TbboMsg::default_for_schema(Schema::Tbbo) - )] - #[case::zstd_mbp1_v1( - 1, - Schema::Mbp1, - Compression::ZStd, - Mbp1Msg::default_for_schema(Schema::Mbp1) - )] + #[case::zstd_tbbo_v1(1, Schema::Tbbo, Compression::ZStd, TbboMsg::default())] + #[case::zstd_mbp1_v1(1, Schema::Mbp1, Compression::ZStd, Mbp1Msg::default())] #[case::zstd_mbp10_v1(1, Schema::Mbp10, Compression::ZStd, Mbp10Msg::default())] #[case::zstd_ohlcv1d_v1( 1, @@ -840,18 +820,8 @@ mod tests { #[case::zstd_statistics_v1(1, Schema::Statistics, Compression::ZStd, StatMsg::default())] #[case::uncompressed_mbo_v2(2, Schema::Mbo, Compression::None, MboMsg::default())] #[case::uncompressed_trades_v2(2, Schema::Trades, Compression::None, TradeMsg::default())] - #[case::uncompressed_tbbo_v2( - 2, - Schema::Tbbo, - Compression::None, - TbboMsg::default_for_schema(Schema::Tbbo) - )] - #[case::uncompressed_mbp1_v2( - 2, - Schema::Mbp1, - Compression::None, - Mbp1Msg::default_for_schema(Schema::Mbp1) - )] + #[case::uncompressed_tbbo_v2(2, Schema::Tbbo, Compression::None, TbboMsg::default())] + #[case::uncompressed_mbp1_v2(2, Schema::Mbp1, Compression::None, Mbp1Msg::default())] #[case::uncompressed_mbp10_v2(2, Schema::Mbp10, Compression::None, Mbp10Msg::default())] #[case::uncompressed_ohlcv1d_v2( 2, @@ -897,18 +867,8 @@ mod tests { )] #[case::zstd_mbo_v2(2, Schema::Mbo, Compression::ZStd, MboMsg::default())] #[case::zstd_trades_v2(2, Schema::Trades, Compression::ZStd, TradeMsg::default())] - #[case::zstd_tbbo_v2( - 2, - Schema::Tbbo, - Compression::ZStd, - TbboMsg::default_for_schema(Schema::Tbbo) - )] - #[case::zstd_mbp1_v2( - 2, - Schema::Mbp1, - Compression::ZStd, - Mbp1Msg::default_for_schema(Schema::Mbp1) - )] + #[case::zstd_tbbo_v2(2, Schema::Tbbo, Compression::ZStd, TbboMsg::default())] + #[case::zstd_mbp1_v2(2, Schema::Mbp1, Compression::ZStd, Mbp1Msg::default())] #[case::zstd_cbbo_v2( 2, Schema::Cbbo, diff --git a/rust/dbn/src/decode/dbz.rs b/rust/dbn/src/decode/dbz.rs index fed71c0..1ec3376 100644 --- a/rust/dbn/src/decode/dbz.rs +++ b/rust/dbn/src/decode/dbz.rs @@ -418,13 +418,13 @@ mod tests { #[rstest] #[case::mbo(MboMsg::default(), Schema::Mbo, 2)] - #[case::mbp1(Mbp1Msg::default_for_schema(Schema::Mbp1), Schema::Mbp1, 2)] + #[case::mbp1(Mbp1Msg::default(), Schema::Mbp1, 2)] #[case::mbp10(Mbp10Msg::default(), Schema::Mbp10, 2)] #[case::ohlcv_1d(OhlcvMsg::default_for_schema(Schema::Ohlcv1D), Schema::Ohlcv1D, 0)] #[case::ohlcv_1h(OhlcvMsg::default_for_schema(Schema::Ohlcv1H), Schema::Ohlcv1H, 2)] #[case::ohlcv_1m(OhlcvMsg::default_for_schema(Schema::Ohlcv1M), Schema::Ohlcv1M, 2)] #[case::ohlcv_1s(OhlcvMsg::default_for_schema(Schema::Ohlcv1S), Schema::Ohlcv1S, 2)] - #[case::tbbo(TbboMsg::default_for_schema(Schema::Tbbo), Schema::Tbbo, 2)] + #[case::tbbo(TbboMsg::default(), Schema::Tbbo, 2)] #[case::trades(TradeMsg::default(), Schema::Trades, 2)] #[case::definition(InstrumentDefMsgV1::default(), Schema::Definition, 2)] fn test_decode_stream( diff --git a/rust/dbn/src/enums.rs b/rust/dbn/src/enums.rs index ad140d1..1153e04 100644 --- a/rust/dbn/src/enums.rs +++ b/rust/dbn/src/enums.rs @@ -40,8 +40,7 @@ impl From for char { } } -/// A [tick action](https://databento.com/docs/standards-and-conventions/common-fields-enums-types) -/// used to indicate order life cycle. +/// An [order event or order book operation](https://app0.databento.com/docs/api-reference-historical/basics/schemas-and-conventions). /// /// For example usage see: /// - [Order actions](https://databento.com/docs/examples/order-book/order-actions) diff --git a/rust/dbn/src/lib.rs b/rust/dbn/src/lib.rs index 593d54c..9e647e2 100644 --- a/rust/dbn/src/lib.rs +++ b/rust/dbn/src/lib.rs @@ -66,10 +66,10 @@ pub use crate::{ metadata::{MappingInterval, Metadata, MetadataBuilder, SymbolMapping}, publishers::{Dataset, Publisher, Venue}, record::{ - Bbo1MMsg, Bbo1SMsg, BidAskPair, Cbbo1MMsg, Cbbo1SMsg, CbboMsg, ConsolidatedBidAskPair, - ErrorMsg, HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg, OhlcvMsg, - Record, RecordHeader, RecordMut, StatMsg, StatusMsg, SymbolMappingMsg, SystemMsg, TbboMsg, - TradeMsg, WithTsOut, + Bbo1MMsg, Bbo1SMsg, BboMsg, BidAskPair, Cbbo1MMsg, Cbbo1SMsg, CbboMsg, + ConsolidatedBidAskPair, ErrorMsg, HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, + Mbp10Msg, Mbp1Msg, OhlcvMsg, Record, RecordHeader, RecordMut, StatMsg, StatusMsg, + SymbolMappingMsg, SystemMsg, TbboMsg, TradeMsg, WithTsOut, }, record_enum::{RecordEnum, RecordRefEnum}, record_ref::RecordRef, diff --git a/rust/dbn/src/python/record.rs b/rust/dbn/src/python/record.rs index 67ae212..cf0f557 100644 --- a/rust/dbn/src/python/record.rs +++ b/rust/dbn/src/python/record.rs @@ -10,7 +10,7 @@ use pyo3::{ use crate::{ compat::{ErrorMsgV1, InstrumentDefMsgV1, SymbolMappingMsgV1, SystemMsgV1}, record::{str_to_c_chars, CbboMsg, ConsolidatedBidAskPair}, - rtype, BidAskPair, ErrorMsg, FlagSet, HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, + rtype, BboMsg, BidAskPair, ErrorMsg, FlagSet, HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg, OhlcvMsg, Publisher, Record, RecordHeader, SType, SecurityUpdateAction, StatMsg, StatUpdateAction, StatusAction, StatusMsg, StatusReason, SymbolMappingMsg, SystemMsg, TradeMsg, TradingEvent, TriState, UserDefinedInstrument, WithTsOut, FIXED_PRICE_SCALE, @@ -113,7 +113,7 @@ impl MboMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -211,6 +211,138 @@ impl BidAskPair { } } +#[pymethods] +impl BboMsg { + #[new] + fn py_new( + rtype: u8, + publisher_id: u16, + instrument_id: u32, + ts_event: u64, + price: i64, + size: u32, + side: c_char, + ts_recv: u64, + sequence: u32, + flags: Option, + levels: Option, + ) -> Self { + Self { + hd: RecordHeader::new::(rtype, publisher_id, instrument_id, ts_event), + price, + size, + side, + flags: flags.unwrap_or_default(), + ts_recv, + sequence, + levels: [levels.unwrap_or_default()], + _reserved1: Default::default(), + _reserved2: Default::default(), + _reserved3: Default::default(), + } + } + + fn __bytes__(&self) -> &[u8] { + self.as_ref() + } + + fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py { + match op { + CompareOp::Eq => self.eq(other).into_py(py), + CompareOp::Ne => self.ne(other).into_py(py), + _ => py.NotImplemented(), + } + } + + fn __repr__(&self) -> String { + format!("{self:?}") + } + + #[getter] + fn rtype(&self) -> u8 { + self.hd.rtype + } + + #[getter] + fn publisher_id(&self) -> u16 { + self.hd.publisher_id + } + + #[getter] + fn instrument_id(&self) -> u32 { + self.hd.instrument_id + } + + #[getter] + fn ts_event(&self) -> u64 { + self.hd.ts_event + } + + #[getter] + #[pyo3(name = "pretty_price")] + fn py_pretty_price(&self) -> f64 { + self.price as f64 / FIXED_PRICE_SCALE as f64 + } + + #[getter] + #[pyo3(name = "pretty_ts_event")] + fn py_pretty_ts_event(&self, py: Python<'_>) -> PyResult { + get_utc_nanosecond_timestamp(py, self.ts_event()) + } + + #[getter] + #[pyo3(name = "pretty_ts_recv")] + fn py_pretty_ts_recv(&self, py: Python<'_>) -> PyResult { + get_utc_nanosecond_timestamp(py, self.ts_recv) + } + + #[pyo3(name = "record_size")] + fn py_record_size(&self) -> usize { + self.record_size() + } + + #[classattr] + fn size_hint() -> PyResult { + Ok(mem::size_of::()) + } + + #[getter] + #[pyo3(name = "side")] + fn py_side(&self) -> char { + self.side as u8 as char + } + + #[classattr] + #[pyo3(name = "_dtypes")] + fn py_dtypes() -> Vec<(String, String)> { + Self::field_dtypes("") + } + + #[classattr] + #[pyo3(name = "_price_fields")] + fn py_price_fields() -> Vec { + Self::price_fields("") + } + + #[classattr] + #[pyo3(name = "_timestamp_fields")] + fn py_timestamp_fields() -> Vec { + Self::timestamp_fields("") + } + + #[classattr] + #[pyo3(name = "_hidden_fields")] + fn py_hidden_fields() -> Vec { + Self::hidden_fields("") + } + + #[classattr] + #[pyo3(name = "_ordered_fields")] + fn py_ordered_fields() -> Vec { + Self::ordered_fields("") + } +} + #[pymethods] impl CbboMsg { #[new] @@ -305,7 +437,7 @@ impl CbboMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -513,7 +645,7 @@ impl TradeMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -653,7 +785,7 @@ impl Mbp1Msg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -805,7 +937,7 @@ impl Mbp10Msg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -948,7 +1080,7 @@ impl OhlcvMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[classattr] @@ -1083,7 +1215,7 @@ impl StatusMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[classattr] @@ -1392,7 +1524,7 @@ impl InstrumentDefMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -1801,7 +1933,7 @@ impl InstrumentDefMsgV1 { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2047,7 +2179,7 @@ impl ImbalanceMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2205,7 +2337,7 @@ impl StatMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[classattr] @@ -2295,7 +2427,7 @@ impl ErrorMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2391,7 +2523,7 @@ impl ErrorMsgV1 { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2522,7 +2654,7 @@ impl SymbolMappingMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2668,7 +2800,7 @@ impl SymbolMappingMsgV1 { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2770,7 +2902,7 @@ impl SystemMsg { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] @@ -2871,7 +3003,7 @@ impl SystemMsgV1 { #[classattr] fn size_hint() -> PyResult { - Ok(mem::size_of::()) + Ok(mem::size_of::()) } #[getter] diff --git a/rust/dbn/src/record.rs b/rust/dbn/src/record.rs index fa42596..789b7ed 100644 --- a/rust/dbn/src/record.rs +++ b/rust/dbn/src/record.rs @@ -170,16 +170,14 @@ pub struct ConsolidatedBidAskPair { /// The bid publisher ID assigned by Databento, which denotes the dataset and venue. #[dbn(fmt_method)] pub bid_pb: u16, - // Reserved for later usage - #[dbn(skip)] + // Reserved for later usage. #[doc(hidden)] #[cfg_attr(feature = "serde", serde(skip))] pub _reserved1: [c_char; 2], /// The ask publisher ID assigned by Databento, which denotes the dataset and venue. #[dbn(fmt_method)] pub ask_pb: u16, - // Reserved for later usage - #[dbn(skip)] + // Reserved for later usage. #[doc(hidden)] #[cfg_attr(feature = "serde", serde(skip))] pub _reserved2: [c_char; 2], @@ -253,7 +251,7 @@ pub struct TradeMsg { )] #[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope #[cfg_attr(test, derive(type_layout::TypeLayout))] -#[dbn_record(rtype::MBP_1, rtype::BBO_1S, rtype::BBO_1M)] +#[dbn_record(rtype::MBP_1)] pub struct Mbp1Msg { /// The common header. #[pyo3(get)] @@ -358,8 +356,67 @@ pub struct Mbp10Msg { pub levels: [BidAskPair; 10], } +/// Subsampled market by price with a known book depth of 1. The record of the +/// [`Bbo1S`](crate::Schema::Bbo1S) and [`Bbo1M`](crate::Schema::Bbo1M) schemas. +#[repr(C)] +#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "trivial_copy", derive(Copy))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "python", + pyo3::pyclass(set_all, dict, module = "databento_dbn", name = "BBOMsg"), + derive(crate::macros::PyFieldDesc) +)] +#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope +#[cfg_attr(test, derive(type_layout::TypeLayout))] +#[dbn_record(rtype::BBO_1S, rtype::BBO_1M)] +pub struct BboMsg { + /// The common header. + #[pyo3(get)] + pub hd: RecordHeader, + /// The price of the last trade expressed as a signed integer where every 1 unit + /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001. + #[dbn(fixed_price)] + #[pyo3(get)] + pub price: i64, + /// The quantity of the last trade. + #[pyo3(get)] + pub size: u32, + // Reserved for later usage. + #[doc(hidden)] + #[cfg_attr(feature = "serde", serde(skip))] + pub _reserved1: u8, + /// The side that initiated the last trade. Can be **A**sk for a sell order (or sell + /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or + /// **N**one where no side is specified by the original source. + #[dbn(c_char, encode_order(2))] + pub side: c_char, + /// A bit field indicating event end, message characteristics, and data quality. See + /// [`enums::flags`](crate::enums::flags) for possible values. + #[pyo3(get)] + pub flags: FlagSet, + // Reserved for later usage. + #[doc(hidden)] + #[cfg_attr(feature = "serde", serde(skip))] + pub _reserved2: u8, + /// The interval timestamp expressed as number of nanoseconds since the UNIX epoch. + #[dbn(encode_order(0), index_ts, unix_nanos)] + #[pyo3(get)] + pub ts_recv: u64, + // Reserved for later usage. + #[doc(hidden)] + #[cfg_attr(feature = "serde", serde(skip))] + pub _reserved3: [u8; 4], + /// The sequence number assigned at the venue of the last update. + #[pyo3(get)] + pub sequence: u32, + /// The top of the order book. + #[pyo3(get)] + pub levels: [BidAskPair; 1], +} + /// Consolidated market by price implementation with a known book depth of 1. The record of the -/// [`Cbbo`](crate::enums::Schema::Cbbo) schema. +/// [`Cbbo`](crate::Schema::Cbbo) schema. #[repr(C)] #[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)] #[cfg_attr(feature = "trivial_copy", derive(Copy))] @@ -420,9 +477,9 @@ pub struct CbboMsg { /// The record of the [`Tbbo`](crate::enums::Schema::Tbbo) schema. pub type TbboMsg = Mbp1Msg; /// The record of the [`Bbo1S`](crate::enums::Schema::Bbo1S) schema. -pub type Bbo1SMsg = Mbp1Msg; +pub type Bbo1SMsg = BboMsg; /// The record of the [`Bbo1M`](crate::enums::Schema::Bbo1M) schema. -pub type Bbo1MMsg = Mbp1Msg; +pub type Bbo1MMsg = BboMsg; /// The record of the [`Cbbo1S`](crate::enums::Schema::Cbbo1S) schema. pub type Cbbo1SMsg = CbboMsg; /// The record of the [`Cbbo1M`](crate::enums::Schema::Cbbo1M) schema. @@ -1113,6 +1170,7 @@ pub struct WithTsOut { #[cfg(test)] mod tests { + use mem::offset_of; use rstest::rstest; use type_layout::{Field, TypeLayout}; @@ -1180,8 +1238,9 @@ mod tests { #[case::header(RecordHeader::default::(rtype::MBO), 16)] #[case::mbo(MboMsg::default(), 56)] #[case::ba_pair(BidAskPair::default(), 32)] - #[case::mbp1(Mbp1Msg::default_for_schema(Schema::Mbp1), mem::size_of::() + mem::size_of::())] + #[case::mbp1(Mbp1Msg::default(), mem::size_of::() + mem::size_of::())] #[case::mbp10(Mbp10Msg::default(), mem::size_of::() + mem::size_of::() * 10)] + #[case::bbo(BboMsg::default_for_schema(Schema::Bbo1S), mem::size_of::())] #[case::cbbo(CbboMsg::default_for_schema(Schema::Cbbo), mem::size_of::())] #[case::trade(TradeMsg::default(), 48)] #[case::definition(InstrumentDefMsg::default(), 400)] @@ -1201,7 +1260,8 @@ mod tests { #[case::header(RecordHeader::default::(rtype::MBO))] #[case::mbo(MboMsg::default())] #[case::ba_pair(BidAskPair::default())] - #[case::mbp1(Mbp1Msg::default_for_schema(crate::Schema::Mbp1))] + #[case::mbp1(Mbp1Msg::default())] + #[case::bbo(BboMsg::default_for_schema(crate::Schema::Bbo1S))] #[case::cbbo(CbboMsg::default_for_schema(crate::Schema::Cbbo))] #[case::mbp10(Mbp10Msg::default())] #[case::trade(TradeMsg::default())] @@ -1223,6 +1283,18 @@ mod tests { } } + #[test] + fn test_bbo_alignment_matches_mbp1() { + assert_eq!(offset_of!(BboMsg, hd), offset_of!(Mbp1Msg, hd)); + assert_eq!(offset_of!(BboMsg, price), offset_of!(Mbp1Msg, price)); + assert_eq!(offset_of!(BboMsg, size), offset_of!(Mbp1Msg, size)); + assert_eq!(offset_of!(BboMsg, side), offset_of!(Mbp1Msg, side)); + assert_eq!(offset_of!(BboMsg, flags), offset_of!(Mbp1Msg, flags)); + assert_eq!(offset_of!(BboMsg, ts_recv), offset_of!(Mbp1Msg, ts_recv)); + assert_eq!(offset_of!(BboMsg, sequence), offset_of!(Mbp1Msg, sequence)); + assert_eq!(offset_of!(BboMsg, levels), offset_of!(Mbp1Msg, levels)); + } + #[test] fn test_mbo_index_ts() { let rec = MboMsg { diff --git a/rust/dbn/src/record/impl_default.rs b/rust/dbn/src/record/impl_default.rs index 3a969d4..cfee0ae 100644 --- a/rust/dbn/src/record/impl_default.rs +++ b/rust/dbn/src/record/impl_default.rs @@ -26,7 +26,7 @@ impl Default for MboMsg { flags: FlagSet::default(), channel_id: 0, action: 0, - side: 0, + side: Side::None as c_char, ts_recv: UNDEF_TIMESTAMP, ts_in_delta: 0, sequence: 0, @@ -69,7 +69,7 @@ impl Default for TradeMsg { price: UNDEF_PRICE, size: UNDEF_ORDER_SIZE, action: 0, - side: 0, + side: Side::None as c_char, flags: FlagSet::default(), depth: 0, ts_recv: UNDEF_TIMESTAMP, @@ -79,15 +79,14 @@ impl Default for TradeMsg { } } -impl Mbp1Msg { - /// Creates a new default Mbp1Msg for the given `schema`. - pub fn default_for_schema(schema: Schema) -> Self { +impl Default for Mbp1Msg { + fn default() -> Self { Self { - hd: RecordHeader::default::(RType::from(schema) as u8), + hd: RecordHeader::default::(rtype::MBP_1), price: UNDEF_PRICE, size: UNDEF_ORDER_SIZE, action: 0, - side: 0, + side: Side::None as c_char, flags: FlagSet::default(), depth: 0, ts_recv: UNDEF_TIMESTAMP, @@ -98,15 +97,34 @@ impl Mbp1Msg { } } +impl BboMsg { + /// Creates a new default `BboMsg` for the given `schema`. + pub fn default_for_schema(schema: Schema) -> Self { + Self { + hd: RecordHeader::default::(RType::from(schema) as u8), + price: UNDEF_PRICE, + size: UNDEF_ORDER_SIZE, + side: Side::None as c_char, + flags: FlagSet::default(), + ts_recv: UNDEF_TIMESTAMP, + sequence: 0, + levels: Default::default(), + _reserved1: Default::default(), + _reserved2: Default::default(), + _reserved3: Default::default(), + } + } +} + impl CbboMsg { - /// Creates a new default CbboMsg for the given `schema`. + /// Creates a new default `CbboMsg` for the given `schema`. pub fn default_for_schema(schema: Schema) -> Self { Self { hd: RecordHeader::default::(RType::from(schema) as u8), price: UNDEF_PRICE, size: UNDEF_ORDER_SIZE, action: 0, - side: 0, + side: Side::None as c_char, flags: FlagSet::default(), _reserved: [0; 1], ts_recv: UNDEF_TIMESTAMP, @@ -124,7 +142,7 @@ impl Default for Mbp10Msg { price: UNDEF_PRICE, size: UNDEF_ORDER_SIZE, action: 0, - side: 0, + side: Side::None as c_char, flags: FlagSet::default(), depth: 0, ts_recv: UNDEF_TIMESTAMP, diff --git a/rust/dbn/src/record/methods.rs b/rust/dbn/src/record/methods.rs index 8d77b88..a0ff160 100644 --- a/rust/dbn/src/record/methods.rs +++ b/rust/dbn/src/record/methods.rs @@ -109,8 +109,8 @@ impl MboMsg { .map_err(|_| Error::conversion::(format!("{:#04X}", self.action as u8))) } - /// Parses the raw capture-server-received timestamp into a datetime. Returns `None` - /// if `ts_recv` contains the sentinel for a null timestamp. + /// Parses the raw interval timestamp into a datetime. Returns `None` if `ts_recv` + /// contains the sentinel for a null timestamp. pub fn ts_recv(&self) -> Option { ts_to_dt(self.ts_recv) } @@ -154,6 +154,24 @@ impl TradeMsg { } } +impl BboMsg { + /// Tries to convert the raw `side` to an enum. + /// + /// # Errors + /// This function returns an error if the `side` field does not + /// contain a valid [`Side`]. + pub fn side(&self) -> crate::Result { + Side::try_from(self.side as u8) + .map_err(|_| Error::conversion::(format!("{:#04X}", self.side as u8))) + } + + /// Parses the raw capture-server-received timestamp into a datetime. Returns `None` + /// if `ts_recv` contains the sentinel for a null timestamp. + pub fn ts_recv(&self) -> Option { + ts_to_dt(self.ts_recv) + } +} + impl CbboMsg { /// Tries to convert the raw `side` to an enum. /// From fa7afa210f00138282fd6b6d6c1072f53d246765 Mon Sep 17 00:00:00 2001 From: Carter Green Date: Wed, 3 Jul 2024 15:05:57 -0500 Subject: [PATCH 2/4] ADD: Add status test data --- rust/dbn/src/decode/dbn/async.rs | 4 +++- rust/dbn/src/decode/dbn/sync.rs | 6 ++++-- tests/data/test_data.status.dbn | Bin 0 -> 513 bytes tests/data/test_data.status.dbn.zst | Bin 0 -> 188 bytes 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 tests/data/test_data.status.dbn create mode 100644 tests/data/test_data.status.dbn.zst diff --git a/rust/dbn/src/decode/dbn/async.rs b/rust/dbn/src/decode/dbn/async.rs index 7b6a014..273b8a4 100644 --- a/rust/dbn/src/decode/dbn/async.rs +++ b/rust/dbn/src/decode/dbn/async.rs @@ -665,7 +665,7 @@ mod tests { DbnEncodable, }, rtype, CbboMsg, Error, ErrorMsg, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg, - OhlcvMsg, RecordHeader, Result, Schema, StatMsg, TbboMsg, TradeMsg, WithTsOut, + OhlcvMsg, RecordHeader, Result, Schema, StatMsg, StatusMsg, TbboMsg, TradeMsg, WithTsOut, }; #[rstest] @@ -682,6 +682,7 @@ mod tests { #[case::definitions(Schema::Definition, InstrumentDefMsg::default())] #[case::imbalance(Schema::Imbalance, ImbalanceMsg::default())] #[case::statistics(Schema::Statistics, StatMsg::default())] + #[case::status(Schema::Status, StatusMsg::default())] #[tokio::test] async fn test_dbn_identity( #[case] schema: Schema, @@ -726,6 +727,7 @@ mod tests { #[case::definitions(Schema::Definition, InstrumentDefMsg::default())] #[case::imbalance(Schema::Imbalance, ImbalanceMsg::default())] #[case::statistics(Schema::Statistics, StatMsg::default())] + #[case::status(Schema::Status, StatusMsg::default())] #[tokio::test] async fn test_dbn_zstd_identity( #[case] schema: Schema, diff --git a/rust/dbn/src/decode/dbn/sync.rs b/rust/dbn/src/decode/dbn/sync.rs index 4b8b38f..79a1051 100644 --- a/rust/dbn/src/decode/dbn/sync.rs +++ b/rust/dbn/src/decode/dbn/sync.rs @@ -685,8 +685,8 @@ mod tests { dbn::Encoder, DbnEncodable, DbnRecordEncoder, DynWriter, EncodeDbn, EncodeRecord, }, rtype, CbboMsg, Compression, Error, ErrorMsg, ImbalanceMsg, InstrumentDefMsg, MboMsg, - Mbp10Msg, Mbp1Msg, MetadataBuilder, OhlcvMsg, RecordHeader, Result, StatMsg, TbboMsg, - TradeMsg, WithTsOut, SYMBOL_CSTR_LEN, + Mbp10Msg, Mbp1Msg, MetadataBuilder, OhlcvMsg, RecordHeader, Result, StatMsg, StatusMsg, + TbboMsg, TradeMsg, WithTsOut, SYMBOL_CSTR_LEN, }; #[test] @@ -865,6 +865,7 @@ mod tests { Compression::None, StatMsg::default() )] + #[case::uncompressed_status_v2(2, Schema::Status, Compression::None, StatusMsg::default())] #[case::zstd_mbo_v2(2, Schema::Mbo, Compression::ZStd, MboMsg::default())] #[case::zstd_trades_v2(2, Schema::Trades, Compression::ZStd, TradeMsg::default())] #[case::zstd_tbbo_v2(2, Schema::Tbbo, Compression::ZStd, TbboMsg::default())] @@ -908,6 +909,7 @@ mod tests { )] #[case::zstd_imbalance_v2(2, Schema::Imbalance, Compression::ZStd, ImbalanceMsg::default())] #[case::zstd_statistics_v2(2, Schema::Statistics, Compression::ZStd, StatMsg::default())] + #[case::zstd_status_v2(2, Schema::Status, Compression::ZStd, StatusMsg::default())] fn test_dbn_identity( #[case] version: u8, #[case] schema: Schema, diff --git a/tests/data/test_data.status.dbn b/tests/data/test_data.status.dbn new file mode 100644 index 0000000000000000000000000000000000000000..9b01cd4d5f215eb4c324fafaf0809fcd51b9526f GIT binary patch literal 513 zcmZ>9@?(l*WMFXjaf;CMbqO$LfBIklt~5!)Xm@; z>|scTR$N-BXe7ue9(xl;ULZC#u`t4AB?;nOLW~SqqCgD^?M$p&LBR!5X|7QH(}%jVp9_fBQ7CEhAdH_Y(hH|>(&r4_Q=ROkIDP){S6V*{q&E~ z@5o%gmH$G-4(Y55TKSK$;GowAATPJqz#Bxf2{3%Spu0f)fSg72s!q{HZ*Bu+_P(;r cwdITs;%gh4m|qxLNF6xCa0qBU+rw^U0BY(tj{pDw literal 0 HcmV?d00001 From c657b56e004e091fdf7072b5f2d4cf79c05c3cfe Mon Sep 17 00:00:00 2001 From: Carter Green Date: Mon, 8 Jul 2024 10:30:16 -0500 Subject: [PATCH 3/4] MOD: Upgrade pyo3 dependency --- CHANGELOG.md | 4 + Cargo.lock | 144 +++----- Cargo.toml | 4 +- python/python/databento_dbn/_lib.pyi | 30 +- python/src/dbn_decoder.rs | 29 +- python/src/encode.rs | 5 +- python/src/transcoder.rs | 119 ++++--- rust/dbn-macros/Cargo.toml | 6 +- rust/dbn/Cargo.toml | 2 +- rust/dbn/src/python.rs | 2 +- rust/dbn/src/python/metadata.rs | 48 ++- rust/dbn/src/python/record.rs | 473 +++++++++++++++++++++------ 12 files changed, 557 insertions(+), 309 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd098e..f2bef4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,16 @@ ### Enhancements - Added `BboMsg` record struct for future `bbo-1m` and `bbo-1s` schemas +- Upgraded `pyo3` version to 0.22.1 +- Upgraded `json-writer` to 0.4 ### Breaking changes - Added `Default` trait implementation for `Mbp1Msg` due to it no longer needing to support multiple `rtype` values. The `default_for_schema` function has been removed - Changed `Bbo1sMsg` and `Bbo1mMsg` to be aliases for `BboMsg` - Changed the default value of the `side` fields to `Side::None` +- Reordered parameters and added defaults to Python `Metadata` initializer to match + required arguments in Rust ## 0.18.3 - 2024-07-02 diff --git a/Cargo.lock b/Cargo.lock index 029e176..d6149d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,12 +130,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.4.2" @@ -165,7 +159,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" dependencies = [ - "heck", + "heck 0.4.1", "indexmap 1.9.3", "log", "proc-macro2", @@ -221,10 +215,10 @@ version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -327,7 +321,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", "trybuild", ] @@ -446,7 +440,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -515,6 +509,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.8" @@ -549,15 +549,15 @@ checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "json-writer" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a46aa3d39e9495d71c03e7b68981cd900f17e7ddf2ff97a14583bfbd866f8d23" +checksum = "279046e6427c19c86f93df06fe9dc90c32b43f4a2a85bb3083d579e4a1e7ef03" dependencies = [ "itoa", "ryu", @@ -575,16 +575,6 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.20" @@ -673,7 +663,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -691,29 +681,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", -] - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -785,24 +752,24 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.21.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +checksum = "4e99090d12f6182924499253aaa1e73bf15c69cea8d2774c3c781e35badc3548" dependencies = [ "cfg-if", "indoc", "libc", "memoffset 0.9.0", - "parking_lot", + "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", @@ -812,9 +779,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.21.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +checksum = "7879eb018ac754bba32cb0eec7526391c02c14a093121857ed09fbf1d1057d41" dependencies = [ "once_cell", "target-lexicon", @@ -822,9 +789,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.21.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +checksum = "ce2baa5559a411fc1cf519295f24c34b53d5d725818bc96b5abf94762da09041" dependencies = [ "libc", "pyo3-build-config", @@ -832,27 +799,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.21.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +checksum = "049621c20a23f2def20f4fe67978d1da8d8a883d64b9c21362f3b776e254edc7" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] name = "pyo3-macros-backend" -version = "0.21.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +checksum = "0e969ee2e025435f1819d31a275ba4bb9cbbdf3ac535227fdbd85b9322ffe144" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -864,15 +831,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "regex" version = "1.10.3" @@ -934,7 +892,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.66", + "syn 2.0.69", "unicode-ident", ] @@ -959,7 +917,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -974,15 +932,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "semver" @@ -1007,7 +959,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -1039,12 +991,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "smallvec" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - [[package]] name = "strsim" version = "0.11.0" @@ -1066,11 +1012,11 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -1086,9 +1032,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6" dependencies = [ "proc-macro2", "quote", @@ -1155,7 +1101,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -1210,7 +1156,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.69", ] [[package]] @@ -1269,9 +1215,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a5f13f11071020bb12de7a16b925d2d58636175c20c11dc5f96cb64bb6c9b3" +checksum = "5b1e5645f2ee8025c2f1d75e1138f2dd034d74e6ba54620f3c569ba2a2a1ea06" dependencies = [ "glob", "serde", diff --git a/Cargo.toml b/Cargo.toml index 57f9c1e..bfb507c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ license = "Apache-2.0" [workspace.dependencies] anyhow = "1.0.86" csv = "1.3" -pyo3 = "0.21.2" -pyo3-build-config = "0.21.2" +pyo3 = "0.22.1" +pyo3-build-config = "0.22.1" rstest = "0.21.0" serde = { version = "1.0", features = ["derive"] } time = ">=0.3.35" diff --git a/python/python/databento_dbn/_lib.pyi b/python/python/databento_dbn/_lib.pyi index c7373b1..d14a287 100644 --- a/python/python/databento_dbn/_lib.pyi +++ b/python/python/databento_dbn/_lib.pyi @@ -781,13 +781,13 @@ class Metadata(SupportsBytes): self, dataset: str, start: int, + stype_in: SType | None, stype_out: SType, - symbols: list[str], - partial: list[str], - not_found: list[str], - mappings: Sequence[SymbolMapping], - schema: Schema | None = None, - stype_in: SType | None = None, + schema: Schema | None, + symbols: list[str] | None = None, + partial: list[str] | None = None, + not_found: list[str] | None = None, + mappings: Sequence[SymbolMapping] | None = None, end: int | None = None, limit: int | None = None, ts_out: bool | None = None, @@ -954,8 +954,8 @@ class Metadata(SupportsBytes): ---------- data : bytes The bytes to decode from. - upgrade_policy : VersionUpgradePolicy - How to decode data from prior DBN versions. Defaults to decoding as-is. + upgrade_policy : VersionUpgradePolicy, default UPGRADE + How to decode data from prior DBN versions. Defaults to upgrade decoding. Returns ------- @@ -4750,8 +4750,8 @@ class DBNDecoder: input_version : int, default current DBN version Specify the DBN version of the input. Only used when transcoding data without metadata. - upgrade_policy : VersionUpgradePolicy - How to decode data from prior DBN versions. Defaults to decoding as-is. + upgrade_policy : VersionUpgradePolicy, default UPGRADE + How to decode data from prior DBN versions. Defaults to upgrade decoding. """ def __init__( @@ -4829,10 +4829,10 @@ class Transcoder: pretty_ts : bool, default True Whether to serialize nanosecond UNIX timestamps as ISO8601 datetime strings. Only applicable to CSV and JSON. - map_symbols : bool, default True + map_symbols : bool, default None If symbology mappings from the metadata should be used to create a 'symbol' field, mapping the instrument ID to its requested symbol for - every record. + every record. Defaults to True for text encodings and False for DBN. has_metadata : bool, default True Whether the input bytes begin with DBN metadata. Pass False to transcode individual records or a fragment of a DBN stream. @@ -4848,8 +4848,8 @@ class Transcoder: input_version : int, default current DBN version Specify the DBN version of the input. Only used when transcoding data without metadata. - upgrade_policy : VersionUpgradePolicy - How to decode data from prior DBN versions. Defaults to decoding as-is. + upgrade_policy : VersionUpgradePolicy, default UPGRADE + How to decode data from prior DBN versions. Defaults to upgrade decoding. """ def __init__( @@ -4859,7 +4859,7 @@ class Transcoder: compression: Compression, pretty_px: bool = True, pretty_ts: bool = True, - map_symbols: bool = True, + map_symbols: bool | None = None, has_metadata: bool = True, ts_out: bool = False, symbol_interval_map: dict[int, list[tuple[dt.date, dt.date, str]]] | None = None, diff --git a/python/src/dbn_decoder.rs b/python/src/dbn_decoder.rs index 57aac9a..5bd7b63 100644 --- a/python/src/dbn_decoder.rs +++ b/python/src/dbn_decoder.rs @@ -20,18 +20,24 @@ pub struct DbnDecoder { #[pymethods] impl DbnDecoder { #[new] + #[pyo3(signature = ( + has_metadata = true, + ts_out = false, + input_version = dbn::DBN_VERSION, + upgrade_policy = VersionUpgradePolicy::default() + ))] fn new( - has_metadata: Option, - ts_out: Option, - input_version: Option, - upgrade_policy: Option, + has_metadata: bool, + ts_out: bool, + input_version: u8, + upgrade_policy: VersionUpgradePolicy, ) -> Self { Self { buffer: io::Cursor::default(), - has_decoded_metadata: !has_metadata.unwrap_or(true), - ts_out: ts_out.unwrap_or_default(), - input_version: input_version.unwrap_or(dbn::DBN_VERSION), - upgrade_policy: upgrade_policy.unwrap_or_default(), + has_decoded_metadata: !has_metadata, + ts_out, + input_version, + upgrade_policy, } } @@ -128,7 +134,7 @@ mod tests { encode::{dbn::Encoder, EncodeRecord}, enums::{rtype, SType, Schema}, record::{ErrorMsg, OhlcvMsg, RecordHeader}, - MetadataBuilder, + MetadataBuilder, DBN_VERSION, }; use pyo3::{py_run, types::PyString}; @@ -138,7 +144,7 @@ mod tests { #[test] fn test_partial_metadata_and_records() { setup(); - let mut target = DbnDecoder::new(None, None, None, None); + let mut target = DbnDecoder::new(true, false, DBN_VERSION, VersionUpgradePolicy::default()); let buffer = Vec::new(); let mut encoder = Encoder::new( buffer, @@ -180,7 +186,8 @@ mod tests { #[test] fn test_full_with_partial_record() { setup(); - let mut decoder = DbnDecoder::new(None, None, None, None); + let mut decoder = + DbnDecoder::new(true, false, DBN_VERSION, VersionUpgradePolicy::default()); let buffer = Vec::new(); let mut encoder = Encoder::new( buffer, diff --git a/python/src/encode.rs b/python/src/encode.rs index 157fe41..b1caa0d 100644 --- a/python/src/encode.rs +++ b/python/src/encode.rs @@ -8,6 +8,7 @@ use pyo3::{exceptions::PyTypeError, intern, prelude::*, types::PyBytes}; /// Updates existing fields that have already been written to the given file. #[pyfunction] +#[pyo3(signature = (file, start, end = None, limit = None))] pub fn update_encoded_metadata( _py: Python<'_>, mut file: PyFileLike, @@ -32,8 +33,8 @@ pub struct PyFileLike { inner: PyObject, } -impl<'source> FromPyObject<'source> for PyFileLike { - fn extract(any: &'source PyAny) -> PyResult { +impl<'py> FromPyObject<'py> for PyFileLike { + fn extract_bound(any: &Bound<'py, pyo3::PyAny>) -> PyResult { Python::with_gil(|py| { let obj: PyObject = any.extract()?; if obj.getattr(py, intern!(py, "read")).is_err() { diff --git a/python/src/transcoder.rs b/python/src/transcoder.rs index 0cc8106..38efefa 100644 --- a/python/src/transcoder.rs +++ b/python/src/transcoder.rs @@ -23,24 +23,39 @@ use crate::encode::PyFileLike; #[pyclass(module = "databento_dbn")] pub struct Transcoder(Box); -pub type PySymbolIntervalMap<'py> = HashMap>; +pub type PySymbolIntervalMap<'py> = + HashMap, Bound<'py, PyDate>, String)>>; #[pymethods] impl Transcoder { #[new] + #[pyo3(signature = ( + file, + encoding, + compression, + pretty_px = true, + pretty_ts = true, + map_symbols = None, + has_metadata = true, + ts_out = false, + symbol_interval_map = None, + schema = None, + input_version = dbn::DBN_VERSION, + upgrade_policy = VersionUpgradePolicy::default(), + ))] fn new( file: PyFileLike, encoding: Encoding, compression: Compression, - pretty_px: Option, - pretty_ts: Option, + pretty_px: bool, + pretty_ts: bool, map_symbols: Option, - has_metadata: Option, - ts_out: Option, + has_metadata: bool, + ts_out: bool, symbol_interval_map: Option, schema: Option, - input_version: Option, - upgrade_policy: Option, + input_version: u8, + upgrade_policy: VersionUpgradePolicy, ) -> PyResult { let symbol_map = if let Some(symbol_interval_map) = symbol_interval_map { let mut symbol_map = TsSymbolMap::new(); @@ -49,8 +64,8 @@ impl Transcoder { if symbol.is_empty() { continue; } - let start_date = py_to_time_date(start_date)?; - let end_date = py_to_time_date(end_date)?; + let start_date = py_to_time_date(&start_date)?; + let end_date = py_to_time_date(&end_date)?; symbol_map.insert(iid, start_date, end_date, Arc::new(symbol))?; } } @@ -158,15 +173,15 @@ impl Inner { fn new( file: PyFileLike, compression: Compression, - pretty_px: Option, - pretty_ts: Option, + pretty_px: bool, + pretty_ts: bool, map_symbols: Option, - has_metadata: Option, - ts_out: Option, + has_metadata: bool, + ts_out: bool, symbol_map: Option, schema: Option, - input_version: Option, - upgrade_policy: Option, + input_version: u8, + upgrade_policy: VersionUpgradePolicy, ) -> PyResult { if OUTPUT_ENC == Encoding::Dbn as u8 && map_symbols.unwrap_or(false) { return Err(PyValueError::new_err( @@ -176,15 +191,15 @@ impl Inner { Ok(Self { buffer: io::Cursor::default(), output: DynWriter::new(BufWriter::new(file), compression)?, - use_pretty_px: pretty_px.unwrap_or(true), - use_pretty_ts: pretty_ts.unwrap_or(true), + use_pretty_px: pretty_px, + use_pretty_ts: pretty_ts, map_symbols: map_symbols.unwrap_or(true), - has_decoded_metadata: !has_metadata.unwrap_or(true), - ts_out: ts_out.unwrap_or(false), + has_decoded_metadata: !has_metadata, + ts_out, symbol_map: symbol_map.map(SymbolMap::Historical).unwrap_or_default(), schema, - input_version: input_version.unwrap_or(dbn::DBN_VERSION), - upgrade_policy: upgrade_policy.unwrap_or_default(), + input_version, + upgrade_policy, }) } @@ -442,7 +457,7 @@ mod tests { datasets::XNAS_ITCH, encode::{DbnEncoder, EncodeRecord}, rtype, ErrorMsg, MappingInterval, MetadataBuilder, OhlcvMsg, RecordHeader, SType, Schema, - SymbolMapping, SymbolMappingMsg, WithTsOut, UNDEF_TIMESTAMP, + SymbolMapping, SymbolMappingMsg, WithTsOut, DBN_VERSION, UNDEF_TIMESTAMP, }; use rstest::rstest; use time::macros::{date, datetime}; @@ -473,15 +488,15 @@ mod tests { Py::new(py, file).unwrap().extract(py).unwrap(), Encoding::Json, Compression::None, + true, + true, None, + true, + false, None, None, - None, - None, - None, - None, - None, - None, + DBN_VERSION, + VersionUpgradePolicy::default(), ) .unwrap() }); @@ -548,15 +563,15 @@ mod tests { Py::new(py, file).unwrap().extract(py).unwrap(), Encoding::Csv, Compression::None, + true, + true, None, + true, + false, None, None, - None, - None, - None, - None, - None, - None, + DBN_VERSION, + VersionUpgradePolicy::default(), ) .unwrap() }); @@ -622,15 +637,15 @@ mod tests { Py::new(py, file).unwrap().extract(py).unwrap(), encoding, Compression::None, - None, - Some(false), + true, + false, Some(map_symbols), - None, - Some(true), - None, - None, + true, + true, None, None, + DBN_VERSION, + VersionUpgradePolicy::default(), ) .unwrap() }); @@ -748,15 +763,15 @@ mod tests { Py::new(py, file).unwrap().extract(py).unwrap(), encoding, Compression::None, - None, - None, + true, + true, Some(map_symbols), - None, - None, + true, + false, None, Some(Schema::Ohlcv1S), - None, - None, + DBN_VERSION, + VersionUpgradePolicy::default(), ) .unwrap() }); @@ -881,15 +896,15 @@ mod tests { Py::new(py, file).unwrap().extract(py).unwrap(), encoding, Compression::None, + true, + true, None, - None, - None, - None, - None, + true, + false, None, Some(schema), - None, - None, + DBN_VERSION, + VersionUpgradePolicy::default(), ) .unwrap() }); diff --git a/rust/dbn-macros/Cargo.toml b/rust/dbn-macros/Cargo.toml index 9d7291b..27cc873 100644 --- a/rust/dbn-macros/Cargo.toml +++ b/rust/dbn-macros/Cargo.toml @@ -13,13 +13,13 @@ proc-macro = true [dependencies] # Get name of current crate in macros, like $crate in macro_rules macros proc-macro-crate = "3.1.0" -proc-macro2 = "1.0.85" +proc-macro2 = "1.0.86" # Convert code to token streams quote = "1.0.36" # Token parsing -syn = { version = "2.0.66", features = ["full"] } +syn = { version = "2.0.69", features = ["full"] } [dev-dependencies] csv = { workspace = true } dbn = { path = "../dbn" } -trybuild = "1.0.96" +trybuild = "1.0.97" diff --git a/rust/dbn/Cargo.toml b/rust/dbn/Cargo.toml index b5a23a3..3a670b2 100644 --- a/rust/dbn/Cargo.toml +++ b/rust/dbn/Cargo.toml @@ -34,7 +34,7 @@ fallible-streaming-iterator = { version = "0.1.9", features = ["std"] } itoa = "1.0" num_enum = "0.7" pyo3 = { workspace = true, optional = true } -json-writer = "0.3" +json-writer = "0.4" serde = { workspace = true, features = ["derive"], optional = true } # extra enum traits for Python strum = { version = "0.26", features = ["derive"], optional = true } diff --git a/rust/dbn/src/python.rs b/rust/dbn/src/python.rs index 2a490a4..384145a 100644 --- a/rust/dbn/src/python.rs +++ b/rust/dbn/src/python.rs @@ -83,7 +83,7 @@ impl IntoPy for FlagSet { /// /// # Errors /// This function returns an error if input has an invalid month. -pub fn py_to_time_date(py_date: &PyDate) -> PyResult { +pub fn py_to_time_date(py_date: &Bound<'_, PyDate>) -> PyResult { let month = time::Month::try_from(py_date.get_month()).map_err(|e| DBNError::new_err(e.to_string()))?; time::Date::from_calendar_date(py_date.get_year(), month, py_date.get_day()) diff --git a/rust/dbn/src/python/metadata.rs b/rust/dbn/src/python/metadata.rs index b544c44..50dea84 100644 --- a/rust/dbn/src/python/metadata.rs +++ b/rust/dbn/src/python/metadata.rs @@ -20,35 +20,50 @@ use super::{py_to_time_date, to_py_err}; #[pymethods] impl Metadata { #[new] + #[pyo3(signature = ( + dataset, + start, + stype_in, + stype_out, + schema, + symbols=None, + partial=None, + not_found=None, + mappings=None, + end=None, + limit=None, + ts_out=None, + version=crate::DBN_VERSION, + ))] fn py_new( dataset: String, start: u64, + stype_in: Option, stype_out: SType, - symbols: Vec, - partial: Vec, - not_found: Vec, - mappings: Vec, schema: Option, - stype_in: Option, + symbols: Option>, + partial: Option>, + not_found: Option>, + mappings: Option>, end: Option, limit: Option, ts_out: Option, - version: Option, + version: u8, ) -> Metadata { Metadata::builder() .dataset(dataset) .start(start) .stype_out(stype_out) - .symbols(symbols) - .partial(partial) - .not_found(not_found) - .mappings(mappings) + .symbols(symbols.unwrap_or_default()) + .partial(partial.unwrap_or_default()) + .not_found(not_found.unwrap_or_default()) + .mappings(mappings.unwrap_or_default()) .schema(schema) .stype_in(stype_in) .end(NonZeroU64::new(end.unwrap_or_default())) .limit(NonZeroU64::new(limit.unwrap_or_default())) .ts_out(ts_out.unwrap_or_default()) - .version(version.unwrap_or(crate::DBN_VERSION)) + .version(version) .build() } @@ -78,14 +93,13 @@ impl Metadata { res } - #[pyo3(name = "decode")] + #[pyo3(name = "decode", signature = (data, upgrade_policy = VersionUpgradePolicy::default()))] #[classmethod] fn py_decode( _cls: &Bound, data: &Bound, - upgrade_policy: Option, + upgrade_policy: VersionUpgradePolicy, ) -> PyResult { - let upgrade_policy = upgrade_policy.unwrap_or_default(); let reader = io::BufReader::new(data.as_bytes()); let mut metadata = DynDecoder::inferred_with_buffer(reader, upgrade_policy)? .metadata() @@ -121,8 +135,8 @@ impl ToPyObject for SymbolMapping { } } -impl<'source> FromPyObject<'source> for MappingInterval { - fn extract(ob: &'source PyAny) -> PyResult { +impl<'py> FromPyObject<'py> for MappingInterval { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let start_date = ob .getattr(intern!(ob.py(), "start_date")) .map_err(|_| to_py_err("Missing start_date".to_owned())) @@ -179,7 +193,7 @@ impl IntoPy for MappingInterval { } } -fn extract_date(any: &PyAny) -> PyResult { +fn extract_date(any: Bound<'_, PyAny>) -> PyResult { let py_date = any.downcast::().map_err(PyErr::from)?; py_to_time_date(py_date) } diff --git a/rust/dbn/src/python/record.rs b/rust/dbn/src/python/record.rs index cf0f557..063fec0 100644 --- a/rust/dbn/src/python/record.rs +++ b/rust/dbn/src/python/record.rs @@ -22,6 +22,21 @@ use super::{to_py_err, PyFieldDesc}; #[pymethods] impl MboMsg { #[new] + #[pyo3(signature = ( + publisher_id, + instrument_id, + ts_event, + order_id, + price, + size, + channel_id, + action, + side, + ts_recv, + ts_in_delta, + sequence, + flags = None, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -162,21 +177,29 @@ impl MboMsg { #[pymethods] impl BidAskPair { #[new] + #[pyo3(signature = ( + bid_px = UNDEF_PRICE, + ask_px = UNDEF_PRICE, + bid_sz = 0, + ask_sz = 0, + bid_ct = 0, + ask_ct = 0, + ))] fn py_new( - bid_px: Option, - ask_px: Option, - bid_sz: Option, - ask_sz: Option, - bid_ct: Option, - ask_ct: Option, + bid_px: i64, + ask_px: i64, + bid_sz: u32, + ask_sz: u32, + bid_ct: u32, + ask_ct: u32, ) -> Self { Self { - bid_px: bid_px.unwrap_or(UNDEF_PRICE), - ask_px: ask_px.unwrap_or(UNDEF_PRICE), - bid_sz: bid_sz.unwrap_or_default(), - ask_sz: ask_sz.unwrap_or_default(), - bid_ct: bid_ct.unwrap_or_default(), - ask_ct: ask_ct.unwrap_or_default(), + bid_px, + ask_px, + bid_sz, + ask_sz, + bid_ct, + ask_ct, } } @@ -214,6 +237,19 @@ impl BidAskPair { #[pymethods] impl BboMsg { #[new] + #[pyo3(signature = ( + rtype, + publisher_id, + instrument_id, + ts_event, + price, + size, + side, + ts_recv, + sequence, + flags = None, + levels = None, + ))] fn py_new( rtype: u8, publisher_id: u16, @@ -346,6 +382,21 @@ impl BboMsg { #[pymethods] impl CbboMsg { #[new] + #[pyo3(signature= ( + rtype, + publisher_id, + instrument_id, + ts_event, + price, + size, + action, + side, + ts_recv, + ts_in_delta, + sequence, + flags = None, + levels = None, + ))] fn py_new( rtype: u8, publisher_id: u16, @@ -486,23 +537,31 @@ impl CbboMsg { #[pymethods] impl ConsolidatedBidAskPair { #[new] + #[pyo3(signature = ( + bid_px = UNDEF_PRICE, + ask_px = UNDEF_PRICE, + bid_sz = 0, + ask_sz = 0, + bid_pb = 0, + ask_pb = 0, + ))] fn py_new( - bid_px: Option, - ask_px: Option, - bid_sz: Option, - ask_sz: Option, - bid_pb: Option, - ask_pb: Option, + bid_px: i64, + ask_px: i64, + bid_sz: u32, + ask_sz: u32, + bid_pb: u16, + ask_pb: u16, ) -> Self { Self { - bid_px: bid_px.unwrap_or(UNDEF_PRICE), - ask_px: ask_px.unwrap_or(UNDEF_PRICE), - bid_sz: bid_sz.unwrap_or_default(), - ask_sz: ask_sz.unwrap_or_default(), - bid_pb: bid_pb.unwrap_or_default(), - ask_pb: ask_pb.unwrap_or_default(), - _reserved1: [0; 2], - _reserved2: [0; 2], + bid_px, + ask_px, + bid_sz, + ask_sz, + bid_pb, + ask_pb, + _reserved1: Default::default(), + _reserved2: Default::default(), } } @@ -556,6 +615,20 @@ impl ConsolidatedBidAskPair { #[pymethods] impl TradeMsg { #[new] + #[pyo3(signature= ( + publisher_id, + instrument_id, + ts_event, + price, + size, + action, + side, + depth, + ts_recv, + ts_in_delta, + sequence, + flags = None, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -694,6 +767,21 @@ impl TradeMsg { #[pymethods] impl Mbp1Msg { #[new] + #[pyo3(signature = ( + publisher_id, + instrument_id, + ts_event, + price, + size, + action, + side, + depth, + ts_recv, + ts_in_delta, + sequence, + flags = None, + levels = None, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -834,6 +922,21 @@ impl Mbp1Msg { #[pymethods] impl Mbp10Msg { #[new] + #[pyo3(signature = ( + publisher_id, + instrument_id, + ts_event, + price, + size, + action, + side, + depth, + ts_recv, + ts_in_delta, + sequence, + flags = None, + levels = None, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -1117,6 +1220,18 @@ impl OhlcvMsg { #[pymethods] impl StatusMsg { #[new] + #[pyo3(signature = ( + publisher_id, + instrument_id, + ts_event, + ts_recv, + action = None, + reason = None, + trading_event = None, + is_trading = None, + is_quoting = None, + is_short_sell_restricted = None, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -1252,6 +1367,71 @@ impl StatusMsg { #[pymethods] impl InstrumentDefMsg { #[new] + #[pyo3(signature = ( + publisher_id, + instrument_id, + ts_event, + ts_recv, + min_price_increment, + display_factor, + min_lot_size_round_lot, + raw_symbol, + group, + exchange, + instrument_class, + match_algorithm, + md_security_trading_status, + security_update_action, + expiration = UNDEF_TIMESTAMP, + activation = UNDEF_TIMESTAMP, + high_limit_price = UNDEF_PRICE, + low_limit_price = UNDEF_PRICE, + max_price_variation = UNDEF_PRICE, + trading_reference_price = UNDEF_PRICE, + unit_of_measure_qty = UNDEF_PRICE, + min_price_increment_amount = UNDEF_PRICE, + price_ratio = UNDEF_PRICE, + inst_attrib_value = None, + underlying_id = None, + raw_instrument_id = None, + market_depth_implied = None, + market_depth = None, + market_segment_id = None, + max_trade_vol = None, + min_lot_size = None, + min_lot_size_block = None, + min_trade_vol = None, + contract_multiplier = None, + decay_quantity = None, + original_contract_size = None, + trading_reference_date = None, + appl_id = None, + maturity_year = None, + decay_start_date = None, + channel_id = None, + currency = "", + settl_currency = "", + secsubtype = "", + asset = "", + cfi = "", + security_type = "", + unit_of_measure = "", + underlying = "", + strike_price_currency = "", + strike_price = UNDEF_PRICE, + main_fraction = None, + price_display_format = None, + settl_price_type = None, + sub_fraction = None, + underlying_product = None, + maturity_month = None, + maturity_day = None, + maturity_week = None, + user_defined_instrument = None, + contract_multiplier_unit = None, + flow_schedule_type = None, + tick_rule = None, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -1267,15 +1447,15 @@ impl InstrumentDefMsg { match_algorithm: c_char, md_security_trading_status: u8, security_update_action: SecurityUpdateAction, - expiration: Option, - activation: Option, - high_limit_price: Option, - low_limit_price: Option, - max_price_variation: Option, - trading_reference_price: Option, - unit_of_measure_qty: Option, - min_price_increment_amount: Option, - price_ratio: Option, + expiration: u64, + activation: u64, + high_limit_price: i64, + low_limit_price: i64, + max_price_variation: i64, + trading_reference_price: i64, + unit_of_measure_qty: i64, + min_price_increment_amount: i64, + price_ratio: i64, inst_attrib_value: Option, underlying_id: Option, raw_instrument_id: Option, @@ -1294,16 +1474,16 @@ impl InstrumentDefMsg { maturity_year: Option, decay_start_date: Option, channel_id: Option, - currency: Option<&str>, - settl_currency: Option<&str>, - secsubtype: Option<&str>, - asset: Option<&str>, - cfi: Option<&str>, - security_type: Option<&str>, - unit_of_measure: Option<&str>, - underlying: Option<&str>, - strike_price_currency: Option<&str>, - strike_price: Option, + currency: &str, + settl_currency: &str, + secsubtype: &str, + asset: &str, + cfi: &str, + security_type: &str, + unit_of_measure: &str, + underlying: &str, + strike_price_currency: &str, + strike_price: i64, main_fraction: Option, price_display_format: Option, settl_price_type: Option, @@ -1327,15 +1507,15 @@ impl InstrumentDefMsg { ts_recv, min_price_increment, display_factor, - expiration: expiration.unwrap_or(UNDEF_TIMESTAMP), - activation: activation.unwrap_or(UNDEF_TIMESTAMP), - high_limit_price: high_limit_price.unwrap_or(UNDEF_PRICE), - low_limit_price: low_limit_price.unwrap_or(UNDEF_PRICE), - max_price_variation: max_price_variation.unwrap_or(UNDEF_PRICE), - trading_reference_price: trading_reference_price.unwrap_or(UNDEF_PRICE), - unit_of_measure_qty: unit_of_measure_qty.unwrap_or(i64::MAX), - min_price_increment_amount: min_price_increment_amount.unwrap_or(UNDEF_PRICE), - price_ratio: price_ratio.unwrap_or(UNDEF_PRICE), + expiration, + activation, + high_limit_price, + low_limit_price, + max_price_variation, + trading_reference_price, + unit_of_measure_qty, + min_price_increment_amount, + price_ratio, inst_attrib_value: inst_attrib_value.unwrap_or(i32::MAX), underlying_id: underlying_id.unwrap_or_default(), raw_instrument_id: raw_instrument_id.unwrap_or(instrument_id), @@ -1355,20 +1535,20 @@ impl InstrumentDefMsg { maturity_year: maturity_year.unwrap_or(u16::MAX), decay_start_date: decay_start_date.unwrap_or(u16::MAX), channel_id: channel_id.unwrap_or(u16::MAX), - currency: str_to_c_chars(currency.unwrap_or_default())?, - settl_currency: str_to_c_chars(settl_currency.unwrap_or_default())?, - secsubtype: str_to_c_chars(secsubtype.unwrap_or_default())?, + currency: str_to_c_chars(currency)?, + settl_currency: str_to_c_chars(settl_currency)?, + secsubtype: str_to_c_chars(secsubtype)?, raw_symbol: str_to_c_chars(raw_symbol)?, group: str_to_c_chars(group)?, exchange: str_to_c_chars(exchange)?, - asset: str_to_c_chars(asset.unwrap_or_default())?, - cfi: str_to_c_chars(cfi.unwrap_or_default())?, - security_type: str_to_c_chars(security_type.unwrap_or_default())?, - unit_of_measure: str_to_c_chars(unit_of_measure.unwrap_or_default())?, - underlying: str_to_c_chars(underlying.unwrap_or_default())?, - strike_price_currency: str_to_c_chars(strike_price_currency.unwrap_or_default())?, + asset: str_to_c_chars(asset)?, + cfi: str_to_c_chars(cfi)?, + security_type: str_to_c_chars(security_type)?, + unit_of_measure: str_to_c_chars(unit_of_measure)?, + underlying: str_to_c_chars(underlying)?, + strike_price_currency: str_to_c_chars(strike_price_currency)?, instrument_class, - strike_price: strike_price.unwrap_or(UNDEF_PRICE), + strike_price, match_algorithm, md_security_trading_status, main_fraction: main_fraction.unwrap_or(u8::MAX), @@ -1656,6 +1836,71 @@ impl InstrumentDefMsg { #[pymethods] impl InstrumentDefMsgV1 { + #[pyo3(signature = ( + publisher_id, + instrument_id, + ts_event, + ts_recv, + min_price_increment, + display_factor, + min_lot_size_round_lot, + raw_symbol, + group, + exchange, + instrument_class, + match_algorithm, + md_security_trading_status, + security_update_action, + expiration = UNDEF_TIMESTAMP, + activation = UNDEF_TIMESTAMP, + high_limit_price = UNDEF_PRICE, + low_limit_price = UNDEF_PRICE, + max_price_variation = UNDEF_PRICE, + trading_reference_price = UNDEF_PRICE, + unit_of_measure_qty = UNDEF_PRICE, + min_price_increment_amount = UNDEF_PRICE, + price_ratio = UNDEF_PRICE, + inst_attrib_value = None, + underlying_id = None, + raw_instrument_id = None, + market_depth_implied = None, + market_depth = None, + market_segment_id = None, + max_trade_vol = None, + min_lot_size = None, + min_lot_size_block = None, + min_trade_vol = None, + contract_multiplier = None, + decay_quantity = None, + original_contract_size = None, + trading_reference_date = None, + appl_id = None, + maturity_year = None, + decay_start_date = None, + channel_id = None, + currency = "", + settl_currency = "", + secsubtype = "", + asset = "", + cfi = "", + security_type = "", + unit_of_measure = "", + underlying = "", + strike_price_currency = "", + strike_price = UNDEF_PRICE, + main_fraction = None, + price_display_format = None, + settl_price_type = None, + sub_fraction = None, + underlying_product = None, + maturity_month = None, + maturity_day = None, + maturity_week = None, + user_defined_instrument = None, + contract_multiplier_unit = None, + flow_schedule_type = None, + tick_rule = None, + ))] #[new] fn py_new( publisher_id: u16, @@ -1672,15 +1917,15 @@ impl InstrumentDefMsgV1 { match_algorithm: c_char, md_security_trading_status: u8, security_update_action: SecurityUpdateAction, - expiration: Option, - activation: Option, - high_limit_price: Option, - low_limit_price: Option, - max_price_variation: Option, - trading_reference_price: Option, - unit_of_measure_qty: Option, - min_price_increment_amount: Option, - price_ratio: Option, + expiration: u64, + activation: u64, + high_limit_price: i64, + low_limit_price: i64, + max_price_variation: i64, + trading_reference_price: i64, + unit_of_measure_qty: i64, + min_price_increment_amount: i64, + price_ratio: i64, inst_attrib_value: Option, underlying_id: Option, raw_instrument_id: Option, @@ -1699,16 +1944,16 @@ impl InstrumentDefMsgV1 { maturity_year: Option, decay_start_date: Option, channel_id: Option, - currency: Option<&str>, - settl_currency: Option<&str>, - secsubtype: Option<&str>, - asset: Option<&str>, - cfi: Option<&str>, - security_type: Option<&str>, - unit_of_measure: Option<&str>, - underlying: Option<&str>, - strike_price_currency: Option<&str>, - strike_price: Option, + currency: &str, + settl_currency: &str, + secsubtype: &str, + asset: &str, + cfi: &str, + security_type: &str, + unit_of_measure: &str, + underlying: &str, + strike_price_currency: &str, + strike_price: i64, main_fraction: Option, price_display_format: Option, settl_price_type: Option, @@ -1732,15 +1977,15 @@ impl InstrumentDefMsgV1 { ts_recv, min_price_increment, display_factor, - expiration: expiration.unwrap_or(UNDEF_TIMESTAMP), - activation: activation.unwrap_or(UNDEF_TIMESTAMP), - high_limit_price: high_limit_price.unwrap_or(UNDEF_PRICE), - low_limit_price: low_limit_price.unwrap_or(UNDEF_PRICE), - max_price_variation: max_price_variation.unwrap_or(UNDEF_PRICE), - trading_reference_price: trading_reference_price.unwrap_or(UNDEF_PRICE), - unit_of_measure_qty: unit_of_measure_qty.unwrap_or(i64::MAX), - min_price_increment_amount: min_price_increment_amount.unwrap_or(UNDEF_PRICE), - price_ratio: price_ratio.unwrap_or(UNDEF_PRICE), + expiration, + activation, + high_limit_price, + low_limit_price, + max_price_variation, + trading_reference_price, + unit_of_measure_qty, + min_price_increment_amount, + price_ratio, inst_attrib_value: inst_attrib_value.unwrap_or(i32::MAX), underlying_id: underlying_id.unwrap_or_default(), raw_instrument_id: raw_instrument_id.unwrap_or(instrument_id), @@ -1760,20 +2005,20 @@ impl InstrumentDefMsgV1 { maturity_year: maturity_year.unwrap_or(u16::MAX), decay_start_date: decay_start_date.unwrap_or(u16::MAX), channel_id: channel_id.unwrap_or(u16::MAX), - currency: str_to_c_chars(currency.unwrap_or_default())?, - settl_currency: str_to_c_chars(settl_currency.unwrap_or_default())?, - secsubtype: str_to_c_chars(secsubtype.unwrap_or_default())?, + currency: str_to_c_chars(currency)?, + settl_currency: str_to_c_chars(settl_currency)?, + secsubtype: str_to_c_chars(secsubtype)?, raw_symbol: str_to_c_chars(raw_symbol)?, group: str_to_c_chars(group)?, exchange: str_to_c_chars(exchange)?, - asset: str_to_c_chars(asset.unwrap_or_default())?, - cfi: str_to_c_chars(cfi.unwrap_or_default())?, - security_type: str_to_c_chars(security_type.unwrap_or_default())?, - unit_of_measure: str_to_c_chars(unit_of_measure.unwrap_or_default())?, - underlying: str_to_c_chars(underlying.unwrap_or_default())?, - strike_price_currency: str_to_c_chars(strike_price_currency.unwrap_or_default())?, + asset: str_to_c_chars(asset)?, + cfi: str_to_c_chars(cfi)?, + security_type: str_to_c_chars(security_type)?, + unit_of_measure: str_to_c_chars(unit_of_measure)?, + underlying: str_to_c_chars(underlying)?, + strike_price_currency: str_to_c_chars(strike_price_currency)?, instrument_class, - strike_price: strike_price.unwrap_or(UNDEF_PRICE), + strike_price, match_algorithm, md_security_trading_status, main_fraction: main_fraction.unwrap_or(u8::MAX), @@ -2240,6 +2485,21 @@ impl ImbalanceMsg { #[pymethods] impl StatMsg { #[new] + #[pyo3(signature= ( + publisher_id, + instrument_id, + ts_event, + ts_recv, + ts_ref, + price, + quantity, + sequence, + ts_in_delta, + stat_type, + channel_id, + update_action = None, + stat_flags = 0, + ))] fn py_new( publisher_id: u16, instrument_id: u32, @@ -2253,7 +2513,7 @@ impl StatMsg { stat_type: u16, channel_id: u16, update_action: Option, - stat_flags: Option, + stat_flags: u8, ) -> Self { Self { hd: RecordHeader::new::(rtype::STATISTICS, publisher_id, instrument_id, ts_event), @@ -2266,7 +2526,7 @@ impl StatMsg { stat_type, channel_id, update_action: update_action.unwrap_or(StatUpdateAction::New as u8), - stat_flags: stat_flags.unwrap_or_default(), + stat_flags, _reserved: Default::default(), } } @@ -2374,8 +2634,9 @@ impl StatMsg { #[pymethods] impl ErrorMsg { #[new] - fn py_new(ts_event: u64, err: &str, is_last: Option) -> PyResult { - Ok(ErrorMsg::new(ts_event, err, is_last.unwrap_or(true))) + #[pyo3(signature = (ts_event, err, is_last = true))] + fn py_new(ts_event: u64, err: &str, is_last: bool) -> PyResult { + Ok(ErrorMsg::new(ts_event, err, is_last)) } fn __bytes__(&self) -> &[u8] { From 8d5a851c37515389a39e835ea06ec391f39de2ed Mon Sep 17 00:00:00 2001 From: Carter Green Date: Tue, 9 Jul 2024 08:55:47 -0500 Subject: [PATCH 4/4] VER: Release 0.19.0 --- CHANGELOG.md | 2 +- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- python/pyproject.toml | 4 ++-- rust/dbn-cli/Cargo.toml | 2 +- rust/dbn/Cargo.toml | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bef4b..30b551e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.19.0 - TBD +## 0.19.0 - 2024-07-09 ### Enhancements - Added `BboMsg` record struct for future `bbo-1m` and `bbo-1s` schemas diff --git a/Cargo.lock b/Cargo.lock index d6149d9..aaa44af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,7 @@ dependencies = [ [[package]] name = "databento-dbn" -version = "0.18.3" +version = "0.19.0" dependencies = [ "dbn", "pyo3", @@ -267,7 +267,7 @@ dependencies = [ [[package]] name = "dbn" -version = "0.18.3" +version = "0.19.0" dependencies = [ "async-compression", "csv", @@ -289,7 +289,7 @@ dependencies = [ [[package]] name = "dbn-c" -version = "0.18.3" +version = "0.19.0" dependencies = [ "anyhow", "cbindgen", @@ -299,7 +299,7 @@ dependencies = [ [[package]] name = "dbn-cli" -version = "0.18.3" +version = "0.19.0" dependencies = [ "anyhow", "assert_cmd", @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "dbn-macros" -version = "0.18.3" +version = "0.19.0" dependencies = [ "csv", "dbn", diff --git a/Cargo.toml b/Cargo.toml index bfb507c..cd88b8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ resolver = "2" [workspace.package] authors = ["Databento "] edition = "2021" -version = "0.18.3" +version = "0.19.0" documentation = "https://docs.databento.com" repository = "https://github.com/databento/dbn" license = "Apache-2.0" diff --git a/python/pyproject.toml b/python/pyproject.toml index 6d01208..0bd561f 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "databento-dbn" -version = "0.18.3" +version = "0.19.0" description = "Python bindings for encoding and decoding Databento Binary Encoding (DBN)" authors = ["Databento "] license = "Apache-2.0" @@ -17,7 +17,7 @@ build-backend = "maturin" [project] name = "databento-dbn" -version = "0.18.3" +version = "0.19.0" authors = [ { name = "Databento", email = "support@databento.com" } ] diff --git a/rust/dbn-cli/Cargo.toml b/rust/dbn-cli/Cargo.toml index 14d0f14..93bc85e 100644 --- a/rust/dbn-cli/Cargo.toml +++ b/rust/dbn-cli/Cargo.toml @@ -16,7 +16,7 @@ name = "dbn" path = "src/main.rs" [dependencies] -dbn = { path = "../dbn", version = "=0.18.3", default-features = false } +dbn = { path = "../dbn", version = "=0.19.0", default-features = false } anyhow = { workspace = true } clap = { version = "4.5", features = ["derive", "wrap_help"] } diff --git a/rust/dbn/Cargo.toml b/rust/dbn/Cargo.toml index 3a670b2..7fb07a3 100644 --- a/rust/dbn/Cargo.toml +++ b/rust/dbn/Cargo.toml @@ -25,7 +25,7 @@ serde = ["dep:serde", "time/parsing", "time/serde"] trivial_copy = [] [dependencies] -dbn-macros = { version = "=0.18.3", path = "../dbn-macros" } +dbn-macros = { version = "=0.19.0", path = "../dbn-macros" } async-compression = { version = "0.4.11", features = ["tokio", "zstd"], optional = true } csv = { workspace = true }