ta-rs
ta-rs copied to clipboard
Give to Typical Price his own structure ?
More and more indicators starts to use the typical price.
Does it have any sense to create a structure TypicalPrice
which will implement Next
?
We will (for example) be able to change:
impl<T: Close + High + Low> Next<&T> for CommodityChannelIndex {
type Output = f64;
fn next(&mut self, input: &T) -> Self::Output {
let tp = (input.close() + input.high() + input.low()) / 3.0; // <--- before
let sma = self.sma.next(tp);
let mad = self.mad.next(input);
if mad == 0.0 {
return 0.0;
}
(tp - sma) / (mad * 0.015)
}
}
to:
impl<T: Close + High + Low> Next<&T> for CommodityChannelIndex {
type Output = f64;
fn next(&mut self, input: &T) -> Self::Output {
let tp = self.tp.next(input); // <--- after
let sma = self.sma.next(tp);
let mad = self.mad.next(input);
if mad == 0.0 {
return 0.0;
}
(tp - sma) / (mad * 0.015)
}
}
Pros:
- The crate provide a new indicator.
- We exclude even more logic from our current indicators.
- Let's imagine a future where our indicators can share a stack, so we do not allocate one
deque: Box<[f64]>
per indicator but only ones with the length of the longest period. Putting TP inside an indicator can offer even more optimization for our "group of indicators" like calculating it only ones and sharing his value across all the group. Idea: callingNext
on the group runCalc
for each indicator.Calc
takes all the parameters and only apply the formula of the indicator (so TP, MAD(20), MAD(x), etc, cannot be processed twice):
fn calc(input: &CommodityChannelIndexInput) -> Self::Output {
(input.tp - input.sma) / (input.mad * 0.015)
}
Cons:
- This is a one-liner...
If I got your idea, in the example you provide TypicalPrice
is just a stateless indicator, which is in a nutshell a function.
Quick googling does not prove that end users would need TypicalPrice
indicator.
If TypicalPrice
is used in multiple indicators, I would probably go rather for simple helper function (I need to rethink this statement when I am fresh).
I think it got only 20% from the 3rd pros. I like the idea of optimization and doing less calculations, if it's possible. However I got absolutely confused with the code snippet:
fn calc(input: &CommodityChannelIndexInput) -> Self::Output {
(input.tp - input.sma) / (input.mad * 0.015)
}
Here you have some kind of special Input for CCI, that brings all tp, sma and mad already precalculated? Where would they come from?
The idea is to create a new structure which can take the ownership of some indicators.
Here is a dirty example (cannot work like that) :
group.add(keltner_channel);
group.add(percentage_price_oscillator);
group.next(&dt);
In this example, both Keltner Channel and Percentage Price Oscillator use an Exponential Moving Average so if both indicators have the same period, it is safe to only calculated one EMA.
The group take care of the order of execution so when we will call group.next
, it will call EMA first, then KC and PPO.
We just need to map the dependencies.
The 3rd pros was about to find a generic way to map the dependencies by putting TypicalPrice
inside his own structure so it can be mapped exactly like EMA will be mapped for KC and PPO.
We will then be able to add a trait Calc
similar to Next
like :
pub trait Next<T> {
type Output;
fn next(&mut self, input: T) -> Self::Output; // <= update the indicator if needed, then call `Calc`
}
pub trait Calc {
type Input;
type Output;
fn calc(input: Self::Input) -> Self::Output; // <= stateless, only apply the formula
}
I also mention another advantage of the group but it will be harder to implement and maybe less clean.
We may move the stack (deque
) to the group for saving memories :
- we just need a stack as long as the longest period
- the indicators may have a different index inside this stack (depending of the period)
But we may need n stacks depending of the indicators :
- MFI (storing the
raw_money_flow
values) - ER (just the closing prices history)
- RoC (just the closing prices history)
- ect
Here, ER and RoC can share their stack but not MFI - even if MFI can share his stack with others MFIs running on a different period.