greyblake / ta-rs Goto Github PK
View Code? Open in Web Editor NEWTechnical analysis library for Rust language
License: MIT License
Technical analysis library for Rust language
License: MIT License
I have a hard time understand how to interpret results that next generate before the full period. For exampel AverageTrueRange next will start emitting values at the first update, in my mind it should return an Optional where the first 13 updates would return None. Right now I have to keep track of the number of updates in a counter and wrap the ATR indicator. Am I wrong in my approach or how are you using it in actual systems?
It would be very nice to have a method that would evaluate an indicator, and return it's current value. Current interface, that returns value only when new item is added is extremely clunky.
Define struct MovingAverageConvergenceDivergenceOutput and use it as an output of MACD, instead of tuple.
(see https://docs.rs/ta/0.1.2/ta/indicators/struct.BollingerBandsOutput.html for example)
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:
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.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:
let mut ker = EfficiencyRatio::new(14)?;
println!("1 {}", ker.next(1.69));
println!("2 {}", ker.next(1.69));
println!("3 {}", ker.next(1.70));
println!("4 {}", ker.next(1.70));
println!("5 {}", ker.next(1.69));
Output:
1 1
2 NaN
3 1
4 1
5 0
If you keep passing 1.69, it subsequently outputs NaN.
ad hoc workaround
// https://github.com/greyblake/ta-rs/issues/73
struct FixEfficiencyRatio {
er: EfficiencyRatio,
}
impl FixEfficiencyRatio {
fn new(period: usize) -> ta::errors::Result<Self> {
Ok(Self {
er: EfficiencyRatio::new(period)?,
})
}
}
impl<T: Close> ta::Next<&T> for FixEfficiencyRatio {
type Output = f64;
fn next(&mut self, input: &T) -> f64 {
match self.er.next(input) {
v if v.is_nan() => 1.0,
v => v,
}
}
}
KER implementation could be simplified with VecDeque.
When I compare the ATR values with tradingview and other technical analysis libraries like TA-lib (which uses the default ATR settings), I see that the values don't match at all.
Code:
let mut atr = ATR::new(14).unwrap();
for record in reader.deserialize() {
let (_timestamp, open, high, low, close, volume): (String, f64, f64, f64, f64, f64) = record.unwrap();
let atr_val = atr.next(&dt);
println!("o: {}, h: {}, l: {}", open, high, low, close);
println!("atr: {:?}\n", atr_val);
Output TA-lib (and tradingview):
open high low close volume atr
timestamp
2021-10-04 13:00:00 303.78 304.78 303.28 304.45 4.729795e+05 3.275000
2021-10-04 13:30:00 304.47 306.73 303.88 306.52 7.521677e+05 3.244643
2021-10-04 14:00:00 306.52 309.34 305.63 308.54 1.235306e+06 3.277883
2021-10-04 14:30:00 308.54 309.22 295.05 298.66 2.537776e+06 4.055891
2021-10-04 15:00:00 298.66 299.28 290.72 298.69 4.477394e+06 4.377613
2021-10-04 15:30:00 298.69 302.13 298.32 300.61 9.675177e+05 4.337069
Output ta-rs:
o: 303.78, h: 304.78, l: 303.28, c: 304.45
atr: 3.284237282587478
o: 304.47, h: 306.73, l: 303.88, c: 306.52
atr: 3.226338978242484
o: 306.52, h: 309.34, l: 305.63, c: 308.54
atr: 3.2908271144768166
o: 308.54, h: 309.22, l: 295.05, c: 298.66
atr: 4.741383499213243
o: 298.66, h: 299.28, l: 290.72, c: 298.69
atr: 5.250532365984803
o: 298.69, h: 302.13, l: 298.32, c: 300.61
atr: 5.058461383853496
I think that Bollinger Bands Indicator would be a nice to have feature.
I see implementation on this indicator as:
It would be nice to be able to use rust_decimal.Decimal
and not lose the precision of f64
. I would be willing in principle to contribute this if you think you would accept it.
Requires DM-/DM+, DI-/DI+ first.
I started implementing all of them in my fork as I need them for the project I'm working on.
While they're all there now I haven't yet verified them and/or wrote tests.
I will update this issue and open a PR once that is done.
In the meantime I'm happy for feedback – just opening this issue so others can benefit/don't do work twice.
As volume is the only other variable with price, addition of volume based indicators would be a good feature.
Can we add Money flow Indicator to start with?
It would be great, if you could add the HMA (Hull Moving Average) indicator.
This is the formula in PineScript:
length = input(9, minval=1)
src = input(close, title="Source")
hma = wma(2*wma(src, length/2)-wma(src, length), round(sqrt(length)))
Here is the website from Alan Hull, explaining the indicator. It's a moving average that is smoother than EMA and less lagging.
Hi,
Would it be possible to limit the number of input values? Instead of adding the value to the next iterator, would it be possible to only use for example 201 input values to calculate the Exponential Moving Average? This is because if you enter for example 500 input values into an ema with a period of 200 you get a totally different output as when you enter 201 input values.
need to use https://github.com/paupino/rust-decimal ( etc) or no?
Thanks
... and return Self
instead of Option<Self>
.
I.e. put the bounds checking that currently happens w/o opt-in inside each period
-based indicator's new()
method at the discretion of the crate user.
A while back I forked this repo for a summer project and one of the things I added to my fork was serde support before I got busy with school and work. If I opened a PR for that would it be welcome? In its present state it may be a bit disruptive to the API since if I recall correctly I had to adjust one of the traits.
Use hand written errors.
Pretty much just
trait RollingWindow {
fn length(&self) -> usize;
}
for all indicators. This won't hide the fact that all these indicators are essentially rolling window filters and need to be pre-fed with historical data with length (RollingWindow::length -1). For combined indicators this is of course the max rolling window length of all indicators (-1, of course).
I use this information in order to read the pre-feed for the window e.g. via ordinary rest calls and then turn to websockets based listening and async streaming. The first yielded value is then the current (in time) value for the given indicator.
Hello,
I've been using the RSI indicator provided by ta
.
Using the RSI indicator, I compute some higher level indicators, their form is not important here.
I wanted to verify that I had implemented the indicator correctly, and so I wrote extensive test cases on real data lifted from TradingView.
This is when I noticed that the RSI value computed on TradingView does not match the RSI computed from this library.
I figured out the issues here:
ta
RSI use a different alpha (k
field), as 2 / (period - 1)
, whereas on TV, the RSI uses an EMA with alpha of 1.0 / period
.I am not sure which version of the RSI is the intended "official" RSI,
but in order to make the RSI implementations match I've had to change things inside the ta
library.
If you have any information on this please let me know, I am a bit clueless.
My suggested change is that you provide a function on EMA
and dependent indicators to set the alpha parameter used.
Add a fractals pattern indicator:
The Formulas for Fractals Are:
Bearish Fractal=
High(N)>High(N−2) and
High(N)>High(N−1) and
High(N)>High(N+1) and
High(N)>High(N+2)
Bullish Fractal=
Low(N)<Low(N−2) and
Low(N)<Low(N−1) and
Low(N)<Low(N+1) and
Low(N)<Low(N+2)
where:
N=High/low of the current price bar
N−2=High/low of price bar two periods
to the left of N
N−1=High/low of price bar one period
to the left of N
N+1=High/low of price bar one period
to the right of N
N+2=High/low of price bar two periods
to the right of N
How can I use special inputs like ohlc4 and hlc3, when making use of the indicators?
use ta::indicators::SimpleMovingAverage as SMA;
use ta::DataItem;
use ta::Next;
fn main() {
let mut sma = SMA::new(4).unwrap();
let mut reader = csv::Reader::from_path("./examples/data/AMZN.csv").unwrap();
for record in reader.deserialize() {
let (date, open, high, low, close, volume): (String, f64, f64, f64, f64, f64) =
record.unwrap();
let dt = DataItem::builder()
.open(open)
.high(high)
.low(low)
.close(close)
.volume(volume)
.build()
.unwrap();
let ohlc4 = (open + high + low + close) / 4.0;
let sma_val = sma.next(ohlc4);
println!("{}: {} = {:2.2}", date, ema, sma_val);
}
}
When I try this code snippet I don't get the simple moving average of the ohlc4 (checked ohlc4 and is correct).
Is it possible to plug in more accurate decimal types, such as https://github.com/paupino/rust-decimal ?
Just for example, OnBalanceVolume
uses only close
and volume
. Yet I need to build a complete DataItem
using the builder()
method.
And this runs the validation if
clause in DataItemBuiler::build()
– no matter what.
It would be nice to be able to use the init struct pattern on DataItem
to forego using a DataItemBuilder
and allowing to skip the validation code.
In my case the data I feed into ta
comes from tickers. So validation is not needed/correctness is guaranteed.
All that would be needed is adding a Default
trait and a is_ok()
method to DataItem
and remove the validation code from DataItemBuiler::build()
. That method would always just return a DataItem
. Otherwise the method should be named build_and_validate()
to express the hidden runtime penalty it entails.
As such TaError::DataItemIncomplete
would not be needed any more since we acknowledge that some indicators work just fine with 'incomplete' DataItems
and Data::Item::is_ok()
could just return false
if the check fails. Analogous what is now TaError::DataItemInvalid
.
This suggestion would break the API/existing code of course.
The other option is to just add the Default
trait and is_ok()
methods to DataItem
. This way new code using ta
could use the aforementioned init struct pattern. A call to is_ok()
can be wrapped in compile time flags to make sure it doesn't end up in release
builds.
Does that make sense?
See subject.
I started doing this here. I only added tests for SMA and EMA. SMA works, i.e. the SMA of ta-rs
matches the SMA output of TA Lib
1:1. But for EMA it fails.
There are two options: I am doing something wrong or the EMA implementation in ta-rs
has a bug. I'm just opening this issue here to get some opinions.
The code I copied for data.rs is from test_data.c.
The actual tests are vastly simplified from the ones in TA Lib
. I.e. I only run the iterator for the resp. indicator n times and compare the result with the expected one. See here.
I tried to make a DataItem vector from a Candle vector. but when I do this:
DataItem::builder()
.high(i.high.to_f64().unwrap())
.low(i.low.to_f64().unwrap())
.close(i.close.to_f64().unwrap())
.volume(i.volume.to_f64().unwrap())
.build().unwrap()
(i
is a Candle in a for loop) it throws a DataItemIncomplete error.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.