ta-rs icon indicating copy to clipboard operation
ta-rs copied to clipboard

Give to Typical Price his own structure ?

Open tirz opened this issue 3 years ago • 2 comments

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: calling Next on the group run Calc 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...

tirz avatar Nov 16 '20 03:11 tirz

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?

greyblake avatar Nov 19 '20 23:11 greyblake

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.

tirz avatar Nov 21 '20 04:11 tirz