YATA (Yet Another Technical Analysis library)

repository·master·Indexed 19 days ago

https://github.com/amv-dev/yata

A Rust library providing a comprehensive suite of technical analysis methods and indicators for financial time-series data. Version 0.7.0 includes a wide variety of moving averages (SMA, EMA, HMA, KAMA, etc.), technical indicators (MACD, RSI, Bollinger Bands, Ichimoku Cloud, etc.), and time-series conversion tools like Heikin Ashi and Renko. It features a Method trait for custom indicators, an OHLCV trait for candlestick data, and a Window circular buffer for managing data series.

Tokens
13K
Snippets
48
Records
55
Agent score
63%

What's inside yata

  1. Enable unsafe_performance for higher performance

    master

    By default, YATA contains no unsafe code. You can optionally enable the unsafe_performance feature via Cargo.toml or the --feature flag in your CLI. This enables unsafe code blocks (primarily unsafe vector element access) which can increase performance for some methods by approximately 5-10%.

    # In Cargo.toml
    [dependencies]
    yata = { version = "0.7.0", features = ["unsafe_performance"] }
  2. Use YATA methods and indicators

    master

    YATA (Yet Another Technical Analysis library) provides a wide range of technical analysis tools for financial time series. It is organized into two main categories:

    1. Methods: Mathematical operations applied to data, such as various Moving Averages (SMA, EMA, WMA, etc.), time series conversions (Heikin Ashi, Renko), and statistical measures (Standard Deviation, Momentum).
    2. Indicators: Complex technical tools built using one or more methods, such as MACD, RSI, Bollinger Bands, and Ichimoku Cloud.

    To use the library, you typically initialize a method or indicator and then feed it data (usually Candle or OHLCV objects) using the .next() method.

    use yata::prelude::*;
    use yata::methods::EMA;
    
    // EMA of length=3
    let mut ema = EMA::new(3, &3.0).unwrap();
    
    ema.next(&3.0);
    ema.next(&6.0);
    
    assert_eq!(ema.next(&9.0), 6.75);
    assert_eq!(ema.next(&12.0), 9.375);
  3. What is a Window and how to use it

    master

    A Window<T> is a circular buffer used for managing data series, typically for technical indicators. It maintains a fixed size and allows you to push new values while retrieving the oldest value that was overwritten.

    Key behaviors:

    • Pushing: When you push a new value, the Window returns the oldest value that was previously stored at that position.
    • Ordering: The Window provides two ways to iterate: iter() (from newest to oldest) and iter_rev() (from oldest to newest).
    • Indexing: You can access elements using index notation w[i], where 0 is the newest element and size - 1 is the oldest.
    use yata::core::Window;
    
    let mut w = Window::new(3, 1); // [1, 1, 1]
    
    assert_eq!(w.push(2), 1); // [1, 1, 2]
    assert_eq!(w.push(3), 1); // [1, 2, 3]
    assert_eq!(w.push(4), 1); // [2, 3, 4]
    assert_eq!(w.push(5), 2); // [3, 4, 5]
  4. Configure YATA features

    master

    YATA provides several feature flags to customize its behavior and data types:

    • serde: Enables serde crate support for serialization/deserialization.
    • period_type_u16: Sets PeriodType to u16.
    • period_type_u32: Sets PeriodType to u32.
    • period_type_u64: Sets PeriodType to u64.
    • value_type_f32: Sets ValueType to f32.
    • unsafe_performance: Enables optional unsafe code blocks for potential performance gains.
    # Example configuration in Cargo.toml
    [dependencies]
    yata = {
        version = "0.7.0",
        features = ["serde", "period_type_u32", "value_type_f32"]
    }
  5. Implement the OHLCV trait for price data

    master

    The OHLCV trait is the core interface for inputting Open-High-Low-Close-Volume (candlestick) timeseries data into YATA indicators. You can implement this trait for your own custom data structures, or use the built-in implementations for tuples and arrays of 5 float values.

    Commonly used implementations include:

    • (ValueType, ValueType, ValueType, ValueType, ValueType) (a 5-element tuple)
    • [ValueType; 5] (a 5-element array)

    Methods available on any OHLCV type include basic accessors (open(), high(), low(), close(), volume()) and several derived price calculations.

    use yata::prelude::OHLCV;
    //         open high low  close, volume
    let row = (2.0, 5.0, 1.0,  4.0,   10.0 );
    assert_eq!(row.open(), row.0);
    assert_eq!(row.high(), row.1);
    assert_eq!(row.low(), row.2);
    assert_eq!(row.close(), row.3);
    assert_eq!(row.volume(), row.4);
  6. How Renko chart construction works

    master

    Renko charts are non-time-based charts that only update when price moves a certain amount. In yata, this is implemented using three distinct components:

    1. Renko method: The core engine. When you call Method::next on a Renko instance, it returns a RenkoOutput.
    2. RenkoOutput: The output of the Renko method. It implements Iterator, allowing you to iterate over the RenkoBlocks generated during that specific step. A single step might produce zero, one, or multiple blocks.
    3. RenkoBlock: The final unit of the chart, representing a single 'brick'. It contains open, close, and volume values.

    Workflow:

    1. Initialize Renko with a relative size and a Source (e.g., Source::Close).
    2. Call renko.next(&input_ohlcv) to get a RenkoOutput.
    3. Iterate over the RenkoOutput to consume the resulting RenkoBlocks.
    use yata::prelude::*;
    use yata::core::Source;
    use yata::methods::Renko;
    
    // 1. Initialize
    let first_candle = Candle { close: 100.0, ..Candle::default() };
    let mut renko = Renko::new((0.01, Source::Close), &first_candle).unwrap();
    
    // 2. Feed data and 3. Iterate over output
    let next_candle = Candle { close: 105.0, ..Candle::default() };
    let output = renko.next(&next_candle);
    for block in output {
        println!("Block Open: {}, Close: {}", block.open, block.close);
    }
  7. Enable unsafe_performance for higher speed

    master
    By default, YATA contains no unsafe code. However, you can enable the unsafe_performance feature in your Cargo.toml or via the --features flag in your CLI. This enables unsafe code blocks (primarily for faster vector element access) which can increase performance by approximately 5-10% for certain methods.
  8. Apply a Method to a Sequence

    master

    Once you have a Method instance, you can apply it to data in several ways depending on whether you want to preserve the original data, create a new vector, or modify data in-place.

    1. Generate a new vector (over)

    Use .over(inputs) to iterate a method over a Sequence and return a new Vec containing the results. The output length is guaranteed to match the input length.

    2. Call a method on a Sequence (call)

    If you have a Sequence object, you can pass a mutable reference to your method using .call(&mut method). This is useful when working with YATA's Sequence abstraction.

    3. In-place modification (apply)

    If the Method's Input and Output types are the same, you can use .apply(&mut sequence) to modify the values within the sequence directly.

    4. One-shot execution (new_over and new_apply)

    If you don't want to manage the lifecycle of the Method instance manually, use these static methods:

    • Method::new_over(params, inputs): Creates a new instance and returns a Vec of results.
    • Method::new_apply(params, &mut sequence): Creates a new instance and applies it to the sequence in-place.
    use yata::methods::SMA;
    use yata::prelude::*;
    
    let s: Vec<_> = vec![1., 2., 3., 4., 5.];
    let mut ma = SMA::new(2, &s[0]).unwrap();
    
    // 1. Get a new vector
    let result = ma.over(s.clone());
    
    // 2. Apply in-place (if Input == Output)
    let mut s_mut = vec![1., 2., 3., 4., 5.];
    let mut ma_inplace = SMA::new(2, &s_mut[0]).unwrap();
    s_mut.apply(&mut ma_inplace);
  9. How to use technical analysis methods

    master

    Methods in YATA (like EMA, SMA, RSI, etc.) are typically used by initializing a new instance with specific parameters and then calling .next() with each new data point. This allows for streaming or iterative calculation of technical indicators.

    use yata::prelude::*;
    use yata::methods::EMA;
    
    // EMA of length=3
    let mut ema = EMA::new(3, &3.0).unwrap();
    
    ema.next(&3.0);
    ema.next(&6.0);
    
    assert_eq!(ema.next(&9.0), 6.75);
    assert_eq!(ema.next(&12.0), 9.375);
  10. How to use technical indicators

    master

    Indicators in YATA often compose multiple methods. You can configure them by setting their internal method fields (e.g., macd.method1) or by using helper types like MA to define the underlying moving averages. After configuration, use .init() with an initial data point and then iterate using .next().

    use yata::helpers::{RandomCandles, MA};
    use yata::indicators::MACD;
    use yata::prelude::*;
    
    let mut candles = RandomCandles::new();
    let mut macd = MACD::default();
    
    // One way of defining methods inside indicators
    macd.method1 = "sma-4".parse().unwrap(); 
    
    // Another way of defining methods inside indicators
    macd.signal = MA::TEMA(5); 
    
    let mut macd = macd.init(&candles.first()).unwrap();
    
    for candle in candles.take(10) {
    	let result = macd.next(&candle);
    	println!("{:?}", result);
    }