finance_solution/stocks/ta/mod.rs
1//! Technical analysis indicators on price / volume series.
2//!
3//! **Scope of this module:** pure **batch** building blocks a quant *engine* or notebook
4//! consumes. This crate does **not** run an event loop, subscribe to market data, or own
5//! portfolio state. See the crate README (“Quant pattern” and “Why not a streaming engine?”).
6//!
7//! # Layers (performance)
8//!
9//! | Layer | API | Cost class | Use |
10//! |-------|-----|------------|-----|
11//! | **Config** | `*Params` / `Validated*` (`Copy`) | O(1) validate once | Build at startup / `const` |
12//! | **Hot path (batch)** | `sma`, `ema`, `stochastics`, `macd`, … | O(n) pure math, no `String` | Research, backtests |
13//! | **Hot path (live)** | `SmaState` / `StochState` / … `push` / `push_bars` | O(1)–O(window) per bar | Streaming payloads |
14//! | **Solution** | `*_solution` | O(n) + formulas + tables | Teaching, audit, observability |
15//!
16//! # Quant ergonomics — recommended pattern (`const` + validate + `.compute`)
17//!
18//! Production code should **not** invent a new parameter list on every bar. Define the
19//! indicator variation once, validate once, reuse forever:
20//!
21//! ```
22//! use finance_solution::stocks::ta::{
23//! StochasticParams, ValidatedStochastic,
24//! MacdParams, ValidatedMacd,
25//! BollingerParams, ValidatedBollinger,
26//! };
27//!
28//! // --- Strategy knobs (module-level const packs) ---
29//! const FAST_STOCH_9_3: StochasticParams = StochasticParams::fast(9, 3);
30//! const MACD_12_26_9: MacdParams = MacdParams::standard();
31//! const BB_20_2: BollingerParams = BollingerParams::standard();
32//!
33//! // --- Startup: O(1) validation ---
34//! let stoch = ValidatedStochastic::new(FAST_STOCH_9_3).unwrap();
35//! let macd_eng = ValidatedMacd::new(MACD_12_26_9).unwrap();
36//! let bb = ValidatedBollinger::new(BB_20_2).unwrap();
37//!
38//! // --- Hot path: many symbols / many days ---
39//! # let high = [11.0_f64, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0];
40//! # let low = [10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5];
41//! # let close= [10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5];
42//! # let closes: Vec<f64> = (1..=40).map(|x| 100.0 + x as f64 * 0.1).collect();
43//! let kd = stoch.compute(&high, &low, &close).unwrap();
44//! let m = macd_eng.compute(&closes).unwrap();
45//! let bands = bb.compute(&closes).unwrap();
46//! assert_eq!(kd.k.len(), high.len());
47//! assert_eq!(m.macd.len(), closes.len());
48//! assert_eq!(bands.middle.len(), closes.len());
49//! ```
50//!
51//! **Why this shape?**
52//!
53//! 1. **Clarity** — `FAST_STOCH_9_3` documents the strategy; no magic positional args.
54//! 2. **Safety** — period=0 fails at `new`, not mid-batch.
55//! 3. **Speed** — validation is noise vs O(n) windows (see Criterion suite D).
56//! 4. **Variations** — Fast(9,3), Full(14,3,3), MACD(8,17,9) are just different `const` packs
57//! on the **same** functions — no combinatorial API explosion.
58//!
59//! Free functions (`stochastics(...)`, `macd(...)`, …) remain for scripts and doctests.
60//!
61//! # Warm-up policy
62//!
63//! Output length equals input length. Bars before a window is full are [`None`].
64//! Solution tables print warm-up as `n/a`.
65//!
66//! # Indicators
67//!
68//! | Indicator | Params / presets | Series | Solution + table |
69//! |-----------|------------------|--------|------------------|
70//! | SMA / EMA | `period` | [`sma`], [`ema`] (via [`SmaState`]/[`EmaState`]) | — |
71//! | Stochastic | [`StochasticParams::fast`] / [`full`](StochasticParams::full) | [`stochastics`] | [`stochastics_solution`] |
72//! | MACD | [`MacdParams::standard`] (12,26,9) | [`macd`] | [`macd_solution`] |
73//! | Bollinger | [`BollingerParams::standard`] (20,2, sample stdev) | [`bollinger`] | [`bollinger_solution`] |
74//! | Keltner | [`KeltnerParams::standard`] (20,10,2) | [`keltner`] | [`keltner_solution`] |
75//! | VWAP | [`VwapParams::cumulative_typical`] | [`vwap`] | [`vwap_solution`] |
76//! | RVOL | [`RvolParams::days_20`] | [`rvol`] | [`rvol_solution`] |
77//!
78//! # Incremental / streaming state (live bars)
79//!
80//! For tick/5s/1m **payloads**, use `*State` types: `push` one bar at a time, `push_bars` for
81//! multi-bar messages, or `from_history` then only push live updates. See [`state`] module docs
82//! and the README quant-engine sketch. **You** call `reset()` on VWAP when your calendar says so.
83//!
84//! Conventions worth knowing:
85//!
86//! - **Stochastic flat window** (HH == LL): carry previous raw %K, else 50.
87//! - **Bollinger stdev**: [`StdevKind::Sample`] (`n−1`) default; optional population (`n`).
88//! - **SMA/EMA batch** shares code with [`SmaState`] / [`EmaState`] (parity by construction).
89
90pub mod bollinger;
91pub mod common;
92pub mod keltner;
93pub mod macd;
94pub mod moving_average;
95#[cfg(test)]
96mod proptests;
97pub mod ring;
98pub mod rvol;
99pub mod state;
100pub mod stochastic;
101pub mod vwap;
102
103#[doc(inline)]
104pub use bollinger::*;
105#[doc(inline)]
106pub use common::StdevKind;
107#[doc(inline)]
108pub use keltner::*;
109#[doc(inline)]
110pub use macd::*;
111#[doc(inline)]
112pub use moving_average::*;
113#[doc(inline)]
114pub use rvol::*;
115#[doc(inline)]
116pub use state::*;
117#[doc(inline)]
118pub use stochastic::*;
119#[doc(inline)]
120pub use vwap::*;