dbn/
v2.rs

1//! Record data types for encoding different Databento [`Schema`](crate::enums::Schema)s
2//! in DBN version 2.
3
4pub use crate::compat::{
5    InstrumentDefMsgV2 as InstrumentDefMsg, StatMsgV1 as StatMsg,
6    ASSET_CSTR_LEN_V2 as ASSET_CSTR_LEN, SYMBOL_CSTR_LEN_V2 as SYMBOL_CSTR_LEN,
7    UNDEF_STAT_QUANTITY_V2 as UNDEF_STAT_QUANTITY,
8};
9pub use crate::record::{
10    Bbo1MMsg, Bbo1SMsg, BboMsg, Cbbo1MMsg, Cbbo1SMsg, CbboMsg, Cmbp1Msg, ErrorMsg, ImbalanceMsg,
11    MboMsg, Mbp10Msg, Mbp1Msg, OhlcvMsg, StatusMsg, SymbolMappingMsg, SystemMsg, TbboMsg, TcbboMsg,
12    TradeMsg, WithTsOut,
13};
14
15mod impl_default;
16mod methods;
17
18use std::os::raw::c_char;
19
20use crate::{
21    compat::{InstrumentDefRec, SymbolMappingRec},
22    rtype, v1, RecordHeader, SystemCode,
23};
24
25/// The DBN version of this module.
26pub const DBN_VERSION: u8 = 2;
27
28impl From<&v1::InstrumentDefMsg> for InstrumentDefMsg {
29    fn from(old: &v1::InstrumentDefMsg) -> Self {
30        let mut res = Self {
31            // recalculate length
32            hd: RecordHeader::new::<Self>(
33                rtype::INSTRUMENT_DEF,
34                old.hd.publisher_id,
35                old.hd.instrument_id,
36                old.hd.ts_event,
37            ),
38            ts_recv: old.ts_recv,
39            min_price_increment: old.min_price_increment,
40            display_factor: old.display_factor,
41            expiration: old.expiration,
42            activation: old.activation,
43            high_limit_price: old.high_limit_price,
44            low_limit_price: old.low_limit_price,
45            max_price_variation: old.max_price_variation,
46            trading_reference_price: old.trading_reference_price,
47            unit_of_measure_qty: old.unit_of_measure_qty,
48            min_price_increment_amount: old.min_price_increment_amount,
49            price_ratio: old.price_ratio,
50            inst_attrib_value: old.inst_attrib_value,
51            underlying_id: old.underlying_id,
52            raw_instrument_id: old.raw_instrument_id,
53            market_depth_implied: old.market_depth_implied,
54            market_depth: old.market_depth,
55            market_segment_id: old.market_segment_id,
56            max_trade_vol: old.max_trade_vol,
57            min_lot_size: old.min_lot_size,
58            min_lot_size_block: old.min_lot_size_block,
59            min_lot_size_round_lot: old.min_lot_size_round_lot,
60            min_trade_vol: old.min_trade_vol,
61            contract_multiplier: old.contract_multiplier,
62            decay_quantity: old.decay_quantity,
63            original_contract_size: old.original_contract_size,
64            trading_reference_date: old.trading_reference_date,
65            appl_id: old.appl_id,
66            maturity_year: old.maturity_year,
67            decay_start_date: old.decay_start_date,
68            channel_id: old.channel_id,
69            currency: old.currency,
70            settl_currency: old.settl_currency,
71            secsubtype: old.secsubtype,
72            group: old.group,
73            exchange: old.exchange,
74            asset: old.asset,
75            cfi: old.cfi,
76            security_type: old.security_type,
77            unit_of_measure: old.unit_of_measure,
78            underlying: old.underlying,
79            strike_price_currency: old.strike_price_currency,
80            instrument_class: old.instrument_class,
81            strike_price: old.strike_price,
82            match_algorithm: old.match_algorithm,
83            md_security_trading_status: old.md_security_trading_status,
84            main_fraction: old.main_fraction,
85            price_display_format: old.price_display_format,
86            settl_price_type: old.settl_price_type,
87            sub_fraction: old.sub_fraction,
88            underlying_product: old.underlying_product,
89            security_update_action: old.security_update_action as c_char,
90            maturity_month: old.maturity_month,
91            maturity_day: old.maturity_day,
92            maturity_week: old.maturity_week,
93            user_defined_instrument: old.user_defined_instrument,
94            contract_multiplier_unit: old.contract_multiplier_unit,
95            flow_schedule_type: old.flow_schedule_type,
96            tick_rule: old.tick_rule,
97            ..Default::default()
98        };
99        res.raw_symbol[..v1::SYMBOL_CSTR_LEN].copy_from_slice(old.raw_symbol.as_slice());
100        res
101    }
102}
103
104impl From<&v1::ErrorMsg> for ErrorMsg {
105    fn from(old: &v1::ErrorMsg) -> Self {
106        let mut new = Self {
107            hd: RecordHeader::new::<Self>(
108                rtype::ERROR,
109                old.hd.publisher_id,
110                old.hd.instrument_id,
111                old.hd.ts_event,
112            ),
113            ..Default::default()
114        };
115        new.err[..old.err.len()].copy_from_slice(old.err.as_slice());
116        new
117    }
118}
119
120impl From<&v1::SymbolMappingMsg> for SymbolMappingMsg {
121    fn from(old: &v1::SymbolMappingMsg) -> Self {
122        let mut res = Self {
123            hd: RecordHeader::new::<Self>(
124                rtype::SYMBOL_MAPPING,
125                old.hd.publisher_id,
126                old.hd.instrument_id,
127                old.hd.ts_event,
128            ),
129            start_ts: old.start_ts,
130            end_ts: old.end_ts,
131            ..Default::default()
132        };
133        res.stype_in_symbol[..v1::SYMBOL_CSTR_LEN].copy_from_slice(old.stype_in_symbol.as_slice());
134        res.stype_out_symbol[..v1::SYMBOL_CSTR_LEN]
135            .copy_from_slice(old.stype_out_symbol.as_slice());
136        res
137    }
138}
139
140impl From<&v1::SystemMsg> for SystemMsg {
141    fn from(old: &v1::SystemMsg) -> Self {
142        let mut new = Self {
143            hd: RecordHeader::new::<Self>(
144                rtype::SYSTEM,
145                old.hd.publisher_id,
146                old.hd.instrument_id,
147                old.hd.ts_event,
148            ),
149            ..Default::default()
150        };
151        if old.is_heartbeat() {
152            new.code = SystemCode::Heartbeat as u8;
153        } else if let Ok(msg) = old.msg() {
154            if msg.starts_with("End of interval for ") {
155                new.code = SystemCode::EndOfInterval as u8;
156            } else if msg.starts_with("Subscription request ") && msg.ends_with(" succeeded") {
157                new.code = SystemCode::SubscriptionAck as u8;
158            } else if msg.starts_with("Warning: slow reading") {
159                new.code = SystemCode::SlowReaderWarning as u8;
160            } else if msg.starts_with("Finished ") && msg.ends_with(" replay") {
161                new.code = SystemCode::ReplayCompleted as u8;
162            }
163        }
164        new.msg[..old.msg.len()].copy_from_slice(old.msg.as_slice());
165        new
166    }
167}
168
169impl SymbolMappingRec for SymbolMappingMsg {
170    fn stype_in_symbol(&self) -> crate::Result<&str> {
171        Self::stype_in_symbol(self)
172    }
173
174    fn stype_out_symbol(&self) -> crate::Result<&str> {
175        Self::stype_out_symbol(self)
176    }
177
178    fn start_ts(&self) -> Option<time::OffsetDateTime> {
179        Self::start_ts(self)
180    }
181
182    fn end_ts(&self) -> Option<time::OffsetDateTime> {
183        Self::end_ts(self)
184    }
185}
186
187impl InstrumentDefRec for InstrumentDefMsg {
188    fn raw_symbol(&self) -> crate::Result<&str> {
189        Self::raw_symbol(self)
190    }
191
192    fn asset(&self) -> crate::Result<&str> {
193        Self::asset(self)
194    }
195
196    fn security_type(&self) -> crate::Result<&str> {
197        Self::security_type(self)
198    }
199
200    fn security_update_action(&self) -> crate::Result<crate::SecurityUpdateAction> {
201        Self::security_update_action(self)
202    }
203
204    fn channel_id(&self) -> u16 {
205        self.channel_id
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use std::mem;
212
213    use rstest::*;
214    use type_layout::{Field, TypeLayout};
215
216    use crate::v3;
217
218    use super::*;
219
220    #[test]
221    fn test_default_equivalency() {
222        assert_eq!(
223            v3::InstrumentDefMsg::from(&InstrumentDefMsg::default()),
224            v3::InstrumentDefMsg::default()
225        );
226    }
227
228    #[cfg(feature = "python")]
229    #[test]
230    fn test_strike_price_order_didnt_change() {
231        use crate::python::PyFieldDesc;
232
233        let v2_fields: Vec<_> = InstrumentDefMsg::ordered_fields("")
234            .into_iter()
235            .filter(|f| {
236                !matches!(
237                    f.as_ref(),
238                    "md_security_trading_status"
239                        | "trading_reference_date"
240                        | "trading_reference_price"
241                        | "settl_price_type"
242                )
243            })
244            .collect();
245        let v3_fields: Vec<_> = v3::InstrumentDefMsg::ordered_fields("")
246            .into_iter()
247            .take_while(|f| !f.starts_with("leg_"))
248            .collect();
249        assert_eq!(v2_fields, v3_fields);
250    }
251
252    #[rstest]
253    #[case::definition(InstrumentDefMsg::default(), 400)]
254    fn test_sizes<R: Sized>(#[case] _rec: R, #[case] exp: usize) {
255        assert_eq!(mem::size_of::<R>(), exp);
256        assert!(mem::size_of::<R>() <= crate::MAX_RECORD_LEN);
257    }
258
259    #[rstest]
260    #[case::definition(InstrumentDefMsg::default())]
261    fn test_alignment_and_no_padding<R: TypeLayout>(#[case] _rec: R) {
262        let layout = R::type_layout();
263        assert_eq!(layout.alignment, 8, "Unexpected alignment: {layout}");
264        for field in layout.fields.iter() {
265            assert!(
266                matches!(field, Field::Field { .. }),
267                "Detected padding: {layout}"
268            );
269        }
270    }
271}