Skip to main content

ta_benchmarks/
catalogue_execution.rs

1//! Static dispatch for Catalogue Matrix case execution.
2//!
3//! The benchmark selects a case once, before constructing a measured operation. Implementors
4//! remain concrete, so the compiler can monomorphize the selected path and timed closures need
5//! neither dynamic dispatch nor an extra allocation.
6
7use crate::catalogue_cases::{CaseKind, CaseSpec};
8
9/// A Catalogue Matrix case selected independently of an execution backend or mode.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct CaseAdapter {
12    kind: CaseKind,
13}
14
15/// Metadata implemented by the concrete Rust, C, and Python case adapters.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct AdapterContract {
18    pub parameters: &'static str,
19    pub output_kind: &'static str,
20    pub output_arity: usize,
21}
22
23impl CaseAdapter {
24    #[must_use]
25    pub const fn new(kind: CaseKind) -> Self {
26        Self { kind }
27    }
28
29    #[must_use]
30    pub const fn kind(self) -> CaseKind {
31        self.kind
32    }
33
34    /// Returns the metadata contract implemented by this case's concrete adapters.
35    #[must_use]
36    pub const fn contract(self) -> AdapterContract {
37        match self.kind {
38            CaseKind::Sma => AdapterContract {
39                parameters: "timeperiod=14",
40                output_kind: "float",
41                output_arity: 1,
42            },
43            CaseKind::Bbands => AdapterContract {
44                parameters: "timeperiod=20;nbdevup=2;nbdevdn=2;matype=SMA",
45                output_kind: "float",
46                output_arity: 3,
47            },
48            CaseKind::Rsi => AdapterContract {
49                parameters: "timeperiod=14",
50                output_kind: "float",
51                output_arity: 1,
52            },
53            CaseKind::Macd => AdapterContract {
54                parameters: "fastperiod=12;slowperiod=26;signalperiod=9",
55                output_kind: "float",
56                output_arity: 3,
57            },
58            CaseKind::Atr | CaseKind::Adx | CaseKind::LinearReg => AdapterContract {
59                parameters: "timeperiod=14",
60                output_kind: "float",
61                output_arity: 1,
62            },
63            CaseKind::HtDcPhase
64            | CaseKind::TypPrice
65            | CaseKind::Obv
66            | CaseKind::Sin
67            | CaseKind::Add => AdapterContract {
68                parameters: "none",
69                output_kind: "float",
70                output_arity: 1,
71            },
72            CaseKind::CdlDoji | CaseKind::CdlEngulfing | CaseKind::Cdl3WhiteSoldiers => {
73                AdapterContract {
74                    parameters: "candle_settings=TA-Lib defaults",
75                    output_kind: "integer",
76                    output_arity: 1,
77                }
78            }
79        }
80    }
81
82    /// Rejects manifest metadata that no longer describes the executable adapters.
83    pub fn validate_spec(self, spec: &CaseSpec) -> Result<(), String> {
84        let contract = self.contract();
85        if (spec.parameters, spec.output_kind, spec.output_arity)
86            != (
87                contract.parameters,
88                contract.output_kind,
89                contract.output_arity,
90            )
91        {
92            return Err(format!(
93                "case {} manifest metadata differs from executable adapter contract",
94                spec.id
95            ));
96        }
97        Ok(())
98    }
99
100    /// Dispatches to a concrete backend operation.
101    ///
102    /// `O` is statically known at each call site. In particular, this method does not erase a
103    /// timed closure behind another trait object.
104    pub fn execute<C, O>(self, operations: &mut O, context: C) -> O::Output
105    where
106        O: CaseOperations<C>,
107    {
108        match self.kind {
109            CaseKind::Sma => operations.sma(context),
110            CaseKind::Bbands => operations.bbands(context),
111            CaseKind::Rsi => operations.rsi(context),
112            CaseKind::Macd => operations.macd(context),
113            CaseKind::Atr => operations.atr(context),
114            CaseKind::Adx => operations.adx(context),
115            CaseKind::HtDcPhase => operations.ht_dc_phase(context),
116            CaseKind::CdlDoji => operations.cdl_doji(context),
117            CaseKind::CdlEngulfing => operations.cdl_engulfing(context),
118            CaseKind::Cdl3WhiteSoldiers => operations.cdl_3_white_soldiers(context),
119            CaseKind::LinearReg => operations.linear_reg(context),
120            CaseKind::TypPrice => operations.typ_price(context),
121            CaseKind::Obv => operations.obv(context),
122            CaseKind::Sin => operations.sin(context),
123            CaseKind::Add => operations.add(context),
124        }
125    }
126}
127
128/// Concrete operations supported by every Catalogue execution backend.
129///
130/// Backends use `Context` to carry the fixture, execution mode, or preallocated C buffers. Keeping
131/// it generic lets verification and timed-operation construction share the same case adapter while
132/// returning different concrete result types.
133pub trait CaseOperations<Context> {
134    type Output;
135
136    fn sma(&mut self, context: Context) -> Self::Output;
137    fn bbands(&mut self, context: Context) -> Self::Output;
138    fn rsi(&mut self, context: Context) -> Self::Output;
139    fn macd(&mut self, context: Context) -> Self::Output;
140    fn atr(&mut self, context: Context) -> Self::Output;
141    fn adx(&mut self, context: Context) -> Self::Output;
142    fn ht_dc_phase(&mut self, context: Context) -> Self::Output;
143    fn cdl_doji(&mut self, context: Context) -> Self::Output;
144    fn cdl_engulfing(&mut self, context: Context) -> Self::Output;
145    fn cdl_3_white_soldiers(&mut self, context: Context) -> Self::Output;
146    fn linear_reg(&mut self, context: Context) -> Self::Output;
147    fn typ_price(&mut self, context: Context) -> Self::Output;
148    fn obv(&mut self, context: Context) -> Self::Output;
149    fn sin(&mut self, context: Context) -> Self::Output;
150    fn add(&mut self, context: Context) -> Self::Output;
151}