Skip to main content

kestrel_chartkit/analytics/
mod.rs

1//! On-demand analytical snapshots over chronological OHLCV bars.
2//! These are pure computations: no provider, persistence, strategy scheduling or UI dependencies.
3//! Callers provide finite input bars and applicable volume data. Timestamp values are not inspected.
4//! Output enums/fields preserve the existing consumer wire representation with the serde feature.
5
6mod activity;
7mod cheat_sheet;
8mod fear_gauge;
9mod fear_greed;
10mod price;
11mod regime;
12mod seasonality;
13mod trend;
14mod trend_persistence;
15
16pub use activity::{activity_reading, ActivityReading};
17pub use cheat_sheet::{
18    cheat_sheet, CheatSheetLevel, CheatSheetLevelKind, CheatSheetLevelSide, CheatSheetReading,
19};
20pub use fear_gauge::{fear_gauge_reading, FearGaugeReading, FearGaugeState};
21pub use fear_greed::{fear_greed_reading, FearGreedDriver, FearGreedReading, FearGreedState};
22pub use price::{price_summary, PriceSummary};
23pub use regime::{classify_regime as classify_trend_regime, RegimeReading, RegimeState};
24pub use seasonality::{monthly_seasonality, MonthStatistics, MonthlyReturn, SeasonalityReport};
25pub use trend::{trend_reading, MarketPhase, TrendDirection, TrendReading};
26pub use trend_persistence::{
27    trend_persistence_reading, TrendPersistenceDirection, TrendPersistenceReading,
28    TrendPersistenceSensor, TrendPersistenceState,
29};
30
31/// True range for a bar given the previous bar's close: the full
32/// three-term form, or high−low on the very first bar. Shared by the
33/// regime (Choppiness) and price (ATR) read-outs.
34fn true_range(bar: &crate::Bar, prev_close: Option<f64>) -> f64 {
35    match prev_close {
36        None => bar.high - bar.low,
37        Some(prev) => (bar.high - bar.low)
38            .max((bar.high - prev).abs())
39            .max((bar.low - prev).abs()),
40    }
41}
42
43/// Kaufman Efficiency Ratio over the last `n` price changes (needs `n`+1
44/// closes): net move ÷ summed absolute moves, 0 = pure chop, 1 = a perfectly
45/// straight move. Shared by `regime` (regime vote) and `trend_persistence`
46/// (one of its four trend sensors) — same formula, same window shape.
47fn efficiency_ratio(bars: &[crate::Bar], n: usize) -> Option<f64> {
48    if bars.len() < n + 1 {
49        return None;
50    }
51    let closes = &bars[bars.len() - n - 1..];
52    let net = (closes[closes.len() - 1].close - closes[0].close).abs();
53    let mut volatility = 0.0;
54    for pair in closes.windows(2) {
55        volatility += (pair[1].close - pair[0].close).abs();
56    }
57    if volatility <= 0.0 {
58        return None;
59    }
60    Some(net / volatility)
61}
62
63mod position;
64pub use position::{atr_distance, position_alignment, PositionAlignment};