use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::{Candle, Tick};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Side {
Buy,
Sell,
}
impl Side {
pub fn opposite(self) -> Self {
match self {
Self::Buy => Self::Sell,
Self::Sell => Self::Buy,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Symbol(pub String);
impl Symbol {
#[inline]
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for Symbol {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<String> for Symbol {
fn from(s: String) -> Self {
Self(s)
}
}
impl AsRef<str> for Symbol {
#[inline]
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<str> for Symbol {
#[inline]
fn borrow(&self) -> &str {
&self.0
}
}
impl PartialEq<str> for Symbol {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for Symbol {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Exchange(pub String);
impl From<&str> for Exchange {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MarketDataEvent {
Ticker {
exchange: Exchange,
symbol: Symbol,
tick: Tick,
},
Candle {
exchange: Exchange,
symbol: Symbol,
candle: Candle,
},
Trade {
exchange: Exchange,
symbol: Symbol,
side: Side,
price: f64,
size: f64,
timestamp: DateTime<Utc>,
},
}
impl MarketDataEvent {
pub fn symbol(&self) -> &Symbol {
match self {
Self::Ticker { symbol, .. }
| Self::Candle { symbol, .. }
| Self::Trade { symbol, .. } => symbol,
}
}
pub fn exchange(&self) -> &Exchange {
match self {
Self::Ticker { exchange, .. }
| Self::Candle { exchange, .. }
| Self::Trade { exchange, .. } => exchange,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Candle, Price, Tick, Volume};
use chrono::TimeZone;
#[test]
fn side_opposite_is_involutive() {
assert_eq!(Side::Buy.opposite(), Side::Sell);
assert_eq!(Side::Sell.opposite(), Side::Buy);
assert_eq!(Side::Buy.opposite().opposite(), Side::Buy);
assert_eq!(Side::Sell.opposite().opposite(), Side::Sell);
}
#[test]
fn symbol_as_str_borrow_and_display() {
let s = Symbol::new("BTCUSDT");
assert_eq!(s.as_str(), "BTCUSDT");
assert_eq!(s.as_ref(), "BTCUSDT");
assert_eq!(format!("{s}"), "BTCUSDT");
assert_eq!(s, "BTCUSDT");
assert_ne!(s, "ETHUSDT");
use std::collections::HashMap;
let mut m: HashMap<Symbol, i32> = HashMap::new();
m.insert(Symbol::new("BTCUSDT"), 1);
assert_eq!(m.get("BTCUSDT").copied(), Some(1));
}
#[test]
fn symbol_serde_transparent() {
let s = Symbol::new("ETHUSDT");
let json = serde_json::to_string(&s).unwrap();
assert_eq!(json, "\"ETHUSDT\"");
let back: Symbol = serde_json::from_str(&json).unwrap();
assert_eq!(back, s);
}
fn ev_ticker() -> MarketDataEvent {
MarketDataEvent::Ticker {
exchange: Exchange::from("kucoin"),
symbol: Symbol::new("XBTUSDTM"),
tick: Tick {
symbol: Symbol::new("XBTUSDTM"),
timestamp: Utc.timestamp_opt(0, 0).unwrap(),
bid: Price(1.0),
ask: Price(1.0),
bid_size: Volume(1.0),
ask_size: Volume(1.0),
last_price: None,
last_size: None,
},
}
}
fn ev_candle() -> MarketDataEvent {
MarketDataEvent::Candle {
exchange: Exchange::from("binance"),
symbol: Symbol::new("BTCUSDT"),
candle: Candle {
time: 0,
open: 1.0,
high: 1.0,
low: 1.0,
close: 1.0,
volume: 1.0,
},
}
}
fn ev_trade() -> MarketDataEvent {
MarketDataEvent::Trade {
exchange: Exchange::from("bybit"),
symbol: Symbol::new("ETHUSDT"),
side: Side::Buy,
price: 1.0,
size: 1.0,
timestamp: Utc.timestamp_opt(0, 0).unwrap(),
}
}
#[test]
fn market_data_event_accessors_cover_all_variants() {
let t = ev_ticker();
assert_eq!(t.symbol(), &Symbol::new("XBTUSDTM"));
assert_eq!(t.exchange(), &Exchange::from("kucoin"));
let c = ev_candle();
assert_eq!(c.symbol(), &Symbol::new("BTCUSDT"));
assert_eq!(c.exchange(), &Exchange::from("binance"));
let tr = ev_trade();
assert_eq!(tr.symbol(), &Symbol::new("ETHUSDT"));
assert_eq!(tr.exchange(), &Exchange::from("bybit"));
}
}