use bot_core::{InstrumentId, Quote};
use std::collections::HashMap;
#[async_trait::async_trait]
pub trait QuoteSource: Send + Sync {
async fn poll_quotes(&self, instruments: &[InstrumentId]) -> Vec<Quote>;
fn last_quote(&self, instrument: &InstrumentId) -> Option<Quote>;
fn set_time(&mut self, time_ms: i64);
fn current_time_ms(&self) -> i64;
}
pub struct MockQuoteSource {
quotes: HashMap<InstrumentId, Quote>,
time_ms: i64,
}
impl MockQuoteSource {
pub fn new() -> Self {
Self {
quotes: HashMap::new(),
time_ms: bot_core::now_ms(),
}
}
pub fn set_quote(&mut self, quote: Quote) {
self.quotes.insert(quote.instrument.clone(), quote);
}
pub fn clear(&mut self) {
self.quotes.clear();
}
}
impl Default for MockQuoteSource {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl QuoteSource for MockQuoteSource {
async fn poll_quotes(&self, instruments: &[InstrumentId]) -> Vec<Quote> {
instruments
.iter()
.filter_map(|inst| self.quotes.get(inst).cloned())
.collect()
}
fn last_quote(&self, instrument: &InstrumentId) -> Option<Quote> {
self.quotes.get(instrument).cloned()
}
fn set_time(&mut self, time_ms: i64) {
self.time_ms = time_ms;
}
fn current_time_ms(&self) -> i64 {
self.time_ms
}
}
#[cfg(test)]
mod tests {
use super::*;
use bot_core::{Price, Qty};
#[tokio::test]
async fn test_mock_quote_source() {
let mut source = MockQuoteSource::new();
let instrument = InstrumentId::new("BTC-PERP");
let quote = Quote {
instrument: instrument.clone(),
bid: Price::new(rust_decimal::Decimal::new(50000, 0)),
ask: Price::new(rust_decimal::Decimal::new(50010, 0)),
bid_size: Qty::new(rust_decimal::Decimal::new(1, 0)),
ask_size: Qty::new(rust_decimal::Decimal::new(1, 0)),
ts: 0,
};
source.set_quote(quote.clone());
let quotes = source.poll_quotes(&[instrument.clone()]).await;
assert_eq!(quotes.len(), 1);
assert_eq!(quotes[0].bid.0, rust_decimal::Decimal::new(50000, 0));
}
}