use super::tick::LseMessage;
use crate::{
event::{MarketEvent, MarketIter},
subscription::trade::PublicTrade,
};
use chrono::Utc;
use rustrade_instrument::exchange::ExchangeId;
use smol_str::SmolStr;
impl<InstrumentKey> From<(ExchangeId, InstrumentKey, LseMessage)>
for MarketIter<InstrumentKey, PublicTrade>
{
fn from((exchange, instrument, message): (ExchangeId, InstrumentKey, LseMessage)) -> Self {
let LseMessage::Tick(tick) = message else {
return Self(vec![]);
};
Self(vec![Ok(MarketEvent {
time_exchange: tick.time_exchange,
time_received: Utc::now(),
exchange,
instrument,
kind: PublicTrade {
id: SmolStr::default(),
price: tick.price,
amount: tick.volume,
side: None,
},
})])
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
use chrono::DateTime;
use rust_decimal_macros::dec;
fn tick_json(ts: &str, price: &str, volume: &str) -> String {
format!(
r#"{{"type":"tick","symbol":"BTC/USD","ts":"{ts}","price":{price},
"bid":{price},"ask":42001.0,"volume":{volume}}}"#
)
}
fn decode(json: &str) -> Vec<MarketEvent<u8, PublicTrade>> {
let message: LseMessage = serde_json::from_str(json).unwrap();
MarketIter::<u8, PublicTrade>::from((ExchangeId::LseCrypto, 1_u8, message))
.0
.into_iter()
.map(Result::unwrap)
.collect()
}
#[test]
fn a_tick_decodes_to_one_trade() {
let events = decode(&tick_json(
"2026-01-02T09:37:24.760146+00:00",
"42000.5",
"0.00155",
));
assert_eq!(events.len(), 1);
assert_eq!(events[0].exchange, ExchangeId::LseCrypto);
assert_eq!(events[0].instrument, 1);
assert_eq!(
events[0].time_exchange,
"2026-01-02T09:37:24.760146Z"
.parse::<DateTime<Utc>>()
.unwrap()
);
assert_eq!(events[0].kind.price, dec!(42000.5));
assert_eq!(events[0].kind.amount, dec!(0.00155));
assert_eq!(events[0].kind.side, None);
}
#[test]
fn identical_consecutive_ticks_decode_to_identical_events() {
let json = tick_json("2026-01-02T09:37:24.760146+00:00", "42000.5", "0.00155");
let first = decode(&json);
let second = decode(&json);
assert_eq!(first.len(), 1);
assert_eq!(second.len(), 1);
assert_eq!(first[0].time_exchange, second[0].time_exchange);
assert_eq!(first[0].kind, second[0].kind);
}
#[test]
fn the_trade_id_is_empty_because_no_identifier_could_be_synthesised() {
let events = decode(&tick_json(
"2026-01-02T09:37:24.760146+00:00",
"42000.5",
"0.00155",
));
assert!(events[0].kind.id.is_empty());
}
#[test]
fn the_fabricated_fx_size_is_passed_through_rather_than_rewritten() {
let events = decode(&tick_json("2026-01-02 09:37:21.690159+00:00", "1.1", "1.0"));
assert_eq!(events[0].kind.amount, dec!(1.0));
}
#[test]
fn a_control_frame_yields_no_trades() {
let events = decode(r#"{"type":"replay_complete","symbol":"BTC/USD","rows":41}"#);
assert!(events.is_empty());
}
}