Skip to content

Commit

Permalink
ADD: Add pretty module to DBN
Browse files Browse the repository at this point in the history
  • Loading branch information
threecgreen committed Jul 18, 2023
1 parent 37b34da commit 022c6cf
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 78 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
and methods. This should make it easier to disambiguate between error types.
- `EncodeDbn::encode_record` and `EncodeDbn::record_record_ref` no longer treat a
`BrokenPipe` error differently
- Added `pretty::Px` and `pretty::Ts` newtypes to expose price and timestamp formatting
logic outside of CSV and JSON encoding
- Added interning for Python strings
- Added `pretty_` Python attributes for DBN price fields
- Added `pretty_` Python attributes for DBN UTC timestamp fields
Expand Down
6 changes: 3 additions & 3 deletions rust/dbn/src/encode/csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ pub(crate) mod serialize {
use csv::Writer;

use crate::{
encode::{format_px, format_ts},
enums::{SecurityUpdateAction, UserDefinedInstrument},
pretty::{fmt_px, fmt_ts},
record::{c_chars_to_str, BidAskPair, HasRType, RecordHeader, WithTsOut},
UNDEF_PRICE, UNDEF_TIMESTAMP,
};
Expand Down Expand Up @@ -311,7 +311,7 @@ pub(crate) mod serialize {
if px == UNDEF_PRICE {
csv_writer.write_field("")
} else {
csv_writer.write_field(format_px(px))
csv_writer.write_field(fmt_px(px))
}
} else {
csv_writer.write_field(px.to_string())
Expand All @@ -325,7 +325,7 @@ pub(crate) mod serialize {
if PRETTY_TS {
match ts {
0 | UNDEF_TIMESTAMP => csv_writer.write_field(""),
ts => csv_writer.write_field(format_ts(ts)),
ts => csv_writer.write_field(fmt_ts(ts)),
}
} else {
csv_writer.write_field(ts.to_string())
Expand Down
6 changes: 3 additions & 3 deletions rust/dbn/src/encode/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ pub(crate) mod serialize {
use time::format_description::FormatItem;

use crate::json_writer::{JsonObjectWriter, NULL};
use crate::pretty::{fmt_px, fmt_ts};
use crate::UNDEF_TIMESTAMP;
use crate::{
encode::{format_px, format_ts},
enums::{SecurityUpdateAction, UserDefinedInstrument},
record::{c_chars_to_str, BidAskPair, HasRType, RecordHeader, WithTsOut},
Metadata, UNDEF_PRICE,
Expand Down Expand Up @@ -384,7 +384,7 @@ pub(crate) mod serialize {
if px == UNDEF_PRICE {
writer.value(key, NULL);
} else {
writer.value(key, &format_px(px));
writer.value(key, &fmt_px(px));
}
} else {
// Convert to string to avoid a loss of precision
Expand All @@ -400,7 +400,7 @@ pub(crate) mod serialize {
if PRETTY_TS {
match ts {
0 | UNDEF_TIMESTAMP => writer.value(key, NULL),
ts => writer.value(key, &format_ts(ts)),
ts => writer.value(key, &fmt_ts(ts)),
};
} else {
// Convert to string to avoid a loss of precision
Expand Down
73 changes: 1 addition & 72 deletions rust/dbn/src/encode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ pub mod json;
use std::{fmt, io, num::NonZeroU64};

use streaming_iterator::StreamingIterator;
use time::format_description::FormatItem;

// Re-exports
#[cfg(feature = "async")]
Expand All @@ -31,7 +30,7 @@ use crate::{
enums::{Compression, Encoding},
record::HasRType,
record_ref::RecordRef,
rtype_ts_out_dispatch, Metadata, Result, FIXED_PRICE_SCALE,
rtype_ts_out_dispatch, Metadata, Result,
};

use self::{csv::serialize::CsvSerialize, json::serialize::JsonSerialize};
Expand Down Expand Up @@ -432,31 +431,6 @@ mod test_data {
}
}

fn format_px(px: i64) -> String {
if px == crate::UNDEF_PRICE {
"UNDEF_PRICE".to_owned()
} else {
let (sign, px_abs) = if px < 0 { ("-", -px) } else { ("", px) };
let px_integer = px_abs / FIXED_PRICE_SCALE;
let px_fraction = px_abs % FIXED_PRICE_SCALE;
format!("{sign}{px_integer}.{px_fraction:09}")
}
}

fn format_ts(ts: u64) -> String {
const TS_FORMAT: &[FormatItem<'static>] = time::macros::format_description!(
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z"
);
if ts == 0 {
String::new()
} else {
time::OffsetDateTime::from_unix_timestamp_nanos(ts as i128)
.map_err(|_| ())
.and_then(|dt| dt.format(TS_FORMAT).map_err(|_| ()))
.unwrap_or_else(|_| ts.to_string())
}
}

#[cfg(feature = "async")]
pub use r#async::DynWriter as DynAsyncWriter;

Expand Down Expand Up @@ -537,48 +511,3 @@ mod r#async {
}
}
}

#[cfg(test)]
mod tests {
use crate::UNDEF_PRICE;

use super::*;

#[test]
fn test_format_px_negative() {
assert_eq!(format_px(-100_000), "-0.000100000");
}

#[test]
fn test_format_px_positive() {
assert_eq!(format_px(32_500_000_000), "32.500000000");
}

#[test]
fn test_format_px_zero() {
assert_eq!(format_px(0), "0.000000000");
}

#[test]
fn test_format_px_undef() {
assert_eq!(format_px(UNDEF_PRICE), "UNDEF_PRICE");
}

#[test]
fn format_ts_0() {
assert!(format_ts(0).is_empty());
}

#[test]
fn format_ts_1() {
assert_eq!(format_ts(1), "1970-01-01T00:00:00.000000001Z");
}

#[test]
fn format_ts_future() {
assert_eq!(
format_ts(1622838300000000000),
"2021-06-04T20:25:00.000000000Z"
);
}
}
1 change: 1 addition & 0 deletions rust/dbn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod error;
pub mod json_writer;
pub mod macros;
pub mod metadata;
pub mod pretty;
/// Enumerations for different data sources, venues, and publishers.
pub mod publishers;
#[cfg(feature = "python")]
Expand Down
127 changes: 127 additions & 0 deletions rust/dbn/src/pretty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Contains new types for pretty-printing values found in DBN
//! records.
use std::fmt;

use time::format_description::FormatItem;

use crate::FIXED_PRICE_SCALE;

/// A new type for formatting nanosecond UNIX timestamps.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Ts(pub u64);

/// A new type for formatting nanosecond UNIX timestamps.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Px(pub i64);

impl From<u64> for Ts {
fn from(value: u64) -> Self {
Self(value)
}
}

impl From<i64> for Px {
fn from(value: i64) -> Self {
Self(value)
}
}

impl fmt::Debug for Ts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

impl fmt::Debug for Px {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

impl fmt::Display for Ts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&fmt_ts(self.0))
}
}

impl fmt::Display for Px {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&fmt_px(self.0))
}
}

/// Converts a fixed-precision price to a decimal string.
pub fn fmt_px(px: i64) -> String {
if px == crate::UNDEF_PRICE {
"UNDEF_PRICE".to_owned()
} else {
let (sign, px_abs) = if px < 0 { ("-", -px) } else { ("", px) };
let px_integer = px_abs / FIXED_PRICE_SCALE;
let px_fraction = px_abs % FIXED_PRICE_SCALE;
format!("{sign}{px_integer}.{px_fraction:09}")
}
}

/// Converts a nanosecond UNIX timestamp to a human-readable string in the format
/// `[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z`.
pub fn fmt_ts(ts: u64) -> String {
const TS_FORMAT: &[FormatItem<'static>] = time::macros::format_description!(
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z"
);
if ts == 0 {
String::new()
} else {
time::OffsetDateTime::from_unix_timestamp_nanos(ts as i128)
.map_err(|_| ())
.and_then(|dt| dt.format(TS_FORMAT).map_err(|_| ()))
.unwrap_or_else(|_| ts.to_string())
}
}

#[cfg(test)]
mod tests {
use crate::UNDEF_PRICE;

use super::*;

#[test]
fn test_fmt_px_negative() {
assert_eq!(fmt_px(-100_000), "-0.000100000");
}

#[test]
fn test_fmt_px_positive() {
assert_eq!(fmt_px(32_500_000_000), "32.500000000");
}

#[test]
fn test_fmt_px_zero() {
assert_eq!(fmt_px(0), "0.000000000");
}

#[test]
fn test_fmt_px_undef() {
assert_eq!(fmt_px(UNDEF_PRICE), "UNDEF_PRICE");
}

#[test]
fn test_fmt_ts_0() {
assert!(fmt_ts(0).is_empty());
}

#[test]
fn test_fmt_ts_1() {
assert_eq!(fmt_ts(1), "1970-01-01T00:00:00.000000001Z");
}

#[test]
fn test_fmt_ts_future() {
assert_eq!(
fmt_ts(1622838300000000000),
"2021-06-04T20:25:00.000000000Z"
);
}
}

0 comments on commit 022c6cf

Please sign in to comment.