use std::{
collections::HashMap,
fmt::{Display, Formatter},
hash::Hash,
};
use derive_builder::Builder;
use indexmap::IndexMap;
use nautilus_core::{nanos::UnixNanos, serialization::Serializable};
use serde::{Deserialize, Serialize};
use super::GetTsInit;
use crate::{
enums::AggressorSide,
identifiers::{InstrumentId, TradeId},
types::{price::Price, quantity::Quantity},
};
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Builder)]
#[serde(tag = "type")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
)]
#[cfg_attr(feature = "trivial_copy", derive(Copy))]
pub struct TradeTick {
pub instrument_id: InstrumentId,
pub price: Price,
pub size: Quantity,
pub aggressor_side: AggressorSide,
pub trade_id: TradeId,
pub ts_event: UnixNanos,
pub ts_init: UnixNanos,
}
impl TradeTick {
#[must_use]
pub fn new(
instrument_id: InstrumentId,
price: Price,
size: Quantity,
aggressor_side: AggressorSide,
trade_id: TradeId,
ts_event: UnixNanos,
ts_init: UnixNanos,
) -> Self {
Self {
instrument_id,
price,
size,
aggressor_side,
trade_id,
ts_event,
ts_init,
}
}
#[must_use]
pub fn get_metadata(
instrument_id: &InstrumentId,
price_precision: u8,
size_precision: u8,
) -> HashMap<String, String> {
let mut metadata = HashMap::new();
metadata.insert("instrument_id".to_string(), instrument_id.to_string());
metadata.insert("price_precision".to_string(), price_precision.to_string());
metadata.insert("size_precision".to_string(), size_precision.to_string());
metadata
}
#[must_use]
pub fn get_fields() -> IndexMap<String, String> {
let mut metadata = IndexMap::new();
metadata.insert("price".to_string(), "Int64".to_string());
metadata.insert("size".to_string(), "UInt64".to_string());
metadata.insert("aggressor_side".to_string(), "UInt8".to_string());
metadata.insert("trade_id".to_string(), "Utf8".to_string());
metadata.insert("ts_event".to_string(), "UInt64".to_string());
metadata.insert("ts_init".to_string(), "UInt64".to_string());
metadata
}
}
impl Display for TradeTick {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{},{},{},{},{},{}",
self.instrument_id,
self.price,
self.size,
self.aggressor_side,
self.trade_id,
self.ts_event,
)
}
}
impl Serializable for TradeTick {}
impl GetTsInit for TradeTick {
fn ts_init(&self) -> UnixNanos {
self.ts_init
}
}
#[cfg(test)]
mod tests {
use nautilus_core::serialization::Serializable;
use pyo3::{IntoPy, Python};
use rstest::rstest;
use crate::{
data::{stubs::stub_trade_ethusdt_buyer, trade::TradeTick},
enums::AggressorSide,
};
#[rstest]
fn test_to_string(stub_trade_ethusdt_buyer: TradeTick) {
let trade = stub_trade_ethusdt_buyer;
assert_eq!(
trade.to_string(),
"ETHUSDT-PERP.BINANCE,10000.0000,1.00000000,BUYER,123456789,0"
);
}
#[rstest]
fn test_deserialize_raw_string() {
let raw_string = r#"{
"type": "TradeTick",
"instrument_id": "ETHUSDT-PERP.BINANCE",
"price": "10000.0000",
"size": "1.00000000",
"aggressor_side": "BUYER",
"trade_id": "123456789",
"ts_event": 0,
"ts_init": 1
}"#;
let trade: TradeTick = serde_json::from_str(raw_string).unwrap();
assert_eq!(trade.aggressor_side, AggressorSide::Buyer);
}
#[rstest]
fn test_from_pyobject(stub_trade_ethusdt_buyer: TradeTick) {
pyo3::prepare_freethreaded_python();
let trade = stub_trade_ethusdt_buyer;
Python::with_gil(|py| {
let tick_pyobject = trade.into_py(py);
let parsed_tick = TradeTick::from_pyobject(tick_pyobject.bind(py)).unwrap();
assert_eq!(parsed_tick, trade);
});
}
#[rstest]
fn test_json_serialization(stub_trade_ethusdt_buyer: TradeTick) {
let trade = stub_trade_ethusdt_buyer;
let serialized = trade.as_json_bytes().unwrap();
let deserialized = TradeTick::from_json_bytes(serialized.as_ref()).unwrap();
assert_eq!(deserialized, trade);
}
#[rstest]
fn test_msgpack_serialization(stub_trade_ethusdt_buyer: TradeTick) {
let trade = stub_trade_ethusdt_buyer;
let serialized = trade.as_msgpack_bytes().unwrap();
let deserialized = TradeTick::from_msgpack_bytes(serialized.as_ref()).unwrap();
assert_eq!(deserialized, trade);
}
}