use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use super::ActionType;
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Level2Entry {
pub md_update_id: u64,
pub account_id: u64,
pub action_date_time: String,
pub action_type: ActionType,
pub last_trade_price: Decimal,
pub order_id: u64,
pub price: Decimal,
pub instrument_id: u64,
pub quantity: Decimal,
pub side: u8,
}
impl Level2Entry {
pub fn from_array(value: &serde_json::Value) -> Result<Self> {
let arr = value.as_array().ok_or_else(|| {
Error::NdaxError(0, "Level2 entry must be an array".to_string())
})?;
if arr.len() < 10 {
return Err(Error::NdaxError(
0,
format!(
"Level2 entry array must have at least 10 elements, got {}",
arr.len()
),
));
}
Ok(Self {
md_update_id: arr[0].as_u64().unwrap_or(0),
account_id: arr[1].as_u64().unwrap_or(0),
action_date_time: arr[2].as_str().unwrap_or("").to_string(),
action_type: parse_action_type(&arr[3]),
last_trade_price: parse_decimal(&arr[4]),
order_id: arr[5].as_u64().unwrap_or(0),
price: parse_decimal(&arr[6]),
instrument_id: arr[7].as_u64().unwrap_or(0),
quantity: parse_decimal(&arr[8]),
side: arr[9].as_u64().unwrap_or(0) as u8,
})
}
pub fn is_bid(&self) -> bool { self.side == 0 }
pub fn is_ask(&self) -> bool { self.side == 1 }
pub fn is_delete(&self) -> bool { self.action_type == ActionType::Delete }
}
fn parse_decimal(value: &serde_json::Value) -> Decimal {
if let Some(s) = value.as_str() {
s.parse().unwrap_or_default()
} else if let Some(f) = value.as_f64() {
Decimal::try_from(f).unwrap_or_default()
} else if let Some(i) = value.as_i64() {
Decimal::from(i)
} else {
Decimal::default()
}
}
fn parse_action_type(value: &serde_json::Value) -> ActionType {
match value.as_u64().unwrap_or(0) {
0 => ActionType::New,
1 => ActionType::Update,
2 => ActionType::Delete,
_ => ActionType::New,
}
}
#[cfg(test)]
mod tests {
use rust_decimal_macros::dec;
use serde_json::json;
use super::*;
#[test]
fn test_level2_entry_from_array() {
let json = json!([
1234567,
0,
"2024-01-15T10:00:00Z",
0,
50000.00,
98765,
50000.00,
1,
0.001,
0
]);
let entry = Level2Entry::from_array(&json).unwrap();
assert_eq!(entry.md_update_id, 1234567);
assert_eq!(entry.account_id, 0);
assert_eq!(entry.action_date_time, "2024-01-15T10:00:00Z");
assert_eq!(entry.action_type, ActionType::New);
assert_eq!(entry.last_trade_price, dec!(50000.00));
assert_eq!(entry.order_id, 98765);
assert_eq!(entry.price, dec!(50000.00));
assert_eq!(entry.instrument_id, 1);
assert_eq!(entry.quantity, dec!(0.001));
assert_eq!(entry.side, 0);
assert!(entry.is_bid());
assert!(!entry.is_ask());
}
#[test]
fn test_level2_entry_sell_side() {
let json = json!([
999,
0,
"2024-01-15T10:00:00Z",
1,
51000.00,
11111,
51000.00,
1,
0.5,
1
]);
let entry = Level2Entry::from_array(&json).unwrap();
assert_eq!(entry.side, 1);
assert!(!entry.is_bid());
assert!(entry.is_ask());
assert_eq!(entry.action_type, ActionType::Update);
}
#[test]
fn test_level2_entry_delete_action() {
let json = json!([
888,
0,
"2024-01-15T10:00:00Z",
2,
0,
22222,
49000.00,
1,
0,
0
]);
let entry = Level2Entry::from_array(&json).unwrap();
assert!(entry.is_delete());
assert_eq!(entry.action_type, ActionType::Delete);
}
#[test]
fn test_level2_entry_invalid_not_array() {
let json = json!({"key": "value"});
let result = Level2Entry::from_array(&json);
assert!(result.is_err());
}
#[test]
fn test_level2_entry_invalid_too_short() {
let json = json!([1, 2, 3]);
let result = Level2Entry::from_array(&json);
assert!(result.is_err());
}
#[test]
fn test_level2_entry_string_decimals() {
let json = json!([
1234567,
0,
"2024-01-15T10:00:00Z",
0,
"50000.12345",
98765,
"49999.99",
1,
"0.00123456",
0
]);
let entry = Level2Entry::from_array(&json).unwrap();
assert_eq!(entry.last_trade_price, dec!(50000.12345));
assert_eq!(entry.price, dec!(49999.99));
assert_eq!(entry.quantity, dec!(0.00123456));
}
}