use crate::{Ohlcv, Price};
use std::fmt::{Debug, Display};
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug)]
pub enum PriceSource {
Open,
High,
#[default]
Close,
Low,
HL2,
HLC3,
OHLC4,
HLCC4,
TrueRange,
}
impl Display for PriceSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl PriceSource {
pub(crate) fn extract(self, ohlcv: &impl Ohlcv, prev_close: Option<Price>) -> Price {
match self {
Self::Open => ohlcv.open(),
Self::High => ohlcv.high(),
Self::Close => ohlcv.close(),
Self::Low => ohlcv.low(),
Self::HL2 => f64::midpoint(ohlcv.high(), ohlcv.low()),
Self::HLC3 => (ohlcv.high() + ohlcv.low() + ohlcv.close()) / 3.0,
Self::OHLC4 => (ohlcv.open() + ohlcv.high() + ohlcv.low() + ohlcv.close()) / 4.0,
Self::HLCC4 => (ohlcv.high() + ohlcv.low() + ohlcv.close() + ohlcv.close()) / 4.0,
Self::TrueRange => {
let hl = ohlcv.high() - ohlcv.low();
match prev_close {
Some(prev_close) => {
let hc = (ohlcv.high() - prev_close).abs();
let lc = (ohlcv.low() - prev_close).abs();
hl.max(hc).max(lc)
}
None => hl,
}
}
}
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
use crate::test_util::{Bar, assert_approx};
fn bar() -> Bar {
Bar::new(10.0, 30.0, 5.0, 20.0)
}
#[test]
fn extract_open() {
assert_eq!(PriceSource::Open.extract(&bar(), None), 10.0);
}
#[test]
fn extract_high() {
assert_eq!(PriceSource::High.extract(&bar(), None), 30.0);
}
#[test]
fn extract_low() {
assert_eq!(PriceSource::Low.extract(&bar(), None), 5.0);
}
#[test]
fn extract_close() {
assert_eq!(PriceSource::Close.extract(&bar(), None), 20.0);
}
#[test]
fn extract_hl2() {
assert_eq!(PriceSource::HL2.extract(&bar(), None), 17.5);
}
#[test]
fn extract_hlc3() {
let result = PriceSource::HLC3.extract(&bar(), None);
assert_approx!(result, 55.0 / 3.0);
}
#[test]
fn extract_ohlc4() {
assert_eq!(PriceSource::OHLC4.extract(&bar(), None), 16.25);
}
#[test]
fn extract_hlcc4() {
assert_eq!(PriceSource::HLCC4.extract(&bar(), None), 18.75);
}
#[test]
fn true_range_without_prev_close_falls_back_to_hl() {
assert_eq!(PriceSource::TrueRange.extract(&bar(), None), 25.0);
}
#[test]
fn true_range_hl_wins() {
let b = bar();
assert_eq!(PriceSource::TrueRange.extract(&b, Some(15.0)), 25.0);
}
#[test]
fn true_range_high_vs_prev_close_wins() {
let b = bar();
assert_eq!(PriceSource::TrueRange.extract(&b, Some(-10.0)), 40.0);
}
#[test]
fn true_range_low_vs_prev_close_wins() {
let b = bar();
assert_eq!(PriceSource::TrueRange.extract(&b, Some(50.0)), 45.0);
}
}