use hyper_ta::Candle;
use std::collections::VecDeque;
const DEFAULT_CAPACITY: usize = 200;
pub struct CandleBuffer {
candles: VecDeque<Candle>,
capacity: usize,
symbol: String,
}
impl CandleBuffer {
pub fn new(symbol: String) -> Self {
Self::with_capacity(symbol, DEFAULT_CAPACITY)
}
pub fn with_capacity(symbol: String, capacity: usize) -> Self {
Self {
candles: VecDeque::with_capacity(capacity),
capacity,
symbol,
}
}
pub fn symbol(&self) -> &str {
&self.symbol
}
pub fn len(&self) -> usize {
self.candles.len()
}
pub fn is_empty(&self) -> bool {
self.candles.is_empty()
}
pub fn push(&mut self, candle: Candle) {
if self.candles.len() >= self.capacity {
self.candles.pop_front();
}
self.candles.push_back(candle);
}
pub fn last(&self) -> Option<&Candle> {
self.candles.back()
}
pub fn as_slice(&self) -> Vec<Candle> {
self.candles.iter().cloned().collect()
}
pub fn backfill(&mut self, candles: Vec<Candle>) {
let existing_times: std::collections::HashSet<u64> =
self.candles.iter().map(|c| c.time).collect();
let mut new_candles: Vec<Candle> = candles
.into_iter()
.filter(|c| !existing_times.contains(&c.time))
.collect();
new_candles.sort_by_key(|c| c.time);
let mut all: Vec<Candle> = self.candles.drain(..).collect();
all.extend(new_candles);
all.sort_by_key(|c| c.time);
if all.len() > self.capacity {
all = all.split_off(all.len() - self.capacity);
}
self.candles = all.into_iter().collect();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_candle(time: u64, close: f64) -> Candle {
Candle {
time,
open: close - 1.0,
high: close + 1.0,
low: close - 2.0,
close,
volume: 100.0,
}
}
#[test]
fn push_and_len() {
let mut buf = CandleBuffer::new("BTC-PERP".into());
assert!(buf.is_empty());
buf.push(make_candle(1000, 50000.0));
assert_eq!(buf.len(), 1);
assert_eq!(buf.symbol(), "BTC-PERP");
}
#[test]
fn capacity_eviction() {
let mut buf = CandleBuffer::with_capacity("ETH".into(), 3);
buf.push(make_candle(1, 100.0));
buf.push(make_candle(2, 200.0));
buf.push(make_candle(3, 300.0));
buf.push(make_candle(4, 400.0));
assert_eq!(buf.len(), 3);
assert_eq!(buf.as_slice()[0].time, 2); assert_eq!(buf.last().unwrap().time, 4);
}
#[test]
fn backfill_dedup() {
let mut buf = CandleBuffer::with_capacity("BTC".into(), 5);
buf.push(make_candle(3, 300.0));
buf.push(make_candle(5, 500.0));
buf.backfill(vec![
make_candle(1, 100.0),
make_candle(3, 300.0), make_candle(4, 400.0),
]);
assert_eq!(buf.len(), 4); let times: Vec<u64> = buf.as_slice().iter().map(|c| c.time).collect();
assert_eq!(times, vec![1, 3, 4, 5]);
}
#[test]
fn backfill_respects_capacity() {
let mut buf = CandleBuffer::with_capacity("BTC".into(), 3);
buf.backfill(vec![
make_candle(1, 100.0),
make_candle(2, 200.0),
make_candle(3, 300.0),
make_candle(4, 400.0),
make_candle(5, 500.0),
]);
assert_eq!(buf.len(), 3);
assert_eq!(buf.as_slice()[0].time, 3); }
#[test]
fn last_returns_most_recent() {
let mut buf = CandleBuffer::new("SOL".into());
assert!(buf.last().is_none());
buf.push(make_candle(1, 10.0));
buf.push(make_candle(2, 20.0));
assert_eq!(buf.last().unwrap().close, 20.0);
}
}