diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e775e6..5ac6f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.4.2 - 2023-04-06 +- Fixed support for `ErrorMsg`, `SystemMsg`, and `SymbolMappingMsg` in Python + ## 0.4.1 - 2023-04-05 - Added enums `MatchAlgorithm`, `UserDefinedInstrument` - Added constants `UNDEF_PRICE` and `UNDEF_ORDER_SIZE` diff --git a/Cargo.lock b/Cargo.lock index d2488c3..1a21655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,7 +188,7 @@ dependencies = [ [[package]] name = "databento-dbn" -version = "0.4.1" +version = "0.4.2" dependencies = [ "dbn", "pyo3", @@ -197,7 +197,7 @@ dependencies = [ [[package]] name = "dbn" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-compression", @@ -215,7 +215,7 @@ dependencies = [ [[package]] name = "dbn-c" -version = "0.4.1" +version = "0.4.2" dependencies = [ "cbindgen", "dbn", @@ -224,7 +224,7 @@ dependencies = [ [[package]] name = "dbn-cli" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "assert_cmd", @@ -237,7 +237,7 @@ dependencies = [ [[package]] name = "dbn-macros" -version = "0.4.1" +version = "0.4.2" [[package]] name = "difflib" diff --git a/c/Cargo.toml b/c/Cargo.toml index bb7f047..56c8f69 100644 --- a/c/Cargo.toml +++ b/c/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dbn-c" authors = ["Databento "] -version = "0.4.1" +version = "0.4.2" edition = "2021" description = "C bindings for working with Databento Binary Encoding (DBN)" license = "Apache-2.0" diff --git a/python/Cargo.toml b/python/Cargo.toml index 1c25b42..456aa17 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "databento-dbn" authors = ["Databento "] -version = "0.4.1" +version = "0.4.2" edition = "2021" description = "Python library written in Rust for working with Databento Binary Encoding (DBN)" license = "Apache-2.0" diff --git a/python/databento_dbn.pyi b/python/databento_dbn.pyi index 9720ba4..bd121fc 100644 --- a/python/databento_dbn.pyi +++ b/python/databento_dbn.pyi @@ -1,19 +1,5 @@ """Type stubs for databento_dbn""" -from typing import Any, BinaryIO, Dict, Optional, Sequence, Union - -DBNRecord = Union[ - "Metadata", - "MBOMsg", - "TradeMsg", - "MBP1Msg", - "MBP10Msg", - "OhlcvMsg", - "InstrumentDefMsg", - "ImbalanceMsg", - "ErrorMsg", - "SymbolMappingMsg", - "SystemMsg", -] +from typing import Any, BinaryIO, Dict, Optional, Sequence, SupportsBytes class Metadata: """ @@ -215,7 +201,7 @@ class RecordHeader: """ -class _Record: +class Record(SupportsBytes): """ Base class for DBN records. """ @@ -347,7 +333,7 @@ class _MBOBase: """ -class MBOMsg(_Record, _MBOBase): +class MBOMsg(Record, _MBOBase): """ A market-by-order (MBO) tick message. """ @@ -515,13 +501,13 @@ class _MBPBase: """ -class TradeMsg(_Record, _MBPBase): +class TradeMsg(Record, _MBPBase): """ Market by price implementation with a book depth of 0. Equivalent to MBP-0. The record of the `Trades` schema. """ -class MBP1Msg(_Record, _MBPBase): +class MBP1Msg(Record, _MBPBase): """ Market by price implementation with a known book depth of 1. """ @@ -541,7 +527,7 @@ class MBP1Msg(_Record, _MBPBase): """ -class MBP10Msg(_Record, _MBPBase): +class MBP10Msg(Record, _MBPBase): """ Market by price implementation with a known book depth of 10. """ @@ -561,7 +547,7 @@ class MBP10Msg(_Record, _MBPBase): """ -class OhlcvMsg(_Record): +class OHLCVMsg(Record): """ Open, high, low, close, and volume message. """ @@ -617,7 +603,7 @@ class OhlcvMsg(_Record): """ -class InstrumentDefMsg(_Record): +class InstrumentDefMsg(Record): """ Definition of an instrument. """ @@ -1246,7 +1232,7 @@ class InstrumentDefMsg(_Record): """ -class ImbalanceMsg(_Record): +class ImbalanceMsg(Record): """ An auction imbalance message. """ @@ -1454,7 +1440,7 @@ class ImbalanceMsg(_Record): """ -class ErrorMsg(_Record): +class ErrorMsg(Record): """ An error message from the Databento Live Subscription Gateway (LSG). """ @@ -1470,7 +1456,7 @@ class ErrorMsg(_Record): """ -class SymbolMappingMsg(_Record): +class SymbolMappingMsg(Record): """ A symbol mapping message which maps a symbol of one `SType` to another. """ @@ -1518,7 +1504,7 @@ class SymbolMappingMsg(_Record): """ -class SystemMsg(_Record): +class SystemMsg(Record): """ A non-error message from the Databento Live Subscription Gateway (LSG). Also used for heartbeating. @@ -1552,13 +1538,13 @@ class DbnDecoder: """ def decode( self, - ) -> Sequence[DBNRecord]: + ) -> Sequence[Record]: """ Decode the buffered data into DBN records. Returns ------- - Sequence[DBNRecord] + Sequence[Record] Raises ------ @@ -1693,7 +1679,7 @@ def write_dbn_file( start: int, stype_in: int, stype_out: int, - records: Sequence[DBNRecord], + records: Sequence[Record], end: Optional[int] = None, ) -> None: """ diff --git a/python/src/lib.rs b/python/src/lib.rs index 4ba306c..7578365 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -9,7 +9,7 @@ use dbn::{ python::to_val_err, record::{ BidAskPair, ErrorMsg, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg, OhlcvMsg, - RecordHeader, SymbolMappingMsg, SystemMsg, TradeMsg, + RecordHeader, StatusMsg, SymbolMappingMsg, SystemMsg, TradeMsg, }, }; @@ -36,6 +36,7 @@ fn databento_dbn(_py: Python<'_>, m: &PyModule) -> PyResult<()> { checked_add_class::(m)?; checked_add_class::(m)?; checked_add_class::(m)?; + checked_add_class::(m)?; checked_add_class::(m)?; checked_add_class::(m)?; checked_add_class::(m)?; @@ -102,6 +103,7 @@ impl DbnDecoder { | rtype::OHLCV_1D => { recs.push(rec.get::().unwrap().clone().into_py(py)) } + rtype::STATUS => recs.push(rec.get::().unwrap().clone().into_py(py)), rtype::IMBALANCE => { recs.push(rec.get::().unwrap().clone().into_py(py)) } @@ -112,6 +114,7 @@ impl DbnDecoder { rtype::SYMBOL_MAPPING => { recs.push(rec.get::().unwrap().clone().into_py(py)) } + rtype::SYSTEM => recs.push(rec.get::().unwrap().clone().into_py(py)), rtype::MBO => recs.push(rec.get::().unwrap().clone().into_py(py)), rtype => { return Err(to_val_err(format!("Invalid rtype {rtype} found in record"))) diff --git a/rust/dbn-cli/Cargo.toml b/rust/dbn-cli/Cargo.toml index 01a32cf..50e14a5 100644 --- a/rust/dbn-cli/Cargo.toml +++ b/rust/dbn-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dbn-cli" authors = ["Databento "] -version = "0.4.1" +version = "0.4.2" edition = "2021" description = "Command-line utility for converting Databento Binary Encoding (DBN) files to text-based formats" default-run = "dbn" @@ -17,7 +17,7 @@ path = "src/main.rs" [dependencies] # Databento common DBN library -dbn = { path = "../dbn", version = "=0.4.1" } +dbn = { path = "../dbn", version = "=0.4.2" } # Error handling anyhow = "1.0.68" diff --git a/rust/dbn-macros/Cargo.toml b/rust/dbn-macros/Cargo.toml index 98ef828..abe242e 100644 --- a/rust/dbn-macros/Cargo.toml +++ b/rust/dbn-macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dbn-macros" authors = ["Databento "] -version = "0.4.1" +version = "0.4.2" edition = "2021" description = "Proc macros for dbn crate" license = "Apache-2.0" diff --git a/rust/dbn/Cargo.toml b/rust/dbn/Cargo.toml index 541de7f..2ef1272 100644 --- a/rust/dbn/Cargo.toml +++ b/rust/dbn/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dbn" authors = ["Databento "] -version = "0.4.1" +version = "0.4.2" edition = "2021" description = "Library for working with Databento Binary Encoding (DBN)" license = "Apache-2.0" @@ -18,7 +18,7 @@ python = ["pyo3"] trivial_copy = [] [dependencies] -dbn-macros = { version = "=0.4.1", path = "../dbn-macros" } +dbn-macros = { version = "=0.4.2", path = "../dbn-macros" } # error handling anyhow = "1.0" diff --git a/rust/dbn/src/encode/mod.rs b/rust/dbn/src/encode/mod.rs index 72c5c7e..0625984 100644 --- a/rust/dbn/src/encode/mod.rs +++ b/rust/dbn/src/encode/mod.rs @@ -14,8 +14,8 @@ use crate::{ decode::DecodeDbn, enums::{Compression, Encoding, Schema}, record::{ - HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg, OhlcvMsg, TbboMsg, - TradeMsg, WithTsOut, + HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, Mbp1Msg, OhlcvMsg, StatusMsg, + TbboMsg, TradeMsg, WithTsOut, }, Metadata, }; @@ -97,7 +97,9 @@ pub trait EncodeDbn { (Schema::Imbalance, false) => { self.encode_stream(decoder.decode_stream::()?) } - (Schema::Statistics | Schema::Status, _) => Err(anyhow!("Not implemented")), + (Schema::Status, true) => self.encode_stream(decoder.decode_stream::()?), + (Schema::Status, false) => self.encode_stream(decoder.decode_stream::()?), + (Schema::Statistics, _) => Err(anyhow!("Not implemented")), } } } diff --git a/rust/dbn/src/python.rs b/rust/dbn/src/python.rs index c8de89a..6a59759 100644 --- a/rust/dbn/src/python.rs +++ b/rust/dbn/src/python.rs @@ -21,8 +21,9 @@ use crate::{ enums::{rtype, Compression, SType, Schema, SecurityUpdateAction, UserDefinedInstrument}, metadata::MetadataBuilder, record::{ - str_to_c_chars, BidAskPair, HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, Mbp10Msg, - Mbp1Msg, OhlcvMsg, RecordHeader, TbboMsg, TradeMsg, + str_to_c_chars, BidAskPair, ErrorMsg, HasRType, ImbalanceMsg, InstrumentDefMsg, MboMsg, + Mbp10Msg, Mbp1Msg, OhlcvMsg, RecordHeader, StatusMsg, SymbolMappingMsg, SystemMsg, TbboMsg, + TradeMsg, }, UNDEF_ORDER_SIZE, UNDEF_PRICE, }; @@ -690,6 +691,43 @@ impl OhlcvMsg { } } +#[pymethods] +impl StatusMsg { + #[new] + fn py_new( + publisher_id: u16, + product_id: u32, + ts_event: u64, + ts_recv: u64, + group: &str, + trading_status: u8, + halt_reason: u8, + trading_event: u8, + ) -> PyResult { + Ok(Self { + hd: RecordHeader::new::(rtype::STATUS, publisher_id, product_id, ts_event), + ts_recv, + group: str_to_c_chars(group).map_err(to_val_err)?, + trading_status, + halt_reason, + trading_event, + }) + } + + fn __repr__(&self) -> String { + format!("{self:?}") + } + + fn __bytes__(&self) -> &[u8] { + self.as_ref() + } + + #[pyo3(name = "record_size")] + fn py_record_size(&self) -> usize { + self.record_size() + } +} + #[pymethods] impl InstrumentDefMsg { #[new] @@ -908,6 +946,89 @@ impl ImbalanceMsg { } } +#[pymethods] +impl ErrorMsg { + #[new] + fn py_new(ts_event: u64, err: &str) -> PyResult { + Ok(ErrorMsg::new(ts_event, err)) + } + + fn __repr__(&self) -> String { + format!("{self:?}") + } + + fn __bytes__(&self) -> &[u8] { + self.as_ref() + } + + #[pyo3(name = "record_size")] + fn py_record_size(&self) -> usize { + self.record_size() + } +} + +#[pymethods] +impl SymbolMappingMsg { + #[new] + fn py_new( + publisher_id: u16, + product_id: u32, + ts_event: u64, + stype_in_symbol: &str, + stype_out_symbol: &str, + start_ts: u64, + end_ts: u64, + ) -> PyResult { + Ok(Self { + hd: RecordHeader::new::( + rtype::SYMBOL_MAPPING, + publisher_id, + product_id, + ts_event, + ), + stype_in_symbol: str_to_c_chars(stype_in_symbol).map_err(to_val_err)?, + stype_out_symbol: str_to_c_chars(stype_out_symbol).map_err(to_val_err)?, + start_ts, + _dummy: [0; 4], + end_ts, + }) + } + + fn __repr__(&self) -> String { + format!("{self:?}") + } + + fn __bytes__(&self) -> &[u8] { + self.as_ref() + } + + #[pyo3(name = "record_size")] + fn py_record_size(&self) -> usize { + self.record_size() + } +} + +#[pymethods] +impl SystemMsg { + #[new] + fn py_new(ts_event: u64, msg: &str) -> PyResult { + SystemMsg::new(ts_event, msg).map_err(to_val_err) + } + + fn __repr__(&self) -> String { + format!("{self:?}") + } + + fn __bytes__(&self) -> &[u8] { + self.as_ref() + } + + #[pyo3(name = "record_size")] + fn py_record_size(&self) -> usize { + self.record_size() + } +} + #[cfg(test)] mod tests { use std::io::{Cursor, Seek, Write}; diff --git a/rust/dbn/src/record.rs b/rust/dbn/src/record.rs index 808b546..34a1301 100644 --- a/rust/dbn/src/record.rs +++ b/rust/dbn/src/record.rs @@ -263,6 +263,10 @@ pub struct OhlcvMsg { #[repr(C)] #[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[cfg_attr(feature = "trivial_copy", derive(Copy))] +#[cfg_attr( + feature = "python", + pyo3::pyclass(get_all, set_all, module = "databento_dbn") +)] pub struct StatusMsg { /// The common header. pub hd: RecordHeader,