pub mod account_state;
pub mod bar;
pub mod close;
pub mod delta;
pub mod depth;
pub mod index_price;
pub mod instrument;
pub mod mark_price;
pub mod order_filled;
pub mod position;
pub mod quote;
pub mod report;
pub mod trade;
use arrow::datatypes::{DataType, Field, TimeUnit};
use nautilus_model::types::{
Money, Price, Quantity, fixed::MAX_FLOAT_PRECISION, price::PRICE_ERROR,
};
use rust_decimal::prelude::ToPrimitive;
const DISPLAY_MAX_PRECISION: u8 = 18;
pub(super) fn utf8_field(name: &str, nullable: bool) -> Field {
Field::new(name, DataType::Utf8, nullable)
}
pub(super) fn bool_field(name: &str, nullable: bool) -> Field {
Field::new(name, DataType::Boolean, nullable)
}
pub(super) fn float64_field(name: &str, nullable: bool) -> Field {
Field::new(name, DataType::Float64, nullable)
}
pub(super) fn uint8_field(name: &str, nullable: bool) -> Field {
Field::new(name, DataType::UInt8, nullable)
}
pub(super) fn uint32_field(name: &str, nullable: bool) -> Field {
Field::new(name, DataType::UInt32, nullable)
}
pub(super) fn uint64_field(name: &str, nullable: bool) -> Field {
Field::new(name, DataType::UInt64, nullable)
}
pub(super) fn timestamp_field(name: &str, nullable: bool) -> Field {
Field::new(
name,
DataType::Timestamp(TimeUnit::Nanosecond, None),
nullable,
)
}
pub(super) fn unix_nanos_to_i64(value: u64) -> i64 {
i64::try_from(value).unwrap_or(i64::MAX)
}
pub(super) fn price_to_f64(price: &Price) -> f64 {
if price.is_undefined() || price.raw == PRICE_ERROR || price.precision > DISPLAY_MAX_PRECISION {
return f64::NAN;
}
if price.precision <= MAX_FLOAT_PRECISION {
price.as_f64()
} else {
price.as_decimal().to_f64().unwrap_or(f64::NAN)
}
}
pub(super) fn quantity_to_f64(quantity: &Quantity) -> f64 {
if quantity.is_undefined() || quantity.precision > DISPLAY_MAX_PRECISION {
return f64::NAN;
}
if quantity.precision <= MAX_FLOAT_PRECISION {
quantity.as_f64()
} else {
quantity.as_decimal().to_f64().unwrap_or(f64::NAN)
}
}
pub(super) fn money_to_f64(money: &Money) -> f64 {
if money.currency.precision > DISPLAY_MAX_PRECISION {
return f64::NAN;
}
if money.currency.precision <= MAX_FLOAT_PRECISION {
money.as_f64()
} else {
money.as_decimal().to_f64().unwrap_or(f64::NAN)
}
}
#[cfg(test)]
mod tests {
use nautilus_model::types::{
Currency, Money, Price, Quantity,
price::{ERROR_PRICE, PRICE_ERROR, PRICE_UNDEF},
quantity::QUANTITY_UNDEF,
};
use rstest::rstest;
use super::{money_to_f64, price_to_f64, quantity_to_f64};
#[rstest]
fn test_price_to_f64_normal_value() {
let price = Price::from("100.10");
assert!((price_to_f64(&price) - 100.10).abs() < 1e-9);
}
#[rstest]
fn test_price_to_f64_undef_sentinel_is_nan() {
let price = Price::from_raw(PRICE_UNDEF, 0);
assert!(price_to_f64(&price).is_nan());
}
#[rstest]
fn test_price_to_f64_error_sentinel_is_nan() {
let price = Price::from_raw(PRICE_ERROR, 0);
assert!(price_to_f64(&price).is_nan());
}
#[rstest]
fn test_price_to_f64_error_price_constant_is_nan() {
assert!(price_to_f64(&ERROR_PRICE).is_nan());
}
#[rstest]
fn test_price_to_f64_wei_precision_boundary_is_finite() {
let price = Price {
raw: 1_000_000_000_000_000_000,
precision: 18,
};
let value = price_to_f64(&price);
assert!(value.is_finite(), "precision 18 should not return NaN");
assert!((value - 1.0).abs() < 1e-9);
}
#[rstest]
fn test_quantity_to_f64_normal_value() {
let quantity = Quantity::from(1_000);
assert!((quantity_to_f64(&quantity) - 1_000.0).abs() < 1e-9);
}
#[rstest]
fn test_quantity_to_f64_undef_sentinel_is_nan() {
let quantity = Quantity::from_raw(QUANTITY_UNDEF, 0);
assert!(quantity_to_f64(&quantity).is_nan());
}
#[rstest]
fn test_quantity_to_f64_pathological_precision_is_nan() {
let quantity = Quantity {
raw: 0,
precision: 200,
};
assert!(quantity_to_f64(&quantity).is_nan());
}
#[rstest]
fn test_money_to_f64_normal_value() {
let money = Money::new(123.45, Currency::USD());
assert!((money_to_f64(&money) - 123.45).abs() < 1e-9);
}
#[rstest]
fn test_money_to_f64_pathological_precision_is_nan() {
let mut money = Money::new(0.0, Currency::USD());
money.currency.precision = 200;
assert!(money_to_f64(&money).is_nan());
}
}