use crate::Decimal;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MarketTick {
pub timestamp: u64,
pub bid_price: Decimal,
pub bid_size: Decimal,
pub ask_price: Decimal,
pub ask_size: Decimal,
pub last_price: Option<Decimal>,
pub last_size: Option<Decimal>,
}
impl MarketTick {
#[must_use]
pub fn new(
timestamp: u64,
bid_price: Decimal,
bid_size: Decimal,
ask_price: Decimal,
ask_size: Decimal,
) -> Self {
Self {
timestamp,
bid_price,
bid_size,
ask_price,
ask_size,
last_price: None,
last_size: None,
}
}
#[must_use]
pub fn with_last_trade(
timestamp: u64,
bid_price: Decimal,
bid_size: Decimal,
ask_price: Decimal,
ask_size: Decimal,
last_price: Decimal,
last_size: Decimal,
) -> Self {
Self {
timestamp,
bid_price,
bid_size,
ask_price,
ask_size,
last_price: Some(last_price),
last_size: Some(last_size),
}
}
#[must_use]
pub fn mid_price(&self) -> Decimal {
(self.bid_price + self.ask_price) / Decimal::TWO
}
#[must_use]
pub fn spread(&self) -> Decimal {
self.ask_price - self.bid_price
}
#[must_use]
pub fn spread_bps(&self) -> Decimal {
let mid = self.mid_price();
if mid > Decimal::ZERO {
(self.spread() / mid) * Decimal::from(10000)
} else {
Decimal::ZERO
}
}
#[must_use]
pub fn total_liquidity(&self) -> Decimal {
self.bid_size + self.ask_size
}
#[must_use]
pub fn imbalance(&self) -> Decimal {
let total = self.total_liquidity();
if total > Decimal::ZERO {
(self.bid_size - self.ask_size) / total
} else {
Decimal::ZERO
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OHLCVBar {
pub timestamp: u64,
pub open: Decimal,
pub high: Decimal,
pub low: Decimal,
pub close: Decimal,
pub volume: Decimal,
}
impl OHLCVBar {
#[must_use]
pub fn new(
timestamp: u64,
open: Decimal,
high: Decimal,
low: Decimal,
close: Decimal,
volume: Decimal,
) -> Self {
Self {
timestamp,
open,
high,
low,
close,
volume,
}
}
#[must_use]
pub fn range(&self) -> Decimal {
self.high - self.low
}
#[must_use]
pub fn body(&self) -> Decimal {
if self.close > self.open {
self.close - self.open
} else {
self.open - self.close
}
}
#[must_use]
pub fn is_bullish(&self) -> bool {
self.close > self.open
}
#[must_use]
pub fn is_bearish(&self) -> bool {
self.close < self.open
}
#[must_use]
pub fn typical_price(&self) -> Decimal {
(self.high + self.low + self.close) / Decimal::from(3)
}
#[must_use]
pub fn vwap(&self) -> Decimal {
self.typical_price()
}
}
pub trait HistoricalDataSource {
fn next_tick(&mut self) -> Option<MarketTick>;
fn peek_tick(&self) -> Option<&MarketTick>;
fn reset(&mut self);
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn remaining(&self) -> usize;
}
#[derive(Debug, Clone)]
pub struct VecDataSource {
ticks: Vec<MarketTick>,
index: usize,
}
impl VecDataSource {
#[must_use]
pub fn new(ticks: Vec<MarketTick>) -> Self {
Self { ticks, index: 0 }
}
#[must_use]
pub fn empty() -> Self {
Self {
ticks: Vec::new(),
index: 0,
}
}
#[must_use]
pub fn current_index(&self) -> usize {
self.index
}
#[must_use]
pub fn ticks(&self) -> &[MarketTick] {
&self.ticks
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&MarketTick> {
self.ticks.get(index)
}
pub fn push(&mut self, tick: MarketTick) {
self.ticks.push(tick);
}
#[must_use]
pub fn time_range(&self) -> Option<(u64, u64)> {
if self.ticks.is_empty() {
None
} else {
Some((
self.ticks.first().unwrap().timestamp,
self.ticks.last().unwrap().timestamp,
))
}
}
}
impl HistoricalDataSource for VecDataSource {
fn next_tick(&mut self) -> Option<MarketTick> {
if self.index < self.ticks.len() {
let tick = self.ticks[self.index].clone();
self.index += 1;
Some(tick)
} else {
None
}
}
fn peek_tick(&self) -> Option<&MarketTick> {
self.ticks.get(self.index)
}
fn reset(&mut self) {
self.index = 0;
}
fn len(&self) -> usize {
self.ticks.len()
}
fn remaining(&self) -> usize {
self.ticks.len().saturating_sub(self.index)
}
}
impl Default for VecDataSource {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
fn create_test_tick(timestamp: u64, bid: Decimal, ask: Decimal) -> MarketTick {
MarketTick::new(timestamp, bid, dec!(1.0), ask, dec!(1.0))
}
#[test]
fn test_market_tick_new() {
let tick = MarketTick::new(1000, dec!(100.0), dec!(1.0), dec!(100.1), dec!(1.0));
assert_eq!(tick.timestamp, 1000);
assert_eq!(tick.bid_price, dec!(100.0));
assert_eq!(tick.ask_price, dec!(100.1));
assert!(tick.last_price.is_none());
}
#[test]
fn test_market_tick_with_last_trade() {
let tick = MarketTick::with_last_trade(
1000,
dec!(100.0),
dec!(1.0),
dec!(100.1),
dec!(1.0),
dec!(100.05),
dec!(0.5),
);
assert_eq!(tick.last_price, Some(dec!(100.05)));
assert_eq!(tick.last_size, Some(dec!(0.5)));
}
#[test]
fn test_market_tick_mid_price() {
let tick = create_test_tick(1000, dec!(100.0), dec!(100.2));
assert_eq!(tick.mid_price(), dec!(100.1));
}
#[test]
fn test_market_tick_spread() {
let tick = create_test_tick(1000, dec!(100.0), dec!(100.2));
assert_eq!(tick.spread(), dec!(0.2));
}
#[test]
fn test_market_tick_spread_bps() {
let tick = create_test_tick(1000, dec!(100.0), dec!(100.1));
let bps = tick.spread_bps();
assert!(bps > dec!(9.0) && bps < dec!(10.0));
}
#[test]
fn test_market_tick_imbalance() {
let tick = MarketTick::new(1000, dec!(100.0), dec!(2.0), dec!(100.1), dec!(1.0));
let imbalance = tick.imbalance();
assert!(imbalance > dec!(0.33) && imbalance < dec!(0.34));
}
#[test]
fn test_ohlcv_bar_new() {
let bar = OHLCVBar::new(
1000,
dec!(100.0),
dec!(105.0),
dec!(99.0),
dec!(102.0),
dec!(1000.0),
);
assert_eq!(bar.timestamp, 1000);
assert_eq!(bar.open, dec!(100.0));
assert_eq!(bar.high, dec!(105.0));
assert_eq!(bar.low, dec!(99.0));
assert_eq!(bar.close, dec!(102.0));
assert_eq!(bar.volume, dec!(1000.0));
}
#[test]
fn test_ohlcv_bar_range() {
let bar = OHLCVBar::new(
1000,
dec!(100.0),
dec!(105.0),
dec!(99.0),
dec!(102.0),
dec!(1000.0),
);
assert_eq!(bar.range(), dec!(6.0));
}
#[test]
fn test_ohlcv_bar_body() {
let bullish = OHLCVBar::new(
1000,
dec!(100.0),
dec!(105.0),
dec!(99.0),
dec!(103.0),
dec!(1000.0),
);
assert_eq!(bullish.body(), dec!(3.0));
let bearish = OHLCVBar::new(
1000,
dec!(103.0),
dec!(105.0),
dec!(99.0),
dec!(100.0),
dec!(1000.0),
);
assert_eq!(bearish.body(), dec!(3.0));
}
#[test]
fn test_ohlcv_bar_bullish_bearish() {
let bullish = OHLCVBar::new(
1000,
dec!(100.0),
dec!(105.0),
dec!(99.0),
dec!(103.0),
dec!(1000.0),
);
assert!(bullish.is_bullish());
assert!(!bullish.is_bearish());
let bearish = OHLCVBar::new(
1000,
dec!(103.0),
dec!(105.0),
dec!(99.0),
dec!(100.0),
dec!(1000.0),
);
assert!(!bearish.is_bullish());
assert!(bearish.is_bearish());
}
#[test]
fn test_ohlcv_bar_typical_price() {
let bar = OHLCVBar::new(
1000,
dec!(100.0),
dec!(105.0),
dec!(99.0),
dec!(102.0),
dec!(1000.0),
);
assert_eq!(bar.typical_price(), dec!(102.0));
}
#[test]
fn test_vec_data_source_new() {
let ticks = vec![
create_test_tick(1000, dec!(100.0), dec!(100.1)),
create_test_tick(1001, dec!(100.1), dec!(100.2)),
];
let source = VecDataSource::new(ticks);
assert_eq!(source.len(), 2);
assert!(!source.is_empty());
}
#[test]
fn test_vec_data_source_empty() {
let source = VecDataSource::empty();
assert_eq!(source.len(), 0);
assert!(source.is_empty());
}
#[test]
fn test_vec_data_source_next_tick() {
let ticks = vec![
create_test_tick(1000, dec!(100.0), dec!(100.1)),
create_test_tick(1001, dec!(100.1), dec!(100.2)),
];
let mut source = VecDataSource::new(ticks);
let tick1 = source.next_tick().unwrap();
assert_eq!(tick1.timestamp, 1000);
let tick2 = source.next_tick().unwrap();
assert_eq!(tick2.timestamp, 1001);
assert!(source.next_tick().is_none());
}
#[test]
fn test_vec_data_source_peek_tick() {
let ticks = vec![
create_test_tick(1000, dec!(100.0), dec!(100.1)),
create_test_tick(1001, dec!(100.1), dec!(100.2)),
];
let mut source = VecDataSource::new(ticks);
let peeked = source.peek_tick().unwrap();
assert_eq!(peeked.timestamp, 1000);
let peeked2 = source.peek_tick().unwrap();
assert_eq!(peeked2.timestamp, 1000);
source.next_tick();
let peeked3 = source.peek_tick().unwrap();
assert_eq!(peeked3.timestamp, 1001);
}
#[test]
fn test_vec_data_source_reset() {
let ticks = vec![
create_test_tick(1000, dec!(100.0), dec!(100.1)),
create_test_tick(1001, dec!(100.1), dec!(100.2)),
];
let mut source = VecDataSource::new(ticks);
source.next_tick();
source.next_tick();
assert_eq!(source.remaining(), 0);
source.reset();
assert_eq!(source.remaining(), 2);
assert_eq!(source.current_index(), 0);
}
#[test]
fn test_vec_data_source_remaining() {
let ticks = vec![
create_test_tick(1000, dec!(100.0), dec!(100.1)),
create_test_tick(1001, dec!(100.1), dec!(100.2)),
create_test_tick(1002, dec!(100.2), dec!(100.3)),
];
let mut source = VecDataSource::new(ticks);
assert_eq!(source.remaining(), 3);
source.next_tick();
assert_eq!(source.remaining(), 2);
source.next_tick();
assert_eq!(source.remaining(), 1);
source.next_tick();
assert_eq!(source.remaining(), 0);
}
#[test]
fn test_vec_data_source_time_range() {
let ticks = vec![
create_test_tick(1000, dec!(100.0), dec!(100.1)),
create_test_tick(2000, dec!(100.1), dec!(100.2)),
create_test_tick(3000, dec!(100.2), dec!(100.3)),
];
let source = VecDataSource::new(ticks);
let (start, end) = source.time_range().unwrap();
assert_eq!(start, 1000);
assert_eq!(end, 3000);
}
#[test]
fn test_vec_data_source_time_range_empty() {
let source = VecDataSource::empty();
assert!(source.time_range().is_none());
}
#[test]
fn test_vec_data_source_push() {
let mut source = VecDataSource::empty();
source.push(create_test_tick(1000, dec!(100.0), dec!(100.1)));
source.push(create_test_tick(1001, dec!(100.1), dec!(100.2)));
assert_eq!(source.len(), 2);
}
#[cfg(feature = "serde")]
#[test]
fn test_market_tick_serialization() {
let tick = MarketTick::new(1000, dec!(100.0), dec!(1.0), dec!(100.1), dec!(1.0));
let json = serde_json::to_string(&tick).unwrap();
let deserialized: MarketTick = serde_json::from_str(&json).unwrap();
assert_eq!(tick, deserialized);
}
#[cfg(feature = "serde")]
#[test]
fn test_ohlcv_bar_serialization() {
let bar = OHLCVBar::new(
1000,
dec!(100.0),
dec!(105.0),
dec!(99.0),
dec!(102.0),
dec!(1000.0),
);
let json = serde_json::to_string(&bar).unwrap();
let deserialized: OHLCVBar = serde_json::from_str(&json).unwrap();
assert_eq!(bar, deserialized);
}
}