use super::bar::Bar;
pub const MAX_BARS: usize = 10_000;
pub const MAX_VISIBLE_BARS: usize = 2_000;
#[derive(Debug, Clone, Default)]
pub struct BarData {
pub bars: Vec<Bar>,
}
impl BarData {
pub fn new() -> Self {
Self { bars: Vec::new() }
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
bars: Vec::with_capacity(capacity),
}
}
pub fn from_bars(bars: Vec<Bar>) -> Self {
Self { bars }
}
pub fn push(&mut self, bar: Bar) {
self.bars.push(bar);
}
pub fn len(&self) -> usize {
self.bars.len()
}
pub fn is_empty(&self) -> bool {
self.bars.is_empty()
}
pub fn clear(&mut self) {
self.bars.clear();
}
pub fn trim_to_limit(&mut self) {
if self.bars.len() > MAX_BARS {
let excess = self.bars.len() - MAX_BARS;
self.bars.drain(0..excess);
log::debug!("Trimmed {} old bars, {} remaining", excess, self.bars.len());
}
}
pub fn push_with_limit(&mut self, bar: Bar) {
self.bars.push(bar);
self.trim_to_limit();
}
pub fn first(&self) -> Option<&Bar> {
self.bars.first()
}
pub fn last(&self) -> Option<&Bar> {
self.bars.last()
}
pub fn get(&self, index: usize) -> Option<&Bar> {
self.bars.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Bar> {
self.bars.get_mut(index)
}
pub fn iter(&self) -> impl Iterator<Item = &Bar> {
self.bars.iter()
}
pub fn min_price(&self) -> Option<f64> {
self.bars
.iter()
.map(|c| c.low)
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn max_price(&self) -> Option<f64> {
self.bars
.iter()
.map(|c| c.high)
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn price_range(&self) -> Option<(f64, f64)> {
let min = self.min_price()?;
let max = self.max_price()?;
Some((min, max))
}
pub fn max_volume(&self) -> Option<f64> {
self.bars
.iter()
.map(|c| c.volume)
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn total_volume(&self) -> f64 {
self.bars.iter().map(|c| c.volume).sum()
}
pub fn avg_volume(&self) -> Option<f64> {
if self.bars.is_empty() {
return None;
}
Some(self.total_volume() / self.bars.len() as f64)
}
pub fn to_heikin_ashi(&self) -> Self {
if self.bars.is_empty() {
return Self::new();
}
let mut ha_bars = Vec::with_capacity(self.bars.len());
let first = &self.bars[0];
let mut prev_ha_open = (first.open + first.close) / 2.0;
let mut prev_ha_close = (first.open + first.high + first.low + first.close) / 4.0;
ha_bars.push(Bar::new(
first.time,
prev_ha_open,
first.high,
first.low,
prev_ha_close,
first.volume,
));
for bar in self.bars.iter().skip(1) {
let ha_close = (bar.open + bar.high + bar.low + bar.close) / 4.0;
let ha_open = (prev_ha_open + prev_ha_close) / 2.0;
let ha_high = bar.high.max(ha_open).max(ha_close);
let ha_low = bar.low.min(ha_open).min(ha_close);
ha_bars.push(Bar::new(
bar.time, ha_open, ha_high, ha_low, ha_close, bar.volume,
));
prev_ha_open = ha_open;
prev_ha_close = ha_close;
}
Self::from_bars(ha_bars)
}
pub fn to_regular(&self) -> Self {
self.clone()
}
pub fn slice(&self, start: usize, end: usize) -> &[Bar] {
let end = end.min(self.bars.len());
let start = start.min(end);
&self.bars[start..end]
}
}
impl IntoIterator for BarData {
type Item = Bar;
type IntoIter = std::vec::IntoIter<Bar>;
fn into_iter(self) -> Self::IntoIter {
self.bars.into_iter()
}
}
impl<'a> IntoIterator for &'a BarData {
type Item = &'a Bar;
type IntoIter = std::slice::Iter<'a, Bar>;
fn into_iter(self) -> Self::IntoIter {
self.bars.iter()
}
}
impl FromIterator<Bar> for BarData {
fn from_iter<I: IntoIterator<Item = Bar>>(iter: I) -> Self {
Self {
bars: iter.into_iter().collect(),
}
}
}
impl std::ops::Index<usize> for BarData {
type Output = Bar;
fn index(&self, index: usize) -> &Self::Output {
&self.bars[index]
}
}
impl std::ops::IndexMut<usize> for BarData {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.bars[index]
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
fn create_test_bars() -> BarData {
let now = Utc::now();
BarData::from_bars(vec![
Bar::new(now, 100.0, 110.0, 95.0, 105.0, 1000.0),
Bar::new(now, 105.0, 115.0, 100.0, 110.0, 1500.0),
Bar::new(now, 110.0, 120.0, 105.0, 115.0, 1200.0),
])
}
#[test]
fn test_aggregations() {
let data = create_test_bars();
assert_eq!(data.min_price(), Some(95.0));
assert_eq!(data.max_price(), Some(120.0));
assert_eq!(data.max_volume(), Some(1500.0));
assert_eq!(data.total_volume(), 3700.0);
}
#[test]
fn test_heikin_ashi() {
let data = create_test_bars();
let ha = data.to_heikin_ashi();
assert_eq!(ha.len(), data.len());
assert!(ha.bars[1].open != data.bars[1].open);
}
#[test]
fn test_iteration() {
let data = create_test_bars();
let count = data.iter().count();
assert_eq!(count, 3);
}
#[test]
fn test_indexing() {
let data = create_test_bars();
assert_eq!(data[0].open, 100.0);
assert_eq!(data[2].close, 115.0);
}
}