finance_solution/stocks/ta/
common.rs1use crate::util::error::{require_finite, FinanceError, FinanceResult};
4
5pub(crate) fn opt_cell(v: Option<f64>) -> String {
6 match v {
7 Some(x) => x.to_string(),
8 None => "n/a".to_string(),
9 }
10}
11
12pub(crate) fn validate_series(name: &'static str, xs: &[f64]) -> FinanceResult<()> {
13 if xs.is_empty() {
14 return Err(FinanceError::EmptyInput { what: name });
15 }
16 for &x in xs {
17 require_finite(name, x)?;
18 }
19 Ok(())
20}
21
22pub(crate) fn validate_positive_volume(volume: &[f64]) -> FinanceResult<()> {
23 validate_series("volume", volume)?;
24 for &v in volume {
25 if v < 0.0 {
26 return Err(FinanceError::InvalidCashflow {
27 message: "volume must be non-negative",
28 });
29 }
30 }
31 Ok(())
32}
33
34pub(crate) fn require_same_len(a: &[f64], b: &[f64], context: &'static str) -> FinanceResult<()> {
35 if a.len() != b.len() {
36 return Err(FinanceError::LengthMismatch {
37 left: a.len(),
38 right: b.len(),
39 context,
40 });
41 }
42 Ok(())
43}
44
45pub(crate) fn require_hlc(high: &[f64], low: &[f64], close: &[f64]) -> FinanceResult<()> {
46 validate_series("high", high)?;
47 validate_series("low", low)?;
48 validate_series("close", close)?;
49 require_same_len(high, low, "high/low")?;
50 require_same_len(high, close, "high/close")?;
51 for i in 0..high.len() {
52 if high[i] < low[i] {
53 return Err(FinanceError::InvalidCashflow {
54 message: "high must be >= low for each bar",
55 });
56 }
57 }
58 Ok(())
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
63pub enum StdevKind {
64 #[default]
66 Sample,
67 Population,
69}
70
71pub(crate) fn window_stdev(window: &[f64], kind: StdevKind) -> Option<f64> {
74 let n = window.len();
75 match kind {
76 StdevKind::Sample if n < 2 => return None,
77 StdevKind::Population if n < 1 => return None,
78 _ => {}
79 }
80 let mean = window.iter().sum::<f64>() / n as f64;
81 let denom = match kind {
82 StdevKind::Sample => n as f64 - 1.0,
83 StdevKind::Population => n as f64,
84 };
85 let var = window.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / denom;
86 Some(var.sqrt())
87}
88
89pub(crate) fn sample_stdev(window: &[f64]) -> Option<f64> {
91 window_stdev(window, StdevKind::Sample)
92}
93
94pub(crate) fn true_range(high: f64, low: f64, prev_close: Option<f64>) -> f64 {
96 let hl = high - low;
97 match prev_close {
98 None => hl,
99 Some(pc) => hl.max((high - pc).abs()).max((low - pc).abs()),
100 }
101}