amv-dev / yata

Yet Another Technical Analysis library [for Rust]
Apache License 2.0
321 stars 49 forks source link

BollingerBands results compared to ta-rs #25

Closed mithriljs-cn closed 2 years ago

mithriljs-cn commented 2 years ago

Try to compare yata with ta-rs, the BollingerBands results shows differently:

    use yata::indicators::{BollingerBands};
    use yata::prelude::*;

    use ta::indicators::{BollingerBands as TaBollingerBands, BollingerBandsOutput};
    use ta::Next;

    // ta
    let mut bb = TaBollingerBands::new(3, 2.0_f64).unwrap();
    println!("[ta-bb] \n{:?}\n{:?}\n{:?}\n{:?}", &bb.next(1.0), &bb.next(2.0), &bb.next(4.0), &bb.next(8.0));

    // yata
    let mut boll = BollingerBands::default();
    boll.set("avg_size", String::from("3")).unwrap();
    let mut bb = boll.init(&Candle { close: 1.0, ..Candle::default() }).unwrap();
    println!("[yata-bb] \n{:?}\n{:?}\n{:?}",
        &bb.next(&Candle { close: 2.0, ..Candle::default() }),
        &bb.next(&Candle { close: 4.0, ..Candle::default() }),
        &bb.next(&Candle { close: 8.0, ..Candle::default() }),
    );

The outputs:

[ta-bb] 
BollingerBandsOutput { average: 1.0, upper: 1.0, lower: 1.0 }
BollingerBandsOutput { average: 1.5, upper: 2.5, lower: 0.5 }
BollingerBandsOutput { average: 2.3333333333333335, upper: 4.8277715911826276, lower: -0.1611049245159606 }
BollingerBandsOutput { average: 4.666666666666667, upper: 9.655543182365255, lower: -0.3222098490319212 }
[yata-bb] 
S: [+0.58], V: [ 2.4880,  1.3333,  0.1786]
S: [+0.55], V: [ 5.3884,  2.3333, -0.7217]
S: [+0.55], V: [10.7768,  4.6667, -1.4434]

The question is why it shows different results?

amv-dev commented 2 years ago

There are two independent reasons for this. First of all about average value and first two results. When you initialize an indicator instance, yata creates it as if all previous input values were equal to initial value. So in this situation full history is like [... 1.0, 1.0, 1.0, 1.0, 2.0, 4.0, 8.0] On the other hand ta-rs initializes it with None. So in this situation full history is like [1.0, 2.0, 4.0, 8.0]. In both situations first and second results can't be used in real calculation because indicator is still not fully filled with values. Next average values are the same, so there is no mistakes. In other words, in both situations first two results are invalid and in both situations next results are the same.

Situation with upper and lower bounds is a little bit more complicated. Yata uses biased standard deviation (or starndard deviation with corrected sample) which is default, for example, if you're using TradingView. On the other hand ta-rs uses unbiased standard deviation. In other words, yata's bounds are always a little bit wider than ta-rs'es. There is no mistakes. Both results are correct. It only depends on interpretation.

mithriljs-cn commented 2 years ago

Thank you for the quick reply, here have 2 things want to confirm:

  1. So in this situation full history is like [... 1.0, 1.0, 1.0, 1.0, 2.0, 4.0, 8.0], but avg_size already set to 3, how to restrict the initial value only to 1.0 so that the data looked like the same as [1.0, 2.0, 4.0, 8.0]? (like how to config yata correctly etc., sorry there're too little docs to reference)
  2. Regardless of the upper and lower results, how to make average value the same with the ta?
amv-dev commented 2 years ago

avg_size is how many values we take from the end of historical data. Almost every indicator holds a circular buffer inside which holds last input values. When you call init method, you just initialized this buffer with initial value. In your case it is 1.0. Now watch step-by-step for the buffer:

action -> buffer state -> average value
* init(3, &1.0) -> `[1.0, 1.0, 1.0]` -> average value is 1.0
* next(1.0) -> `[1.0, 1.0, 1.0]` -> average value is still 1.0
* next(2.0) -> `[1.0, 1.0, 2.0]` -> average value is (1+1+2)/3 = 4/3 = 1.333...
* next(4.0) -> `[1.0, 2.0, 4.0]` -> average value is (1+2+4)/3 = 7/4 = 2.333...
* next(8.0) -> `[2.0, 4.0, 8.0]` -> average value is (2+4+8)/3 = 14/3 = 4.666...

On the other hand, when using ta-rs circular buffer has a little bit different implementation:

* init -> `[]` -> no average value at all
* next(1.0) -> `[1.0]` -> average value is 1.0/1 = 1.0
* next(2.0) -> `[1.0, 2.0]` -> average value is (1+2)/2=1.5
* next(4.0) -> `[1.0, 2.0, 4.0]` -> average value is (1+2+4)/3 = 7/4 = 2.333... <- at this step results become the same, because circular buffer is filled with actual historical values
* next(8.0) -> `[2.0, 4.0, 8.0]` -> average value is (2+4+8)/3 = 14/3 = 4.666...

So average values differ only for the very first avg_size-1 results.