use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::tick_consumer::TickConsumer;
use crate::types::Tick;
use std::collections::HashMap;
#[derive(Clone)]
pub struct FootprintPoc {
price_bucket: f64,
levels: HashMap<i64, f64>,
last_poc: f64,
}
impl FootprintPoc {
pub fn new(price_bucket: f64) -> Self {
Self {
price_bucket: price_bucket.max(1e-9),
levels: HashMap::new(),
last_poc: 0.0,
}
}
pub fn update_tick(&mut self, tick: &Tick) -> IndicatorValue {
let bucket = (tick.price / self.price_bucket).floor() as i64;
*self.levels.entry(bucket).or_insert(0.0) += tick.size;
if let Some((&poc_bucket, _)) = self
.levels
.iter()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
{
self.last_poc = poc_bucket as f64 * self.price_bucket;
}
IndicatorValue::Single(self.last_poc)
}
pub fn close_bar(&mut self) {
if let Some((&poc_bucket, _)) = self
.levels
.iter()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
{
self.last_poc = poc_bucket as f64 * self.price_bucket;
}
self.levels.clear();
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.last_poc)
}
pub fn reset(&mut self) {
self.levels.clear();
self.last_poc = 0.0;
}
pub fn is_ready(&self) -> bool {
self.last_poc != 0.0
}
pub fn poc_price(&self) -> f64 { self.last_poc }
}
impl TickConsumer for FootprintPoc {
fn update_tick(&mut self, tick: &Tick) -> IndicatorValue {
FootprintPoc::update_tick(self, tick)
}
fn value(&self) -> IndicatorValue { FootprintPoc::value(self) }
fn reset(&mut self) { FootprintPoc::reset(self) }
fn is_ready(&self) -> bool { FootprintPoc::is_ready(self) }
}
#[cfg(test)]
mod tests {
use super::*;
fn tick(price: f64, qty: f64) -> Tick {
Tick::new(0, price, qty, true)
}
#[test]
fn test_poc_selects_highest_volume_bucket() {
let mut poc = FootprintPoc::new(1.0);
for _ in 0..5 {
poc.update_tick(&tick(100.0, 1.0));
}
for _ in 0..10 {
poc.update_tick(&tick(101.0, 1.0));
}
poc.close_bar();
assert_eq!(poc.poc_price(), 101.0, "bucket 101 has more volume");
}
#[test]
fn test_poc_single_bucket() {
let mut poc = FootprintPoc::new(1.0);
poc.update_tick(&tick(50.0, 100.0));
poc.close_bar();
assert_eq!(poc.poc_price(), 50.0);
}
#[test]
fn test_poc_reset() {
let mut poc = FootprintPoc::new(1.0);
poc.update_tick(&tick(100.0, 10.0));
poc.close_bar();
poc.reset();
assert_eq!(poc.poc_price(), 0.0);
assert!(!poc.is_ready());
}
#[test]
fn test_poc_not_ready_before_close() {
let poc = FootprintPoc::new(1.0);
assert!(!poc.is_ready());
}
}