Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give to Typical Price his own structure ? #39

Open
tirz opened this issue Nov 16, 2020 · 2 comments
Open

Give to Typical Price his own structure ? #39

tirz opened this issue Nov 16, 2020 · 2 comments

Comments

@tirz
Copy link
Contributor

tirz commented Nov 16, 2020

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...
@greyblake
Copy link
Owner

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?

@tirz
Copy link
Contributor Author

tirz commented Nov 21, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants