cxmr_ta/
indicators.rs

1//! Technical analysis operations with parameter descriptors.
2
3use rand::Rng;
4
5use cxmr_ta_core::indicators;
6
7use super::{impls, traits, Error, Operation};
8
9/// Technical analysis operation parameter type alias.
10pub type Param = u32;
11
12/// Technical analysis operation parameter vector type alias.
13pub type Params = Vec<Param>;
14
15/// Technical analysis operation parameter descriptor.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum ParamDescriptor {
18    /// Step parameter.
19    Step {
20        /// Minimum parameter amount.
21        min: Param,
22        /// Maximum parameter amount.
23        max: Param,
24        /// Parameter step amount.
25        step: Param,
26    },
27    /// Power parameter.
28    Power {
29        /// Minimum parameter amount.
30        min: Param,
31        /// Maximum parameter amount.
32        max: Param,
33        /// Parameter step power.
34        power: Param,
35    },
36}
37
38impl ParamDescriptor {
39    /// Generates random parameter.
40    pub fn rand<R: Rng>(&self, r: &mut R) -> Param {
41        match self {
42            ParamDescriptor::Step { min, max, step } => {
43                let rand = r.gen_range(min, max);
44                rand * step
45            }
46            ParamDescriptor::Power { min, max, power } => {
47                let rand = r.gen_range(min, max);
48                rand.pow(*power)
49            }
50        }
51    }
52}
53
54#[macro_export]
55macro_rules! param {
56    ( $min:expr, $max:expr, $step:expr ) => {
57        ParamDescriptor::Step {
58            min: $min,
59            max: $max,
60            step: $step,
61        }
62    };
63}
64
65#[macro_export]
66macro_rules! pow_param {
67    ( $min:expr, $max:expr, $power:expr ) => {
68        ParamDescriptor::Power {
69            min: $min,
70            max: $max,
71            power: $power,
72        }
73    };
74}
75
76/// Indicator mode marker.
77#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
78pub enum Marker {
79    /// Rate indicator.
80    Rate,
81    /// Frequency indicator.
82    Freq,
83}
84
85impl From<&Indicator> for Marker {
86    fn from(indicator: &Indicator) -> Marker {
87        match indicator {
88            Indicator::FastStochastic
89            | Indicator::SlowStochastic
90            | Indicator::EfficiencyRatio
91            | Indicator::RelativeStrengthIndex
92            | Indicator::MoneyFlowIndex
93            | Indicator::RateOfChange
94            | Indicator::AverageTrueRange
95            | Indicator::TrueRange
96            | Indicator::Spread => Marker::Freq,
97            _ => Marker::Rate,
98        }
99    }
100}
101
102/// Operation is technical analysis or orderbook method.
103#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
104pub enum Indicator {
105    /// Average true range (ATR).
106    AverageTrueRange,
107    /// Kaufman's Efficiency Ratio (ER).
108    EfficiencyRatio,
109    /// An exponential moving average (EMA), also known as an exponentially weighted moving average (EWMA).
110    ExponentialMovingAverage,
111    /// Fast stochastic oscillator.
112    FastStochastic,
113    /// Returns the highest value in a given time frame.
114    Maximum,
115    /// Returns the lowest value in a given time frame.
116    Minimum,
117    /// Money Flow Index (MFI).
118    MoneyFlowIndex,
119    /// On Balance Volume (OBV).
120    OnBalanceVolume,
121    /// Rate of Change (ROC)
122    RateOfChange,
123    /// The relative strength index (RSI).
124    RelativeStrengthIndex,
125    /// Simple moving average (SMA).
126    SimpleMovingAverage,
127    /// Slow stochastic oscillator.
128    SlowStochastic,
129    /// The range of a day's trading is simply high - low. The true range extends it to yesterday's closing price if it was outside of today's range.
130    TrueRange,
131    /// Spread in the orderbook.
132    Spread,
133    /// Moving average converge divergence (MACD).
134    MovingAverageConvergenceDivergence,
135    /// Closing price.
136    ClosePrice,
137    /// Ask rate in the orderbook.
138    AskRate,
139    /// Bid rate in the orderbook.
140    BidRate,
141    /// Market entry rate.
142    EntryRate,
143    /// Market exit rate.
144    ExitRate,
145}
146
147/// Boxed indicator implementation.
148pub type BoxIndicator<S> = Box<dyn traits::Next<S>>;
149
150#[macro_export]
151macro_rules! construct_indicator {
152    ( $s:ident, $p:ident, $t:ident, $( $x:tt ), * ) => {
153        {
154            let params = $p.ok_or_else(|| Error::ExpectedParams($s.clone()))?;
155            Box::new(indicators::$t::new(
156                $(
157                    $s.get_param(&params, $x)?,
158                )*
159            )?) as BoxIndicator<S>
160        }
161    };
162}
163
164#[macro_export]
165macro_rules! construct_indicator_local {
166    ( $s:ident, $p:ident, $t:ident, $i:expr, $( $x:tt ), * ) => {
167        {
168            let params = $p.ok_or_else(|| Error::ExpectedParams($s.clone()))?;
169            Box::new($i(
170                $(
171                    $s.get_param(&params, $x)? as usize,
172                )*
173            )) as BoxIndicator<S>
174        }
175    };
176}
177
178#[macro_export]
179macro_rules! construct_indicator_wrapper {
180    ( $s:ident, $p:ident, $t:ident, $i:expr, $( $x:tt ), * ) => {
181        {
182            let params = $p.ok_or_else(|| Error::ExpectedParams($s.clone()))?;
183            Box::new($i(indicators::$t::new(
184                $(
185                    $s.get_param(&params, $x)?,
186                )*
187            )?)) as BoxIndicator<S>
188        }
189    };
190}
191
192#[macro_export]
193macro_rules! static_params {
194    ( $( $x:expr ), *  ) => {
195        {
196            static LIST: &'static [ParamDescriptor] = &[$($x,)*];
197            LIST
198        }
199    };
200}
201
202impl Indicator {
203    /// Returns amount of expected parameters.
204    pub fn parameters(&self) -> usize {
205        match self {
206            Indicator::ExponentialMovingAverage
207            | Indicator::SimpleMovingAverage
208            | Indicator::RelativeStrengthIndex
209            | Indicator::FastStochastic
210            | Indicator::Maximum
211            | Indicator::Minimum
212            | Indicator::MoneyFlowIndex
213            | Indicator::AverageTrueRange
214            | Indicator::EfficiencyRatio
215            | Indicator::ClosePrice
216            | Indicator::Spread
217            | Indicator::AskRate
218            | Indicator::BidRate
219            | Indicator::EntryRate
220            | Indicator::ExitRate
221            | Indicator::RateOfChange => 1,
222            Indicator::SlowStochastic => 2,
223            Indicator::TrueRange | Indicator::OnBalanceVolume => 0,
224            Indicator::MovingAverageConvergenceDivergence => 3,
225        }
226    }
227
228    /// Returns descriptors of expected parameters.
229    /// It can be `None` if doesn't expect any arguments
230    pub fn descriptors(&self) -> Option<&'static [ParamDescriptor]> {
231        match self {
232            Indicator::MovingAverageConvergenceDivergence => Some(static_params!(
233                param!(1, 4, 10),
234                param!(1, 4, 20),
235                param!(1, 4, 3)
236            )),
237            Indicator::ExponentialMovingAverage => Some(static_params!(pow_param!(2, 8, 3))),
238            Indicator::SimpleMovingAverage => Some(static_params!(pow_param!(2, 8, 3))),
239            Indicator::RelativeStrengthIndex => Some(static_params!(pow_param!(2, 8, 3))),
240            Indicator::FastStochastic => Some(static_params!(pow_param!(2, 8, 3))),
241            Indicator::SlowStochastic => {
242                Some(static_params!(pow_param!(2, 8, 3), pow_param!(2, 8, 3)))
243            }
244            Indicator::Maximum => Some(static_params!(pow_param!(2, 8, 3))),
245            Indicator::Minimum => Some(static_params!(pow_param!(2, 8, 3))),
246            Indicator::MoneyFlowIndex => Some(static_params!(pow_param!(2, 8, 3))),
247            Indicator::EfficiencyRatio => Some(static_params!(pow_param!(2, 8, 3))),
248            Indicator::AverageTrueRange => Some(static_params!(pow_param!(2, 8, 3))),
249            Indicator::RateOfChange => Some(static_params!(pow_param!(2, 8, 3))),
250            Indicator::Spread => Some(static_params!(param!(0, 2, 1))),
251            Indicator::AskRate => Some(static_params!(param!(0, 2, 1))),
252            Indicator::BidRate => Some(static_params!(param!(0, 2, 1))),
253            Indicator::EntryRate => Some(static_params!(param!(0, 2, 1))),
254            Indicator::ExitRate => Some(static_params!(param!(0, 2, 1))),
255            Indicator::TrueRange | Indicator::OnBalanceVolume | Indicator::ClosePrice => None,
256        }
257    }
258
259    /// Constructs technical analysis operator using parameters.
260    /// Returns error only if amount of parameters is wrong.
261    pub fn construct<S: traits::State>(
262        &self,
263        params: Option<&Params>,
264    ) -> Result<BoxIndicator<S>, Error> {
265        match self {
266            Indicator::ExponentialMovingAverage => Ok(construct_indicator!(
267                self,
268                params,
269                ExponentialMovingAverage,
270                0
271            )),
272            Indicator::SimpleMovingAverage => {
273                Ok(construct_indicator!(self, params, SimpleMovingAverage, 0))
274            }
275            Indicator::RelativeStrengthIndex => {
276                Ok(construct_indicator!(self, params, RelativeStrengthIndex, 0))
277            }
278            Indicator::FastStochastic => Ok(construct_indicator!(self, params, FastStochastic, 0)),
279            Indicator::SlowStochastic => {
280                Ok(construct_indicator!(self, params, SlowStochastic, 0, 1))
281            }
282            Indicator::Maximum => Ok(construct_indicator!(self, params, Maximum, 0)),
283            Indicator::Minimum => Ok(construct_indicator!(self, params, Minimum, 0)),
284            Indicator::MoneyFlowIndex => Ok(construct_indicator!(self, params, MoneyFlowIndex, 0)),
285            Indicator::AverageTrueRange => Ok(construct_indicator_wrapper!(
286                self,
287                params,
288                AverageTrueRange,
289                impls::AverageTrueRange,
290                0
291            )),
292            Indicator::EfficiencyRatio => Ok(construct_indicator_wrapper!(
293                self,
294                params,
295                EfficiencyRatio,
296                impls::EfficiencyRatio,
297                0
298            )),
299            Indicator::RateOfChange => Ok(construct_indicator!(self, params, EfficiencyRatio, 0)),
300            Indicator::TrueRange => {
301                Ok(Box::new(impls::TrueRange(indicators::TrueRange::new())) as BoxIndicator<S>)
302            }
303            Indicator::OnBalanceVolume => {
304                Ok(Box::new(indicators::OnBalanceVolume::new()) as BoxIndicator<S>)
305            }
306            Indicator::ClosePrice => Ok(Box::new(impls::ClosePrice) as BoxIndicator<S>),
307            Indicator::Spread => Ok(construct_indicator_local!(
308                self,
309                params,
310                Spread,
311                impls::Spread,
312                0
313            )),
314            Indicator::AskRate => Ok(construct_indicator_local!(
315                self,
316                params,
317                AskRate,
318                impls::AskRate,
319                0
320            )),
321            Indicator::BidRate => Ok(construct_indicator_local!(
322                self,
323                params,
324                BidRate,
325                impls::BidRate,
326                0
327            )),
328            Indicator::EntryRate => Ok(construct_indicator_local!(
329                self,
330                params,
331                EntryRate,
332                impls::EntryRate,
333                0
334            )),
335            Indicator::ExitRate => Ok(construct_indicator_local!(
336                self,
337                params,
338                ExitRate,
339                impls::ExitRate,
340                0
341            )),
342            Indicator::MovingAverageConvergenceDivergence => {
343                Err(Error::IndicatorNotImplemented(self.clone()))
344            }
345        }
346    }
347
348    /// Constructs indicator operation with params.
349    pub fn into_op(self) -> Result<Operation, Error> {
350        Operation::new(self, None)
351    }
352
353    /// Constructs indicator operation with params.
354    pub fn with_params(self, params: Params) -> Result<Operation, Error> {
355        Operation::new(self, Some(params))
356    }
357
358    fn get_param(&self, params: &Params, index: usize) -> Result<Param, Error> {
359        params
360            .get(index)
361            .map(|v| v.clone())
362            .ok_or_else(|| Error::ExpectedParameter(self.clone(), index))
363    }
364}
365
366impl std::fmt::Debug for Indicator {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        match self {
369            Indicator::AverageTrueRange => write!(f, "ATR"),
370            Indicator::EfficiencyRatio => write!(f, "ER"),
371            Indicator::ExponentialMovingAverage => write!(f, "EMA"),
372            Indicator::FastStochastic => write!(f, "FST"),
373            Indicator::Maximum => write!(f, "MAX"),
374            Indicator::Minimum => write!(f, "MIN"),
375            Indicator::MoneyFlowIndex => write!(f, "MFI"),
376            Indicator::OnBalanceVolume => write!(f, "OBV"),
377            Indicator::RateOfChange => write!(f, "ROC"),
378            Indicator::RelativeStrengthIndex => write!(f, "RSI"),
379            Indicator::SimpleMovingAverage => write!(f, "SMA"),
380            Indicator::SlowStochastic => write!(f, "SST"),
381            Indicator::TrueRange => write!(f, "TR"),
382            Indicator::Spread => write!(f, "Spread"),
383            Indicator::MovingAverageConvergenceDivergence => write!(f, "MACD"),
384            Indicator::AskRate => write!(f, "Ask"),
385            Indicator::BidRate => write!(f, "Bid"),
386            Indicator::EntryRate => write!(f, "Entry"),
387            Indicator::ExitRate => write!(f, "Exit"),
388            Indicator::ClosePrice => write!(f, "Close"),
389        }
390    }
391}
392
393impl std::str::FromStr for Indicator {
394    type Err = ();
395    fn from_str(s: &str) -> Result<Self, Self::Err> {
396        match s {
397            "AverageTrueRange" => Ok(Indicator::AverageTrueRange),
398            "EfficiencyRatio" => Ok(Indicator::EfficiencyRatio),
399            "ExponentialMovingAverage" => Ok(Indicator::ExponentialMovingAverage),
400            "FastStochastic" => Ok(Indicator::FastStochastic),
401            "Maximum" => Ok(Indicator::Maximum),
402            "Minimum" => Ok(Indicator::Minimum),
403            "MoneyFlowIndex" => Ok(Indicator::MoneyFlowIndex),
404            "OnBalanceVolume" => Ok(Indicator::OnBalanceVolume),
405            "RateOfChange" => Ok(Indicator::RateOfChange),
406            "RelativeStrengthIndex" => Ok(Indicator::RelativeStrengthIndex),
407            "SimpleMovingAverage" => Ok(Indicator::SimpleMovingAverage),
408            "SlowStochastic" => Ok(Indicator::SlowStochastic),
409            "TrueRange" => Ok(Indicator::TrueRange),
410            "Spread" => Ok(Indicator::Spread),
411            "MovingAverageConvergenceDivergence" => {
412                Ok(Indicator::MovingAverageConvergenceDivergence)
413            }
414            "AskRate" => Ok(Indicator::AskRate),
415            "BidRate" => Ok(Indicator::BidRate),
416            "EntryRate" => Ok(Indicator::EntryRate),
417            "ExitRate" => Ok(Indicator::ExitRate),
418            "ClosePrice" => Ok(Indicator::ClosePrice),
419            _ => Err(()),
420        }
421    }
422}