use std::fmt::{Display, Formatter};
use chrono::{DateTime, Utc};
use crate::asset::AssetSymbol;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Quote {
pub time: DateTime<Utc>,
pub ask_price: f64,
pub ask_size: u64,
pub bid_price: f64,
pub bid_size: u64,
pub symbol: AssetSymbol,
}
impl Quote {
pub fn spread(&self) -> f64 {
&self.ask_price - &self.bid_price
}
pub fn spread_percent(&self) -> f64 {
if !self.valid_ask() {
return Default::default();
}
let spread = self.spread();
(spread / &self.ask_price) * 100.0
}
pub fn valid_ask(&self) -> bool {
self.ask_price > 0.0
}
pub fn valid_bid(&self) -> bool {
self.bid_price > 0.0
}
#[cfg(test)]
pub fn fixture(symbol: AssetSymbol) -> Self {
Self {
time: Default::default(),
ask_price: 200.0,
ask_size: 1,
bid_price: 100.0,
bid_size: 1,
symbol,
}
}
}
impl Display for Quote {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let ask = self.ask_price;
let bid = self.bid_price;
let spread = self.spread();
let spread_percent = self.spread_percent();
write!(
f,
"{} ask: {:.2}, bid: {:.2}, spread: {:.2} ({:.2}%)",
self.symbol, ask, bid, spread, spread_percent
)
}
}
#[cfg(test)]
mod test {
use crate::asset::AssetSymbol;
use crate::platform::quote::Quote;
use approx::assert_relative_eq;
#[test]
fn display() {
let quote = Quote {
ask_price: 200.456,
bid_price: 100.123,
symbol: AssetSymbol::new("vti"),
..Default::default()
};
let display = format!("{}", quote);
let expected = "VTI ask: 200.46, bid: 100.12, spread: 100.33 (50.05%)";
assert_eq!(display, expected)
}
#[test]
fn spread() {
let quote = create_spread_quote();
let expected = 0.05;
assert_relative_eq!(quote.spread(), expected, max_relative = 0.001);
}
#[test]
fn spread_percent() {
let quote = create_spread_quote();
let expected = 0.5;
assert_relative_eq!(quote.spread_percent(), expected, max_relative = 0.001)
}
#[test]
fn spread_percent_invalid_ask() {
let mut quote = create_spread_quote();
quote.ask_price = Default::default();
let expected = 0.0;
assert_eq!(quote.spread_percent(), expected)
}
#[test]
fn valid_ask_invalid() {
let quote = Quote {
ask_price: 0.0,
..Default::default()
};
assert!(!quote.valid_ask())
}
#[test]
fn valid_ask_valid() {
let quote = Quote {
ask_price: 10.0,
..Default::default()
};
assert!(quote.valid_ask())
}
#[test]
fn valid_bid_invalid() {
let quote = Quote {
bid_price: 0.0,
..Default::default()
};
assert!(!quote.valid_bid())
}
#[test]
fn valid_bid_valid() {
let quote = Quote {
bid_price: 10.0,
..Default::default()
};
assert!(quote.valid_bid())
}
fn create_spread_quote() -> Quote {
Quote {
ask_price: 10.0,
bid_price: 9.95,
..Default::default()
}
}
}