Skip to main content

ta_benchmarks/
catalogue_cases.rs

1//! Canonical representative cases and executable Indicator Catalogue measurement coverage.
2
3use fast_ta::inventory::{FunctionGroup, INDICATOR_CATALOGUE};
4use std::collections::BTreeSet;
5
6/// The typed Rust/C/Python adapter branch for one representative case.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum CaseKind {
9    Sma,
10    Bbands,
11    Rsi,
12    Macd,
13    Atr,
14    Adx,
15    HtDcPhase,
16    CdlDoji,
17    CdlEngulfing,
18    Cdl3WhiteSoldiers,
19    LinearReg,
20    TypPrice,
21    Obv,
22    Sin,
23    Add,
24}
25
26/// Metadata shared by every implementation adapter for one measured case.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct CaseSpec {
29    pub kind: CaseKind,
30    pub id: &'static str,
31    pub family: &'static str,
32    pub definition: &'static str,
33    pub parameters: &'static str,
34    pub output_kind: &'static str,
35    pub output_arity: usize,
36}
37
38include!(concat!(env!("OUT_DIR"), "/catalogue_cases.rs"));
39
40/// Measurement coverage for one official Indicator Definition.
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct DefinitionCoverage {
43    pub name: &'static str,
44    pub family: &'static str,
45    pub measured: bool,
46}
47
48/// Executable relationship between implemented and measured Catalogue Coverage.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct MeasurementCoverage {
51    pub implemented_count: usize,
52    pub measured_count: usize,
53    pub unmeasured_count: usize,
54    pub definitions: Vec<DefinitionCoverage>,
55}
56
57impl MeasurementCoverage {
58    /// Returns measured coverage as a percentage of implemented definitions.
59    pub fn measured_percent(&self) -> f64 {
60        if self.implemented_count == 0 {
61            0.0
62        } else {
63            self.measured_count as f64 * 100.0 / self.implemented_count as f64
64        }
65    }
66
67    /// Returns measured counts in official family order.
68    pub fn measured_by_family(&self) -> Vec<(&'static str, usize, usize)> {
69        FunctionGroup::ALL
70            .iter()
71            .map(|group| {
72                let family = group.as_str();
73                let implemented = self
74                    .definitions
75                    .iter()
76                    .filter(|definition| definition.family == family)
77                    .count();
78                let measured = self
79                    .definitions
80                    .iter()
81                    .filter(|definition| definition.family == family && definition.measured)
82                    .count();
83                (family, measured, implemented)
84            })
85            .collect()
86    }
87}
88
89/// Validates and returns the canonical measurement coverage model.
90pub fn measurement_coverage() -> Result<MeasurementCoverage, String> {
91    let mut measured_names = BTreeSet::new();
92    for case in MATRIX {
93        if !measured_names.insert(case.id) {
94            return Err(format!("duplicate representative case {:?}", case.id));
95        }
96        let definition = INDICATOR_CATALOGUE.definition(case.id).ok_or_else(|| {
97            format!(
98                "representative case {:?} is not in the Indicator Catalogue",
99                case.id
100            )
101        })?;
102        if !definition.is_implemented() {
103            return Err(format!(
104                "representative case {:?} is not implemented",
105                case.id
106            ));
107        }
108        if definition.group.as_str() != case.family {
109            return Err(format!(
110                "representative case {:?} has family {:?}, expected {:?}",
111                case.id,
112                case.family,
113                definition.group.as_str()
114            ));
115        }
116    }
117
118    let definitions = INDICATOR_CATALOGUE
119        .implemented_definitions()
120        .map(|definition| DefinitionCoverage {
121            name: definition.name,
122            family: definition.group.as_str(),
123            measured: measured_names.contains(definition.name),
124        })
125        .collect::<Vec<_>>();
126    let measured_count = definitions
127        .iter()
128        .filter(|definition| definition.measured)
129        .count();
130    let implemented_count = definitions.len();
131    Ok(MeasurementCoverage {
132        implemented_count,
133        measured_count,
134        unmeasured_count: implemented_count - measured_count,
135        definitions,
136    })
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn representative_cases_are_an_executable_subset_of_the_catalogue() {
145        let coverage = measurement_coverage().unwrap();
146        assert_eq!(coverage.implemented_count, 161);
147        assert_eq!(coverage.measured_count, MATRIX.len());
148        assert_eq!(coverage.unmeasured_count, 161 - MATRIX.len());
149        assert!(coverage
150            .measured_by_family()
151            .iter()
152            .all(|(_, measured, implemented)| *measured > 0 && measured <= implemented));
153    }
154}