├── kand-py ├── src │ ├── helper.rs │ └── ta │ │ ├── ohlcv │ │ ├── apo.rs │ │ ├── kama.rs │ │ ├── ppo.rs │ │ ├── medprice.rs │ │ ├── mod.rs │ │ ├── wma.rs │ │ ├── wclprice.rs │ │ ├── roc.rs │ │ ├── rocr.rs │ │ ├── rocp.rs │ │ ├── mom.rs │ │ ├── rma.rs │ │ ├── trange.rs │ │ ├── stoch.rs │ │ ├── dema.rs │ │ ├── rocr100.rs │ │ └── typprice.rs │ │ ├── other │ │ └── mod.rs │ │ ├── stats │ │ ├── beta.rs │ │ ├── fv.rs │ │ ├── mod.rs │ │ ├── max.rs │ │ ├── min.rs │ │ └── sum.rs │ │ └── mod.rs ├── python │ ├── kand │ │ ├── py.typed │ │ └── __init__.py │ ├── benches │ │ ├── .gitkeep │ │ ├── bench_ema_incremental.py │ │ └── bench_ema_incremental_mt.py │ └── examples │ │ ├── sma.py │ │ └── sma_thread.py ├── .gitignore └── Cargo.toml ├── kand ├── src │ ├── ta │ │ ├── stats │ │ │ ├── fv.rs │ │ │ ├── alpha.rs │ │ │ ├── beta.rs │ │ │ ├── calmar.rs │ │ │ ├── kelly.rs │ │ │ ├── nper.rs │ │ │ ├── ret.rs │ │ │ ├── sharpe.rs │ │ │ ├── sortino.rs │ │ │ ├── winrate.rs │ │ │ ├── drawdown.rs │ │ │ └── mod.rs │ │ ├── ohlcv │ │ │ ├── apo.rs │ │ │ ├── cmo.rs │ │ │ ├── ht_sine.rs │ │ │ ├── kama.rs │ │ │ ├── macdext.rs │ │ │ ├── mama.rs │ │ │ ├── ppo.rs │ │ │ ├── sarext.rs │ │ │ ├── stochf.rs │ │ │ ├── tsf.rs │ │ │ ├── ultosc.rs │ │ │ ├── ht_dcperiod.rs │ │ │ ├── ht_dcphase.rs │ │ │ ├── ht_phasor.rs │ │ │ ├── ht_trendline.rs │ │ │ ├── ht_trendmode.rs │ │ │ ├── linearreg.rs │ │ │ ├── stochrsi.rs │ │ │ ├── linearreg_angle.rs │ │ │ ├── linearreg_slope.rs │ │ │ ├── linearreg_intercept.rs │ │ │ ├── renko.rs │ │ │ └── mod.rs │ │ ├── other │ │ │ └── mod.rs │ │ └── mod.rs │ └── error.rs ├── benches │ ├── benchmarks │ │ ├── helper.rs │ │ ├── other │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── stats │ │ │ ├── mod.rs │ │ │ ├── max_bench.rs │ │ │ ├── min_bench.rs │ │ │ ├── sum_bench.rs │ │ │ ├── beta_bench.rs │ │ │ ├── correl_bench.rs │ │ │ ├── var_bench.rs │ │ │ └── stddev_bench.rs │ │ └── ohlcv │ │ │ ├── obv_bench.rs │ │ │ ├── medprice_bench.rs │ │ │ ├── mom_bench.rs │ │ │ ├── rma_bench.rs │ │ │ ├── roc_bench.rs │ │ │ ├── sma_bench.rs │ │ │ ├── wma_bench.rs │ │ │ ├── cmo_bench.rs │ │ │ ├── ppo_bench.rs │ │ │ ├── tsf_bench.rs │ │ │ ├── kama_bench.rs │ │ │ ├── mama_bench.rs │ │ │ ├── rocp_bench.rs │ │ │ ├── rocr_bench.rs │ │ │ ├── sarext_bench.rs │ │ │ ├── stochf_bench.rs │ │ │ ├── ultosc_bench.rs │ │ │ ├── trange_bench.rs │ │ │ ├── ht_sine_bench.rs │ │ │ ├── macdext_bench.rs │ │ │ ├── stochrsi_bench.rs │ │ │ ├── wclprice_bench.rs │ │ │ ├── ht_phasor_bench.rs │ │ │ ├── linearreg_bench.rs │ │ │ ├── ht_dcphase_bench.rs │ │ │ ├── ht_dcperiod_bench.rs │ │ │ ├── rocr100_bench.rs │ │ │ ├── ht_trendline_bench.rs │ │ │ ├── ht_trendmode_bench.rs │ │ │ ├── retracements_bench.rs │ │ │ ├── typprice_bench.rs │ │ │ ├── linearreg_angle_bench.rs │ │ │ ├── linearreg_slope_bench.rs │ │ │ ├── linearreg_intercept_bench.rs │ │ │ ├── bop_bench.rs │ │ │ ├── ema_bench.rs │ │ │ ├── ad_bench.rs │ │ │ ├── trima_bench.rs │ │ │ ├── cdl_doji_bench.rs │ │ │ ├── vegas_bench.rs │ │ │ ├── adr_bench.rs │ │ │ ├── cdl_dragonfly_doji_bench.rs │ │ │ ├── cdl_gravestone_doji_bench.rs │ │ │ ├── plus_dm_bench.rs │ │ │ ├── minus_dm_bench.rs │ │ │ ├── sar_bench.rs │ │ │ ├── atr_bench.rs │ │ │ ├── dema_bench.rs │ │ │ ├── natr_bench.rs │ │ │ ├── vwap_bench.rs │ │ │ ├── cdl_hammer_bench.rs │ │ │ ├── rsi_bench.rs │ │ │ ├── apo_bench.rs │ │ │ ├── midpoint_bench.rs │ │ │ ├── tema_bench.rs │ │ │ ├── trix_bench.rs │ │ │ ├── midprice_bench.rs │ │ │ ├── willr_bench.rs │ │ │ ├── plus_di_bench.rs │ │ │ ├── minus_di_bench.rs │ │ │ ├── cdl_marubozu_bench.rs │ │ │ ├── cdl_long_shadow_bench.rs │ │ │ ├── cci_bench.rs │ │ │ ├── cdl_inverted_hammer_bench.rs │ │ │ ├── dx_bench.rs │ │ │ ├── adx_bench.rs │ │ │ ├── aroonosc_bench.rs │ │ │ ├── supertrend_bench.rs │ │ │ ├── adxr_bench.rs │ │ │ ├── bbands_bench.rs │ │ │ ├── aroon_bench.rs │ │ │ ├── mfi_bench.rs │ │ │ ├── t3_bench.rs │ │ │ ├── ecl_bench.rs │ │ │ ├── mod.rs │ │ │ ├── adosc_bench.rs │ │ │ ├── stoch_bench.rs │ │ │ └── macd_bench.rs │ └── helper.rs ├── Cargo.toml └── examples │ └── sma.rs ├── .python-version ├── docs ├── api.md ├── assets │ ├── logo.png │ ├── bench_ema.png │ ├── batch_ema_performance_mac_m4.png │ ├── batch_ema_performance_mt_2_mac_m4.png │ ├── batch_ema_performance_mt_4_mac_m4.png │ ├── batch_ema_performance_win_i713700.png │ ├── batch_ema_performance_mt_2_win_i713700.png │ ├── batch_ema_performance_mt_4_win_i713700.png │ └── batch_ema_performance_mt_6_win_i713700.png ├── install.md └── about.md ├── rust-toolchain.toml ├── clippy.toml ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── workflows │ ├── test-rust.yml │ ├── publish-rust.yml │ ├── publish-doc.yml │ ├── release.yml │ └── publish-docker.yml └── dependabot.yml ├── .editorconfig ├── .pre-commit-config.yaml ├── SECURITY.md ├── .gitattributes ├── Cargo.toml ├── LICENSE-MIT ├── Makefile ├── pyproject.toml ├── CHANGELOG.md └── Dockerfile /kand-py/src/helper.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/fv.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /kand-py/python/kand/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/apo.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/kama.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/ppo.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/src/ta/other/mod.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/src/ta/stats/beta.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/src/ta/stats/fv.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/apo.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/cmo.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ht_sine.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/kama.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/macdext.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/mama.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ppo.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/sarext.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/stochf.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/tsf.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ultosc.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/other/mod.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/alpha.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/beta.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/calmar.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/kelly.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/nper.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/ret.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/sharpe.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/sortino.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/winrate.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | 2 | ::: kand 3 | -------------------------------------------------------------------------------- /kand-py/python/benches/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/helper.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ht_dcperiod.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ht_dcphase.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ht_phasor.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ht_trendline.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/ht_trendmode.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/linearreg.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/stochrsi.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/stats/drawdown.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/other/mod.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/linearreg_angle.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/linearreg_slope.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/linearreg_intercept.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/python/benches/bench_ema_incremental.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/python/benches/bench_ema_incremental_mt.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kand-py/python/kand/__init__.py: -------------------------------------------------------------------------------- 1 | from ._kand import * 2 | -------------------------------------------------------------------------------- /kand-py/src/ta/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ohlcv; 2 | pub mod stats; 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /kand/src/ta/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ohlcv; 2 | pub mod stats; 3 | 4 | pub mod types; 5 | -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/logo.png -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/renko.rs: -------------------------------------------------------------------------------- 1 | // https://br.tradingview.com/script/9BKOIhdl-Numbers-Renko/ 2 | -------------------------------------------------------------------------------- /docs/assets/bench_ema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/bench_ema.png -------------------------------------------------------------------------------- /kand/benches/benchmarks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ohlcv; 2 | pub mod stats; 3 | 4 | // pub mod helper; 5 | -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_mac_m4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_mac_m4.png -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_mt_2_mac_m4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_mt_2_mac_m4.png -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_mt_4_mac_m4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_mt_4_mac_m4.png -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_win_i713700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_win_i713700.png -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | too-many-arguments-threshold = 20 2 | type-complexity-threshold = 500 3 | allow-expect-in-tests = true 4 | allow-unwrap-in-tests = true 5 | -------------------------------------------------------------------------------- /kand/src/ta/stats/mod.rs: -------------------------------------------------------------------------------- 1 | // pub mod beta; 2 | pub mod correl; 3 | pub mod max; 4 | pub mod min; 5 | pub mod stddev; 6 | pub mod sum; 7 | pub mod var; 8 | -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_mt_2_win_i713700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_mt_2_win_i713700.png -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_mt_4_win_i713700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_mt_4_win_i713700.png -------------------------------------------------------------------------------- /docs/assets/batch_ema_performance_mt_6_win_i713700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kand-ta/kand/HEAD/docs/assets/batch_ema_performance_mt_6_win_i713700.png -------------------------------------------------------------------------------- /kand-py/src/ta/stats/mod.rs: -------------------------------------------------------------------------------- 1 | // pub mod beta; 2 | pub mod correl; 3 | pub mod max; 4 | pub mod min; 5 | pub mod stddev; 6 | pub mod sum; 7 | pub mod var; 8 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod max_bench; 2 | pub mod min_bench; 3 | pub mod stddev_bench; 4 | pub mod sum_bench; 5 | pub mod var_bench; 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Documentation 4 | url: https://docs.rs/kand/latest/kand/ 5 | about: Please consult the documentation before creating an issue. 6 | - name: Discussion 7 | url: https://github.com/orgs/rust-ta/discussions 8 | about: Join our community to ask questions and collaborate. 9 | -------------------------------------------------------------------------------- /.github/workflows/test-rust.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Build & Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build_and_test: 12 | name: Rust kandx - latest 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: cargo build --verbose 17 | - run: cargo test --verbose 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Check http://editorconfig.org for more information 2 | # This is the main config file for this project: 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | indent_style = space 10 | insert_final_newline = true 11 | indent_size = 2 12 | 13 | [*.{rs,py,pyi}] 14 | indent_size = 4 15 | 16 | [*.snap] 17 | trim_trailing_whitespace = false 18 | 19 | [*.md] 20 | max_line_length = 100 21 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - id: check-merge-conflict 12 | - id: mixed-line-ending 13 | args: ['--fix=lf'] 14 | -------------------------------------------------------------------------------- /kand-py/python/examples/sma.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from kand import sma 3 | import time 4 | 5 | data = np.array([float(i) for i in range(10_000_000)]) 6 | 7 | start_time = time.perf_counter() 8 | result = sma(data, 10) 9 | end_time = time.perf_counter() 10 | 11 | print(f"Result: {result}") 12 | print(f"Result type: {type(result)}") 13 | print(f"Result dtype: {result.dtype}") 14 | print(f"Execution time: {end_time - start_time:.4f} seconds") 15 | 16 | print(sma.__doc__) 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/publish-rust.yml: -------------------------------------------------------------------------------- 1 | name: Publish to crates.io 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | publish: 13 | name: Build, Test & Publish 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Build 18 | run: cargo build --release --verbose 19 | - name: Test 20 | run: cargo test --verbose 21 | - name: Publish to crates.io 22 | env: 23 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 24 | run: cargo publish -p kand 25 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 0.2.x | :white_check_mark: | 11 | | < 0.1 | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | Use this section to tell people how to report a vulnerability. 16 | 17 | Tell them where to go, how often they can expect to get an update on a 18 | reported vulnerability, what to expect if the vulnerability is accepted or 19 | declined, etc. 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Basic settings 2 | * text=auto eol=lf 3 | 4 | # Source code files 5 | *.rs text diff=rust 6 | *.toml text diff=toml 7 | 8 | # Scripts and configuration files 9 | *.sh text eol=lf 10 | *.bat text eol=crlf 11 | *.cmd text eol=crlf 12 | *.json text 13 | *.yaml text 14 | *.xml text 15 | *.md text 16 | *.txt text 17 | 18 | # Visual Studio files 19 | *.sln text eol=crlf 20 | *.vcxproj text eol=crlf 21 | *.vcxproj.filters text eol=crlf 22 | 23 | # Binary files 24 | *.png binary 25 | *.jpg binary 26 | *.gif binary 27 | *.ico binary 28 | *.zip binary 29 | *.dll binary 30 | *.exe binary 31 | *.lib binary 32 | *.pdf binary 33 | 34 | # Special handling 35 | *.json merge=union 36 | *.yaml merge=union 37 | *.xml merge=union 38 | -------------------------------------------------------------------------------- /kand/src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(thiserror::Error, Debug)] 2 | pub enum KandError { 3 | #[error("Invalid parameter value provided to the function")] 4 | InvalidParameter, 5 | 6 | #[error("Insufficient data points for the requested calculation")] 7 | InsufficientData, 8 | 9 | #[error("Input data contains NaN (Not a Number) values")] 10 | NaNDetected, 11 | 12 | #[error("Input arrays have mismatched lengths")] 13 | LengthMismatch, 14 | 15 | #[error("Input data is invalid (out of range or empty)")] 16 | InvalidData, 17 | 18 | #[error("File operation error occurred")] 19 | FileError, 20 | 21 | #[error("Failed to convert between numeric types")] 22 | ConversionError, 23 | 24 | #[error("Invalid input: {0}")] 25 | InvalidInput(String), 26 | 27 | #[error("Calculation error: {0}")] 28 | CalculationError(String), 29 | } 30 | 31 | pub type Result = std::result::Result; 32 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/obv_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::obv::obv; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_obv(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("obv"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | 13 | for size in sizes { 14 | let close = generate_test_data(size); 15 | let volume = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 19 | b.iter(|| { 20 | let _ = obv( 21 | black_box(&close), 22 | black_box(&volume), 23 | black_box(&mut output), 24 | ); 25 | }); 26 | }); 27 | } 28 | 29 | group.finish(); 30 | } 31 | 32 | criterion_group!(ohlcv, bench_obv); 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["kand", "kand-py"] 3 | # Only check / build main crates by default (check all with `--workspace`) 4 | default-members = ["kand"] 5 | 6 | resolver = "3" 7 | 8 | [workspace.dependencies] 9 | approx = "0.5.1" 10 | criterion = "0.5.1" 11 | csv = "1.3.1" 12 | ndarray = "0.16.1" 13 | num_enum = "0.7.3" 14 | num-traits = "0.2.19" 15 | numpy = "0.24.0" 16 | pyo3 = "0.24.0" 17 | rand = "0.9.2" 18 | rayon = "1.10.0" 19 | rust_decimal = "1.36.0" 20 | serde = { version = "1.0.219", features = ["derive"] } 21 | serde_json = "1.0.141" 22 | kand = { path = "./kand", default-features = false, features = ["f64", "i64", "check"] } 23 | thiserror = "2.0.17" 24 | 25 | 26 | [profile.release] 27 | lto = true # Enable Link Time Optimization to remove unused code 28 | codegen-units = 1 # Maximize optimization at the cost of slower compilation 29 | panic = 'abort' # Remove panic handling to reduce binary size 30 | strip = true # Fully strip debug symbols; debugging in production will be difficult 31 | -------------------------------------------------------------------------------- /kand-py/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | .pytest_cache/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | .venv/ 14 | env/ 15 | bin/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | include/ 26 | man/ 27 | venv/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | pip-selfcheck.json 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | 48 | # Mr Developer 49 | .mr.developer.cfg 50 | .project 51 | .pydevproject 52 | 53 | # Rope 54 | .ropeproject 55 | 56 | # Django stuff: 57 | *.log 58 | *.pot 59 | 60 | .DS_Store 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyCharm 66 | .idea/ 67 | 68 | # VSCode 69 | .vscode/ 70 | 71 | # Pyenv 72 | .python-version 73 | -------------------------------------------------------------------------------- /kand/benches/helper.rs: -------------------------------------------------------------------------------- 1 | use kand::TAFloat; 2 | use rand::Rng; 3 | 4 | /// Generate a vector of floating point values simulating price movements 5 | /// 6 | /// # Panics 7 | /// 8 | /// This function will panic if: 9 | /// - Type conversion from `usize` to `TAFloat` fails 10 | #[must_use] 11 | #[allow(clippy::expect_used)] 12 | pub fn generate_test_data(size: usize) -> Vec { 13 | let mut rng = rand::rng(); 14 | let mut data = Vec::with_capacity(size); 15 | 16 | // These constants are used frequently 17 | let price_init: TAFloat = 100.0; 18 | let increment: TAFloat = 0.001; 19 | let half: TAFloat = 0.5; 20 | let two: TAFloat = 2.0; 21 | 22 | let mut price = price_init; 23 | 24 | for i in 0..size { 25 | // Simulate small random price movements 26 | let random_factor: TAFloat = rng.random_range(0.0..1.0); 27 | price = (random_factor - half).mul_add(two, price); 28 | let idx = i as TAFloat; 29 | data.push(idx.mul_add(increment, price)); 30 | } 31 | data 32 | } 33 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/medprice_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::medprice::medprice; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_medprice(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("medprice"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | 12 | for size in sizes { 13 | let input_high = generate_test_data(size); 14 | let input_low = generate_test_data(size); 15 | let mut output_medprice = vec![0.0; size]; 16 | 17 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 18 | b.iter(|| { 19 | let _ = medprice( 20 | black_box(&input_high), 21 | black_box(&input_low), 22 | black_box(&mut output_medprice), 23 | ); 24 | }); 25 | }); 26 | } 27 | 28 | group.finish(); 29 | } 30 | 31 | criterion_group!(ohlcv, bench_medprice); 32 | -------------------------------------------------------------------------------- /kand-py/python/examples/sma_thread.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | from concurrent.futures import ThreadPoolExecutor 4 | from kand import sma 5 | 6 | # Generate test data 7 | data = np.array([float(i) for i in range(10_000_000)]) # 10 million data points 8 | period = 100 9 | 10 | # Single-threaded test 11 | start_time = time.perf_counter() 12 | _ = sma(data, period) # First call 13 | _ = sma(data, period) # Second call 14 | single_thread_time = (time.perf_counter() - start_time) / 2 15 | print(f"Single-threaded execution time: {single_thread_time:.4f} seconds") 16 | 17 | # Multi-threaded test (2 threads) 18 | with ThreadPoolExecutor(max_workers=2) as executor: 19 | start_time = time.perf_counter() 20 | futures = [executor.submit(sma, data, period) for _ in range(2)] # Two concurrent calls 21 | _ = [future.result() for future in futures] 22 | thread_pool_time = (time.perf_counter() - start_time) / 2 23 | 24 | print(f"Multi-threaded execution time: {thread_pool_time:.4f} seconds") 25 | print(f"Speedup: {single_thread_time / thread_pool_time:.2f}x") 26 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/mom_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::mom::mom; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_mom(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("mom"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = mom(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_mom); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/rma_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::rma::rma; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_rma(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("rma"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = rma(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_rma); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/roc_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::roc::roc; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_roc(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("roc"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = roc(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_roc); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/sma_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::sma::sma; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_sma(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("SMA"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = sma(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_sma); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/wma_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::wma::wma; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_wma(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("wma"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = wma(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_wma); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/max_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::stats::max::max; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_max(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("max"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = max(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(stats, bench_max); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/min_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::stats::min::min; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_min(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("min"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = min(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(stats, bench_min); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/sum_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::stats::sum::sum; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_sum(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("sum"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = sum(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(stats, bench_sum); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cmo_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::cmo::cmo; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_cmo(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("cmo"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = cmo(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_cmo); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ppo_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ppo::ppo; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ppo(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ppo"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ppo(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ppo); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/tsf_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::tsf::tsf; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_tsf(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("tsf"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = tsf(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_tsf); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/kama_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::kama::kama; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_kama(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("kama"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = kama(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_kama); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/mama_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::mama::mama; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_mama(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("mama"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = mama(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_mama); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/rocp_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::rocp::rocp; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_rocp(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("rocp"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = rocp(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_rocp); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/rocr_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::rocr::rocr; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_rocr(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("rocr"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = rocr(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_rocr); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/beta_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::beta::beta; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_beta(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("beta"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = beta(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_beta); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/sarext_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::sarext::sarext; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_sarext(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("sarext"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = sarext(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_sarext); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/stochf_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::stochf::stochf; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_stochf(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("stochf"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = stochf(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_stochf); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ultosc_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ultosc::ultosc; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ultosc(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ultosc"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ultosc(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ultosc); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/correl_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::correl::correl; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_correl(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("correl"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = correl(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_correl); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/trange_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::trange::trange; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_trange(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("trange"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | 13 | for size in sizes { 14 | let high = generate_test_data(size); 15 | let low = generate_test_data(size); 16 | let close = generate_test_data(size); 17 | let mut output = vec![0.0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = trange( 22 | black_box(&high), 23 | black_box(&low), 24 | black_box(&close), 25 | black_box(&mut output), 26 | ); 27 | }); 28 | }); 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_trange); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ht_sine_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ht_sine::ht_sine; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ht_sine(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ht_sine"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ht_sine(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ht_sine); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/macdext_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::macdext::macdext; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_macdext(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("macdext"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = macdext(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_macdext); 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Rust TA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/stochrsi_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::stochrsi::stochrsi; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_stochrsi(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("stochrsi"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = stochrsi(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_stochrsi); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/wclprice_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::wclprice::wclprice; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_wclprice(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("wclprice"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | 12 | for size in sizes { 13 | let input_high = generate_test_data(size); 14 | let input_low = generate_test_data(size); 15 | let input_close = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 19 | b.iter(|| { 20 | let _ = wclprice( 21 | black_box(&input_high), 22 | black_box(&input_low), 23 | black_box(&input_close), 24 | black_box(&mut output), 25 | ); 26 | }); 27 | }); 28 | } 29 | 30 | group.finish(); 31 | } 32 | 33 | criterion_group!(ohlcv, bench_wclprice); 34 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ht_phasor_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ht_phasor::ht_phasor; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ht_phasor(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ht_phasor"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ht_phasor(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ht_phasor); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/linearreg_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::linearreg::linearreg; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_linearreg(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("linearreg"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = linearreg(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_linearreg); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ht_dcphase_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ht_dcphase::ht_dcphase; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ht_dcphase(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ht_dcphase"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ht_dcphase(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ht_dcphase); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ht_dcperiod_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ht_dcperiod::ht_dcperiod; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ht_dcperiod(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ht_dcperiod"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ht_dcperiod(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ht_dcperiod); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/rocr100_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::rocr100::rocr100; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_rocr100(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("rocr100"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = 25 | rocr100(black_box(&input), black_box(period), black_box(&mut output)); 26 | }); 27 | }, 28 | ); 29 | } 30 | } 31 | 32 | group.finish(); 33 | } 34 | 35 | criterion_group!(ohlcv, bench_rocr100); 36 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ht_trendline_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ht_trendline::ht_trendline; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ht_trendline(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ht_trendline"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ht_trendline(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ht_trendline); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ht_trendmode_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::ht_trendmode::ht_trendmode; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ht_trendmode(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ht_trendmode"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ht_trendmode(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_ht_trendmode); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/retracements_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::retracements::retracements; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_retracements(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("retracements"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = retracements(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_retracements); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/typprice_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::typprice::typprice; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_typprice(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("typprice"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | 13 | for size in sizes { 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output = vec![0.0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = typprice( 22 | black_box(&input_high), 23 | black_box(&input_low), 24 | black_box(&input_close), 25 | black_box(&mut output), 26 | ); 27 | }); 28 | }); 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_typprice); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/linearreg_angle_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::linearreg_angle::linearreg_angle; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_linearreg_angle(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("linearreg_angle"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = linearreg_angle(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_linearreg_angle); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/linearreg_slope_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::linearreg_slope::linearreg_slope; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_linearreg_slope(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("linearreg_slope"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = linearreg_slope(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_linearreg_slope); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/linearreg_intercept_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::linearreg_intercept::linearreg_intercept; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_linearreg_intercept(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("linearreg_intercept"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = linearreg_intercept(black_box(&input), black_box(period), black_box(&mut output)); 25 | }); 26 | }, 27 | ); 28 | } 29 | } 30 | 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(ohlcv, bench_linearreg_intercept); 35 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/bop_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::bop::bop; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_bop(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("bop"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | 12 | for size in sizes { 13 | let input_open = generate_test_data(size); 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output_bop = vec![0.0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = bop( 22 | black_box(&input_open), 23 | black_box(&input_high), 24 | black_box(&input_low), 25 | black_box(&input_close), 26 | black_box(&mut output_bop), 27 | ); 28 | }); 29 | }); 30 | } 31 | 32 | group.finish(); 33 | } 34 | 35 | criterion_group!(ohlcv, bench_bop); 36 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ema_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::ema::ema; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ema(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("ema"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = ema( 25 | black_box(&input), 26 | black_box(period), 27 | black_box(None), 28 | black_box(&mut output), 29 | ); 30 | }); 31 | }, 32 | ); 33 | } 34 | } 35 | 36 | group.finish(); 37 | } 38 | 39 | criterion_group!(ohlcv, bench_ema); 40 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ad_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::ad::ad; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_ad(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("AD"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | 13 | for size in sizes { 14 | let high = generate_test_data(size); 15 | let low = generate_test_data(size); 16 | let close = generate_test_data(size); 17 | let volume = generate_test_data(size); 18 | let mut output = vec![0.0; size]; 19 | 20 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, _| { 21 | b.iter(|| { 22 | // Use black_box on the result to prevent LLVM from optimizing away the computation 23 | let _ = ad( 24 | black_box(&high), 25 | black_box(&low), 26 | black_box(&close), 27 | black_box(&volume), 28 | black_box(&mut output), 29 | ); 30 | }); 31 | }); 32 | } 33 | 34 | group.finish(); 35 | } 36 | 37 | criterion_group!(ohlcv, bench_ad); 38 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/trima_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::trima::trima; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_trima(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("trima"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input = generate_test_data(size); 15 | let mut output_sma1 = vec![0.0; size]; 16 | let mut output_sma2 = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = trima( 25 | black_box(&input), 26 | black_box(period), 27 | black_box(&mut output_sma1), 28 | black_box(&mut output_sma2), 29 | ); 30 | }); 31 | }, 32 | ); 33 | } 34 | } 35 | 36 | group.finish(); 37 | } 38 | 39 | criterion_group!(ohlcv, bench_trima); 40 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_doji_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_doji::cdl_doji; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_cdl_doji(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("cdl_doji"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | 12 | for size in sizes { 13 | let input_open = generate_test_data(size); 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output_signals = vec![0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = cdl_doji( 22 | black_box(&input_open), 23 | black_box(&input_high), 24 | black_box(&input_low), 25 | black_box(&input_close), 26 | black_box(0.1), 27 | black_box(0.1), 28 | black_box(&mut output_signals), 29 | ); 30 | }); 31 | }); 32 | } 33 | 34 | group.finish(); 35 | } 36 | 37 | criterion_group!(ohlcv, bench_cdl_doji); 38 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/vegas_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::vegas::vegas; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_vegas(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("vegas"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | 13 | for size in sizes { 14 | let input = generate_test_data(size); 15 | let mut output_channel_upper = vec![0.0; size]; 16 | let mut output_channel_lower = vec![0.0; size]; 17 | let mut output_boundary_upper = vec![0.0; size]; 18 | let mut output_boundary_lower = vec![0.0; size]; 19 | 20 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 21 | b.iter(|| { 22 | let _ = vegas( 23 | black_box(&input), 24 | black_box(&mut output_channel_upper), 25 | black_box(&mut output_channel_lower), 26 | black_box(&mut output_boundary_upper), 27 | black_box(&mut output_boundary_lower), 28 | ); 29 | }); 30 | }); 31 | } 32 | 33 | group.finish(); 34 | } 35 | 36 | criterion_group!(ohlcv, bench_vegas); 37 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/adr_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::adr::adr; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_adr(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("adr"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let mut output = vec![0.0; size]; 18 | 19 | for period in &periods { 20 | group.bench_with_input( 21 | BenchmarkId::new(format!("size_{size}"), period), 22 | period, 23 | |b, &period| { 24 | b.iter(|| { 25 | let _ = adr( 26 | black_box(&input_high), 27 | black_box(&input_low), 28 | black_box(period), 29 | black_box(&mut output), 30 | ); 31 | }); 32 | }, 33 | ); 34 | } 35 | } 36 | 37 | group.finish(); 38 | } 39 | 40 | criterion_group!(ohlcv, bench_adr); 41 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_dragonfly_doji_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_dragonfly_doji::cdl_dragonfly_doji; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_cdl_dragonfly_doji(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("cdl_dragonfly_doji"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | 12 | for size in sizes { 13 | let input_open = generate_test_data(size); 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output_signals = vec![0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = cdl_dragonfly_doji( 22 | black_box(&input_open), 23 | black_box(&input_high), 24 | black_box(&input_low), 25 | black_box(&input_close), 26 | black_box(0.1), 27 | black_box(&mut output_signals), 28 | ); 29 | }); 30 | }); 31 | } 32 | 33 | group.finish(); 34 | } 35 | 36 | criterion_group!(ohlcv, bench_cdl_dragonfly_doji); 37 | -------------------------------------------------------------------------------- /kand-py/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kand-py" 3 | version = "0.2.2" 4 | edition = "2024" 5 | readme = "../README.md" 6 | authors = ["CtrlX "] 7 | description = "Kand: A Pure Rust technical analysis library inspired by TA-Lib." 8 | license = "Apache-2.0 OR MIT" 9 | repository = "https://github.com/rust-ta/kand" 10 | documentation = "https://docs.rs/kand" 11 | keywords = ["technical-analysis", "finance", "rust", "talib", "indicators"] 12 | categories = ["finance", "algorithms", "data-structures", "science", "mathematics"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | [lib] 16 | name = "_kand" 17 | crate-type = ["cdylib"] 18 | 19 | [dependencies] 20 | numpy = {workspace = true} 21 | pyo3 = {workspace = true, features = ["extension-module"]} 22 | kand = { workspace = true} 23 | 24 | [features] 25 | default = ["f32", "i64", "check"] # Default: extended precision with basic checks 26 | f32 = ["kand/f32"] # 32-bit floating point 27 | f64 = ["kand/f64"] # 64-bit floating point 28 | i32 = ["kand/i32"] # 32-bit integer 29 | i64 = ["kand/i64"] # 64-bit integer 30 | check = ["kand/check"] # Basic validation checks 31 | deep-check = ["check", "kand/deep-check"] # Extended validation checks 32 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_gravestone_doji_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_gravestone_doji::cdl_gravestone_doji; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_cdl_gravestone_doji(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("cdl_gravestone_doji"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | 12 | for size in sizes { 13 | let input_open = generate_test_data(size); 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output_signals = vec![0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = cdl_gravestone_doji( 22 | black_box(&input_open), 23 | black_box(&input_high), 24 | black_box(&input_low), 25 | black_box(&input_close), 26 | black_box(0.1), 27 | black_box(&mut output_signals), 28 | ); 29 | }); 30 | }); 31 | } 32 | 33 | group.finish(); 34 | } 35 | 36 | criterion_group!(ohlcv, bench_cdl_gravestone_doji); 37 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/plus_dm_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::plus_dm::plus_dm; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_plus_dm(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("plus_dm"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let mut output = vec![0.0; size]; 18 | 19 | for period in &periods { 20 | group.bench_with_input( 21 | BenchmarkId::new(format!("size_{size}"), period), 22 | period, 23 | |b, &period| { 24 | b.iter(|| { 25 | let _ = plus_dm( 26 | black_box(&input_high), 27 | black_box(&input_low), 28 | black_box(period), 29 | black_box(&mut output), 30 | ); 31 | }); 32 | }, 33 | ); 34 | } 35 | } 36 | 37 | group.finish(); 38 | } 39 | 40 | criterion_group!(ohlcv, bench_plus_dm); 41 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/minus_dm_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::minus_dm::minus_dm; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_minus_dm(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("minus_dm"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let mut output = vec![0.0; size]; 18 | 19 | for period in &periods { 20 | group.bench_with_input( 21 | BenchmarkId::new(format!("size_{size}"), period), 22 | period, 23 | |b, &period| { 24 | b.iter(|| { 25 | let _ = minus_dm( 26 | black_box(&input_high), 27 | black_box(&input_low), 28 | black_box(period), 29 | black_box(&mut output), 30 | ); 31 | }); 32 | }, 33 | ); 34 | } 35 | } 36 | 37 | group.finish(); 38 | } 39 | 40 | criterion_group!(ohlcv, bench_minus_dm); 41 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/sar_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::sar::sar; 3 | 4 | use crate::helper::generate_test_data; 5 | #[allow(dead_code)] 6 | fn bench_sar(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("sar"); 8 | 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | 11 | for size in sizes { 12 | let input_high = generate_test_data(size); 13 | let input_low = generate_test_data(size); 14 | let mut output_sar = vec![0.0; size]; 15 | let mut output_is_long = vec![false; size]; 16 | let mut output_af = vec![0.0; size]; 17 | let mut output_ep = vec![0.0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = sar( 22 | black_box(&input_high), 23 | black_box(&input_low), 24 | black_box(0.02), 25 | black_box(0.2), 26 | black_box(&mut output_sar), 27 | black_box(&mut output_is_long), 28 | black_box(&mut output_af), 29 | black_box(&mut output_ep), 30 | ); 31 | }); 32 | }); 33 | } 34 | 35 | group.finish(); 36 | } 37 | 38 | criterion_group!(ohlcv, bench_sar); 39 | -------------------------------------------------------------------------------- /kand/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kand" 3 | version = "0.2.2" 4 | edition = "2024" 5 | authors = ["CtrlX "] 6 | readme = "../README.md" 7 | description = "Kand: A Pure Rust technical analysis library inspired by TA-Lib." 8 | license = "Apache-2.0 OR MIT" 9 | repository = "https://github.com/rust-ta/kand" 10 | documentation = "https://docs.rs/kand" 11 | keywords = ["technical-analysis", "finance", "rust", "talib", "indicators"] 12 | categories = ["finance", "algorithms", "data-structures", "science", "mathematics"] 13 | 14 | [dependencies] 15 | num_enum = { workspace = true } 16 | thiserror = { workspace = true } 17 | 18 | [dev-dependencies] 19 | approx = { workspace = true } 20 | criterion = { workspace = true } 21 | csv = { workspace = true } 22 | ndarray = { workspace = true } 23 | rand = { workspace = true } 24 | rayon = { workspace = true } 25 | 26 | [[bench]] 27 | name = "bench_main" 28 | harness = false 29 | 30 | [features] 31 | default = ["f64", "i64", "check"] # Default: extended precision with basic checks 32 | f32 = [] # 32-bit floating point 33 | f64 = [] # 64-bit floating point 34 | i32 = [] # 32-bit integer 35 | i64 = [] # 64-bit integer 36 | check = [] # Basic validation checks 37 | deep-check = ["check"] # Extended validation checks 38 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/atr_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::atr::atr; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_atr(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("atr"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let high = generate_test_data(size); 16 | let low = generate_test_data(size); 17 | let close = generate_test_data(size); 18 | let mut output = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = atr( 27 | black_box(&high), 28 | black_box(&low), 29 | black_box(&close), 30 | black_box(period), 31 | black_box(&mut output), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(ohlcv, bench_atr); 43 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/dema_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::dema::dema; 3 | 4 | use crate::helper::generate_test_data; 5 | #[allow(dead_code)] 6 | fn bench_dema(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("dema"); 8 | 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | let periods = vec![5, 50, 200]; 11 | 12 | for size in sizes { 13 | let input = generate_test_data(size); 14 | let mut output_dema = vec![0.0; size]; 15 | let mut output_ema1 = vec![0.0; size]; 16 | let mut output_ema2 = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{size}"), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = dema( 25 | black_box(&input), 26 | black_box(period), 27 | black_box(&mut output_dema), 28 | black_box(&mut output_ema1), 29 | black_box(&mut output_ema2), 30 | ); 31 | }); 32 | }, 33 | ); 34 | } 35 | } 36 | 37 | group.finish(); 38 | } 39 | 40 | criterion_group!(ohlcv, bench_dema); 41 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/natr_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::natr::natr; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_natr(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("natr"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let high = generate_test_data(size); 16 | let low = generate_test_data(size); 17 | let close = generate_test_data(size); 18 | let mut output = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = natr( 27 | black_box(&high), 28 | black_box(&low), 29 | black_box(&close), 30 | black_box(period), 31 | black_box(&mut output), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(ohlcv, bench_natr); 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-doc.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Configure Git Credentials 15 | run: | 16 | git config user.name github-actions[bot] 17 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: 3.x 21 | - uses: dtolnay/rust-toolchain@stable 22 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 23 | - uses: actions/cache@v4 24 | with: 25 | key: mkdocs-material-${{ env.cache_id }} 26 | path: .cache 27 | restore-keys: | 28 | mkdocs-material- 29 | - name: Install dependencies 30 | run: | 31 | pip install mkdocs-material mkdocstrings mkdocstrings-python mkdocs-minify-plugin 32 | pip install . 33 | - name: Copy and process README.md 34 | run: | 35 | cp CHANGELOG.md docs/changelog.md 36 | cp README.md docs/index.md 37 | sed -i 's|docs/assets/logo.png|assets/logo.png|g' docs/index.md 38 | sed -i 's|docs/assets/bench_ema.png|assets/bench_ema.png|g' docs/index.md 39 | - run: mkdocs gh-deploy --force 40 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/vwap_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::vwap::vwap; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_vwap(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("vwap"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | 13 | for size in sizes { 14 | let high = generate_test_data(size); 15 | let low = generate_test_data(size); 16 | let close = generate_test_data(size); 17 | let volume = generate_test_data(size); 18 | let mut output_vwap = vec![0.0; size]; 19 | let mut output_cum_pv = vec![0.0; size]; 20 | let mut output_cum_vol = vec![0.0; size]; 21 | 22 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 23 | b.iter(|| { 24 | let _ = vwap( 25 | black_box(&high), 26 | black_box(&low), 27 | black_box(&close), 28 | black_box(&volume), 29 | black_box(&mut output_vwap), 30 | black_box(&mut output_cum_pv), 31 | black_box(&mut output_cum_vol), 32 | ); 33 | }); 34 | }); 35 | } 36 | 37 | group.finish(); 38 | } 39 | 40 | criterion_group!(ohlcv, bench_vwap); 41 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_hammer_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_hammer::cdl_hammer; 3 | 4 | use crate::helper::generate_test_data; 5 | #[allow(dead_code)] 6 | fn bench_cdl_hammer(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("cdl_hammer"); 8 | 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | 11 | for size in sizes { 12 | let input_open = generate_test_data(size); 13 | let input_high = generate_test_data(size); 14 | let input_low = generate_test_data(size); 15 | let input_close = generate_test_data(size); 16 | let mut output_signals = vec![0; size]; 17 | let mut output_body_avg = vec![0.0; size]; 18 | 19 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 20 | b.iter(|| { 21 | let _ = cdl_hammer( 22 | black_box(&input_open), 23 | black_box(&input_high), 24 | black_box(&input_low), 25 | black_box(&input_close), 26 | black_box(5), 27 | black_box(0.1), 28 | black_box(&mut output_signals), 29 | black_box(&mut output_body_avg), 30 | ); 31 | }); 32 | }); 33 | } 34 | 35 | group.finish(); 36 | } 37 | 38 | criterion_group!(ohlcv, bench_cdl_hammer); 39 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/var_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::stats::var::var; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_var(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("var"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | let mut output_sum = vec![0.0; size]; 18 | let mut output_sum_sq = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = var( 27 | black_box(&input), 28 | black_box(period), 29 | black_box(&mut output), 30 | black_box(&mut output_sum), 31 | black_box(&mut output_sum_sq), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(stats, bench_var); 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | permissions: 9 | contents: write # Grants permission to create releases 10 | 11 | jobs: 12 | generate-changelog: 13 | name: Generate Changelog 14 | runs-on: ubuntu-latest 15 | outputs: 16 | release_body: ${{ steps.git-cliff.outputs.content }} 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - name: Generate changelog 23 | id: git-cliff 24 | uses: orhun/git-cliff-action@v2 25 | with: 26 | config: cliff.toml 27 | args: -vv --latest --strip all 28 | 29 | create-release: 30 | name: Create GitHub Release 31 | needs: generate-changelog 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Set release version 38 | shell: bash 39 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV 40 | 41 | - name: Create Release 42 | uses: softprops/action-gh-release@v1 43 | with: 44 | name: Release v${{ env.RELEASE_VERSION }} 45 | body: ${{ needs.generate-changelog.outputs.release_body }} 46 | draft: false 47 | prerelease: ${{ contains(github.ref, '-') }} 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/rsi_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::rsi::rsi; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_rsi(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("rsi"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | let mut output_avg_gain = vec![0.0; size]; 18 | let mut output_avg_loss = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = rsi( 27 | black_box(&input), 28 | black_box(period), 29 | black_box(&mut output), 30 | black_box(&mut output_avg_gain), 31 | black_box(&mut output_avg_loss), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(ohlcv, bench_rsi); 43 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/apo_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; 2 | use kand::ohlcv::apo::apo; 3 | 4 | use crate::helpers::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_apo(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("apo"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_close = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | 18 | for period in &periods { 19 | group.bench_with_input( 20 | BenchmarkId::new(format!("size_{}", size), period), 21 | period, 22 | |b, &period| { 23 | b.iter(|| { 24 | let _ = apo( 25 | black_box(&input_close), 26 | black_box(period), 27 | black_box(period), 28 | black_box(MAType::Exponential), 29 | black_box(&mut output), 30 | black_box(&mut output), 31 | black_box(&mut output), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(ohlcv, bench_apo); 43 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/stats/stddev_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::stats::stddev::stddev; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_stddev(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("stddev"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input = generate_test_data(size); 16 | let mut output = vec![0.0; size]; 17 | let mut output_sum = vec![0.0; size]; 18 | let mut output_sum_sq = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = stddev( 27 | black_box(&input), 28 | black_box(period), 29 | black_box(&mut output), 30 | black_box(&mut output_sum), 31 | black_box(&mut output_sum_sq), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(stats, bench_stddev); 43 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/midpoint_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::midpoint::midpoint; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_midpoint(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("midpoint"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input_price = generate_test_data(size); 15 | let mut output_midpoint = vec![0.0; size]; 16 | let mut output_highest = vec![0.0; size]; 17 | let mut output_lowest = vec![0.0; size]; 18 | 19 | for period in &periods { 20 | group.bench_with_input( 21 | BenchmarkId::new(format!("size_{size}"), period), 22 | period, 23 | |b, &period| { 24 | b.iter(|| { 25 | let _ = midpoint( 26 | black_box(&input_price), 27 | black_box(period), 28 | black_box(&mut output_midpoint), 29 | black_box(&mut output_highest), 30 | black_box(&mut output_lowest), 31 | ); 32 | }); 33 | }, 34 | ); 35 | } 36 | } 37 | 38 | group.finish(); 39 | } 40 | 41 | criterion_group!(ohlcv, bench_midpoint); 42 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/tema_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::tema::tema; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_tema(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("tema"); 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | let periods = vec![5, 50, 200]; 11 | 12 | for size in sizes { 13 | let input = generate_test_data(size); 14 | let mut output = vec![0.0; size]; 15 | let mut output_ema1 = vec![0.0; size]; 16 | let mut output_ema2 = vec![0.0; size]; 17 | let mut output_ema3 = vec![0.0; size]; 18 | 19 | for period in &periods { 20 | group.bench_with_input( 21 | BenchmarkId::new(format!("size_{size}"), period), 22 | period, 23 | |b, &period| { 24 | b.iter(|| { 25 | let _ = tema( 26 | black_box(&input), 27 | black_box(period), 28 | black_box(&mut output), 29 | black_box(&mut output_ema1), 30 | black_box(&mut output_ema2), 31 | black_box(&mut output_ema3), 32 | ); 33 | }); 34 | }, 35 | ); 36 | } 37 | } 38 | group.finish(); 39 | } 40 | 41 | criterion_group!(ohlcv, bench_tema); 42 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/trix_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::trix::trix; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_trix(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("trix"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input = generate_test_data(size); 15 | let mut output = vec![0.0; size]; 16 | let mut ema1_output = vec![0.0; size]; 17 | let mut ema2_output = vec![0.0; size]; 18 | let mut ema3_output = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = trix( 27 | black_box(&input), 28 | black_box(period), 29 | black_box(&mut output), 30 | black_box(&mut ema1_output), 31 | black_box(&mut ema2_output), 32 | black_box(&mut ema3_output), 33 | ); 34 | }); 35 | }, 36 | ); 37 | } 38 | } 39 | 40 | group.finish(); 41 | } 42 | 43 | criterion_group!(ohlcv, bench_trix); 44 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/midprice_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::midprice::midprice; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_midprice(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("midprice"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let mut output_midprice = vec![0.0; size]; 17 | let mut output_highest_high = vec![0.0; size]; 18 | let mut output_lowest_low = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = midprice( 27 | black_box(&input_high), 28 | black_box(&input_low), 29 | black_box(period), 30 | black_box(&mut output_midprice), 31 | black_box(&mut output_highest_high), 32 | black_box(&mut output_lowest_low), 33 | ); 34 | }); 35 | }, 36 | ); 37 | } 38 | } 39 | 40 | group.finish(); 41 | } 42 | 43 | criterion_group!(ohlcv, bench_midprice); 44 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/willr_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::willr::willr; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_willr(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("willr"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output = vec![0.0; size]; 18 | let mut output_highest_high = vec![0.0; size]; 19 | let mut output_lowest_low = vec![0.0; size]; 20 | 21 | for period in &periods { 22 | group.bench_with_input( 23 | BenchmarkId::new(format!("size_{size}"), period), 24 | period, 25 | |b, &period| { 26 | b.iter(|| { 27 | let _ = willr( 28 | black_box(&input_high), 29 | black_box(&input_low), 30 | black_box(&input_close), 31 | black_box(period), 32 | black_box(&mut output), 33 | black_box(&mut output_highest_high), 34 | black_box(&mut output_lowest_low), 35 | ); 36 | }); 37 | }, 38 | ); 39 | } 40 | } 41 | 42 | group.finish(); 43 | } 44 | 45 | criterion_group!(ohlcv, bench_willr); 46 | -------------------------------------------------------------------------------- /kand/src/ta/ohlcv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ad; 2 | pub mod adosc; 3 | pub mod adr; 4 | pub mod adx; 5 | pub mod adxr; 6 | // pub mod apo; 7 | pub mod aroon; 8 | pub mod aroonosc; 9 | pub mod atr; 10 | pub mod bbands; 11 | pub mod bop; 12 | pub mod cci; 13 | pub mod cdl_doji; 14 | pub mod cdl_dragonfly_doji; 15 | pub mod cdl_gravestone_doji; 16 | pub mod cdl_hammer; 17 | pub mod cdl_inverted_hammer; 18 | pub mod cdl_long_shadow; 19 | pub mod cdl_marubozu; 20 | // pub mod cdl_spinning_top; 21 | // pub mod cmo; 22 | pub mod dema; 23 | pub mod dx; 24 | pub mod ecl; 25 | pub mod ema; 26 | pub mod ha; 27 | // pub mod ht_dcperiod; 28 | // pub mod ht_dcphase; 29 | // pub mod ht_phasor; 30 | // pub mod ht_sine; 31 | // pub mod ht_trendline; 32 | // pub mod ht_trendmode; 33 | // pub mod kama; 34 | // pub mod linearreg; 35 | // pub mod linearreg_angle; 36 | // pub mod linearreg_intercept; 37 | // pub mod linearreg_slope; 38 | pub mod macd; 39 | // pub mod macdext; 40 | // pub mod mama; 41 | pub mod medprice; 42 | pub mod mfi; 43 | pub mod midpoint; 44 | pub mod midprice; 45 | pub mod minus_di; 46 | pub mod minus_dm; 47 | pub mod mom; 48 | pub mod natr; 49 | pub mod obv; 50 | pub mod plus_di; 51 | pub mod plus_dm; 52 | // pub mod ppo; 53 | pub mod rma; 54 | pub mod roc; 55 | pub mod rocp; 56 | pub mod rocr; 57 | pub mod rocr100; 58 | pub mod rsi; 59 | pub mod sar; 60 | // pub mod sarext; 61 | pub mod sma; 62 | pub mod stoch; 63 | // pub mod stochf; 64 | // pub mod stochrsi; 65 | pub mod supertrend; 66 | pub mod t3; 67 | pub mod tema; 68 | pub mod trange; 69 | pub mod trima; 70 | pub mod trix; 71 | // pub mod tsf; 72 | pub mod typprice; 73 | // pub mod ultosc; 74 | pub mod vegas; 75 | pub mod vwap; 76 | pub mod wclprice; 77 | pub mod willr; 78 | pub mod wma; 79 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/plus_di_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::plus_di::plus_di; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_plus_di(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("plus_di"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let high = generate_test_data(size); 16 | let low = generate_test_data(size); 17 | let close = generate_test_data(size); 18 | let mut output_plus_di = vec![0.0; size]; 19 | let mut output_smoothed_plus_dm = vec![0.0; size]; 20 | let mut output_smoothed_tr = vec![0.0; size]; 21 | 22 | for period in &periods { 23 | group.bench_with_input( 24 | BenchmarkId::new(format!("size_{size}"), period), 25 | period, 26 | |b, &period| { 27 | b.iter(|| { 28 | let _ = plus_di( 29 | black_box(&high), 30 | black_box(&low), 31 | black_box(&close), 32 | black_box(period), 33 | black_box(&mut output_plus_di), 34 | black_box(&mut output_smoothed_plus_dm), 35 | black_box(&mut output_smoothed_tr), 36 | ); 37 | }); 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | group.finish(); 44 | } 45 | 46 | criterion_group!(ohlcv, bench_plus_di); 47 | -------------------------------------------------------------------------------- /.github/workflows/publish-docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Package 2 | 3 | on: 4 | push: 5 | tags: [ 'v*.*.*' ] 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | IMAGE_NAME: ${{ github.repository }} 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v3 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v3 27 | 28 | - name: Log into registry ${{ env.REGISTRY }} 29 | if: github.event_name != 'pull_request' 30 | uses: docker/login-action@v3 31 | with: 32 | registry: ${{ env.REGISTRY }} 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Extract Docker metadata 37 | id: meta 38 | uses: docker/metadata-action@v5 39 | with: 40 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 41 | tags: | 42 | type=ref,event=branch 43 | type=ref,event=pr 44 | type=semver,pattern={{version}} 45 | type=semver,pattern={{major}}.{{minor}} 46 | 47 | - name: Build and push Docker image 48 | uses: docker/build-push-action@v5 49 | with: 50 | context: . 51 | platforms: linux/amd64,linux/arm64 52 | push: ${{ github.event_name != 'pull_request' }} 53 | tags: ${{ steps.meta.outputs.tags }} 54 | labels: ${{ steps.meta.outputs.labels }} 55 | cache-from: type=gha 56 | cache-to: type=gha,mode=max 57 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/minus_di_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::minus_di::minus_di; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_minus_di(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("minus_di"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let high = generate_test_data(size); 16 | let low = generate_test_data(size); 17 | let close = generate_test_data(size); 18 | let mut output_minus_di = vec![0.0; size]; 19 | let mut output_smoothed_minus_dm = vec![0.0; size]; 20 | let mut output_smoothed_tr = vec![0.0; size]; 21 | 22 | for period in &periods { 23 | group.bench_with_input( 24 | BenchmarkId::new(format!("size_{size}"), period), 25 | period, 26 | |b, &period| { 27 | b.iter(|| { 28 | let _ = minus_di( 29 | black_box(&high), 30 | black_box(&low), 31 | black_box(&close), 32 | black_box(period), 33 | black_box(&mut output_minus_di), 34 | black_box(&mut output_smoothed_minus_dm), 35 | black_box(&mut output_smoothed_tr), 36 | ); 37 | }); 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | group.finish(); 44 | } 45 | 46 | criterion_group!(ohlcv, bench_minus_di); 47 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_marubozu_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_marubozu::cdl_marubozu; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_cdl_marubozu(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("cdl_marubozu"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input_open = generate_test_data(size); 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let input_close = generate_test_data(size); 18 | let mut output_signals = vec![0; size]; 19 | let mut output_body_avg = vec![0.0; size]; 20 | 21 | for period in &periods { 22 | group.bench_with_input( 23 | BenchmarkId::new(format!("size_{size}"), period), 24 | period, 25 | |b, &period| { 26 | b.iter(|| { 27 | let _ = cdl_marubozu( 28 | black_box(&input_open), 29 | black_box(&input_high), 30 | black_box(&input_low), 31 | black_box(&input_close), 32 | black_box(period), 33 | black_box(0.1), 34 | black_box(&mut output_signals), 35 | black_box(&mut output_body_avg), 36 | ); 37 | }); 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | group.finish(); 44 | } 45 | 46 | criterion_group!(ohlcv, bench_cdl_marubozu); 47 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_long_shadow_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_long_shadow::cdl_long_shadow; 3 | 4 | use crate::helper::generate_test_data; 5 | #[allow(dead_code)] 6 | fn bench_cdl_long_shadow(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("cdl_long_shadow"); 8 | 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | let periods = vec![5, 50, 200]; 11 | 12 | for size in sizes { 13 | let input_open = generate_test_data(size); 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output_signals = vec![0; size]; 18 | let mut output_body_avg = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = cdl_long_shadow( 27 | black_box(&input_open), 28 | black_box(&input_high), 29 | black_box(&input_low), 30 | black_box(&input_close), 31 | black_box(period), 32 | black_box(0.5), 33 | black_box(&mut output_signals), 34 | black_box(&mut output_body_avg), 35 | ); 36 | }); 37 | }, 38 | ); 39 | } 40 | } 41 | 42 | group.finish(); 43 | } 44 | 45 | criterion_group!(ohlcv, bench_cdl_long_shadow); 46 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cci_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cci::cci; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_cci(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("cci"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let input_close = generate_test_data(size); 18 | let mut output_cci = vec![0.0; size]; 19 | let mut output_tp = vec![0.0; size]; 20 | let mut output_sma_tp = vec![0.0; size]; 21 | let mut output_mean_dev = vec![0.0; size]; 22 | 23 | for period in &periods { 24 | group.bench_with_input( 25 | BenchmarkId::new(format!("size_{size}"), period), 26 | period, 27 | |b, &period| { 28 | b.iter(|| { 29 | let _ = cci( 30 | black_box(&input_high), 31 | black_box(&input_low), 32 | black_box(&input_close), 33 | black_box(period), 34 | black_box(&mut output_cci), 35 | black_box(&mut output_tp), 36 | black_box(&mut output_sma_tp), 37 | black_box(&mut output_mean_dev), 38 | ); 39 | }); 40 | }, 41 | ); 42 | } 43 | } 44 | 45 | group.finish(); 46 | } 47 | 48 | criterion_group!(ohlcv, bench_cci); 49 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/cdl_inverted_hammer_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::cdl_inverted_hammer::cdl_inverted_hammer; 3 | 4 | use crate::helper::generate_test_data; 5 | #[allow(dead_code)] 6 | fn bench_cdl_inverted_hammer(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("cdl_inverted_hammer"); 8 | 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | let periods = vec![5, 50, 200]; 11 | 12 | for size in sizes { 13 | let input_open = generate_test_data(size); 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let mut output_signals = vec![0; size]; 18 | let mut output_body_avg = vec![0.0; size]; 19 | 20 | for period in &periods { 21 | group.bench_with_input( 22 | BenchmarkId::new(format!("size_{size}"), period), 23 | period, 24 | |b, &period| { 25 | b.iter(|| { 26 | let _ = cdl_inverted_hammer( 27 | black_box(&input_open), 28 | black_box(&input_high), 29 | black_box(&input_low), 30 | black_box(&input_close), 31 | black_box(period), 32 | black_box(0.1), 33 | black_box(&mut output_signals), 34 | black_box(&mut output_body_avg), 35 | ); 36 | }); 37 | }, 38 | ); 39 | } 40 | } 41 | 42 | group.finish(); 43 | } 44 | 45 | criterion_group!(ohlcv, bench_cdl_inverted_hammer); 46 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/dx_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::dx::dx; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_dx(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("dx"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let input_close = generate_test_data(size); 18 | let mut output_dx = vec![0.0; size]; 19 | let mut output_smoothed_plus_dm = vec![0.0; size]; 20 | let mut output_smoothed_minus_dm = vec![0.0; size]; 21 | let mut output_smoothed_tr = vec![0.0; size]; 22 | 23 | for period in &periods { 24 | group.bench_with_input( 25 | BenchmarkId::new(format!("size_{size}"), period), 26 | period, 27 | |b, &period| { 28 | b.iter(|| { 29 | let _ = dx( 30 | black_box(&input_high), 31 | black_box(&input_low), 32 | black_box(&input_close), 33 | black_box(period), 34 | black_box(&mut output_dx), 35 | black_box(&mut output_smoothed_plus_dm), 36 | black_box(&mut output_smoothed_minus_dm), 37 | black_box(&mut output_smoothed_tr), 38 | ); 39 | }); 40 | }, 41 | ); 42 | } 43 | } 44 | 45 | group.finish(); 46 | } 47 | 48 | criterion_group!(ohlcv, bench_dx); 49 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/adx_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::adx::adx; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_adx(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("adx"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let input_close = generate_test_data(size); 18 | let mut output_adx = vec![0.0; size]; 19 | let mut output_smoothed_plus_dm = vec![0.0; size]; 20 | let mut output_smoothed_minus_dm = vec![0.0; size]; 21 | let mut output_smoothed_tr = vec![0.0; size]; 22 | 23 | for period in &periods { 24 | group.bench_with_input( 25 | BenchmarkId::new(format!("size_{size}"), period), 26 | period, 27 | |b, &period| { 28 | b.iter(|| { 29 | let _ = adx( 30 | black_box(&input_high), 31 | black_box(&input_low), 32 | black_box(&input_close), 33 | black_box(period), 34 | black_box(&mut output_adx), 35 | black_box(&mut output_smoothed_plus_dm), 36 | black_box(&mut output_smoothed_minus_dm), 37 | black_box(&mut output_smoothed_tr), 38 | ); 39 | }); 40 | }, 41 | ); 42 | } 43 | } 44 | 45 | group.finish(); 46 | } 47 | 48 | criterion_group!(ohlcv, bench_adx); 49 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/aroonosc_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::aroonosc::aroonosc; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_aroonosc(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("aroonosc"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let mut output_aroonosc = vec![0.0; size]; 18 | let mut output_prev_high = vec![0.0; size]; 19 | let mut output_prev_low = vec![0.0; size]; 20 | let mut output_days_since_high = vec![0; size]; 21 | let mut output_days_since_low = vec![0; size]; 22 | 23 | for period in &periods { 24 | group.bench_with_input( 25 | BenchmarkId::new(format!("size_{size}"), period), 26 | period, 27 | |b, &period| { 28 | b.iter(|| { 29 | let _ = aroonosc( 30 | black_box(&input_high), 31 | black_box(&input_low), 32 | black_box(period), 33 | black_box(&mut output_aroonosc), 34 | black_box(&mut output_prev_high), 35 | black_box(&mut output_prev_low), 36 | black_box(&mut output_days_since_high), 37 | black_box(&mut output_days_since_low), 38 | ); 39 | }); 40 | }, 41 | ); 42 | } 43 | } 44 | 45 | group.finish(); 46 | } 47 | 48 | criterion_group!(ohlcv, bench_aroonosc); 49 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/supertrend_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::supertrend::supertrend; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_supertrend(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("supertrend"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | let multiplier = 3.0; 14 | 15 | for size in sizes { 16 | let input = generate_test_data(size); 17 | let mut trend = vec![0; size]; 18 | let mut supertrend_output = vec![0.0; size]; 19 | let mut atr = vec![0.0; size]; 20 | let mut upper = vec![0.0; size]; 21 | let mut lower = vec![0.0; size]; 22 | 23 | for period in &periods { 24 | group.bench_with_input( 25 | BenchmarkId::new(format!("size_{size}"), period), 26 | period, 27 | |b, &period| { 28 | b.iter(|| { 29 | let _ = supertrend( 30 | black_box(&input), // high 31 | black_box(&input), // low 32 | black_box(&input), // close 33 | black_box(period), 34 | black_box(multiplier), 35 | black_box(&mut trend), 36 | black_box(&mut supertrend_output), 37 | black_box(&mut atr), 38 | black_box(&mut upper), 39 | black_box(&mut lower), 40 | ); 41 | }); 42 | }, 43 | ); 44 | } 45 | } 46 | 47 | group.finish(); 48 | } 49 | 50 | criterion_group!(ohlcv, bench_supertrend); 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Rust project using Cargo 2 | 3 | .PHONY: all 4 | all: pre-commit 5 | 6 | # Build the project with all features enabled in release mode 7 | .PHONY: build 8 | build: 9 | cargo build --release --all-features 10 | 11 | # Update dependencies to their latest compatible versions 12 | .PHONY: update 13 | update: 14 | cargo update 15 | 16 | # Run the project with all features enabled in release mode 17 | .PHONY: run 18 | run: 19 | cargo run --release --all-features 20 | 21 | # Run all tests with all features enabled 22 | .PHONY: test 23 | test: 24 | cargo test --all-features 25 | 26 | # Run benchmarks with all features enabled 27 | .PHONY: bench 28 | bench: 29 | cargo bench --all-features 30 | 31 | # Run Clippy linter with nightly toolchain, fixing issues automatically 32 | # and applying strict linting rules 33 | .PHONY: clippy 34 | clippy: 35 | cargo +nightly clippy --fix \ 36 | --all-targets \ 37 | --all-features \ 38 | --allow-dirty \ 39 | --allow-staged \ 40 | -- -D warnings \ 41 | -W clippy::pedantic \ 42 | -W clippy::nursery \ 43 | -W clippy::unwrap_used \ 44 | -W clippy::expect_used 45 | 46 | # Format the code using rustfmt with nightly toolchain 47 | .PHONY: fmt 48 | fmt: 49 | cargo +nightly fmt 50 | 51 | # Generate documentation for all crates and open it in the browser 52 | .PHONY: doc 53 | doc: 54 | cargo +nightly doc --all-features --no-deps --open 55 | 56 | # Generate CHANGELOG.md using git-cliff 57 | .PHONY: cliff 58 | cliff: 59 | git-cliff 60 | git cliff --output CHANGELOG.md 61 | 62 | # Sync Python environment using uv 63 | .PHONY: uv-sync 64 | uv-sync: 65 | uv venv 66 | uv lock --upgrade 67 | uv sync 68 | uv run "./scripts/gen_stub.py" kand kand-py/python/kand/_kand.pyi 69 | 70 | # Run pre-commit hooks on all files 71 | .PHONY: pre-commit 72 | pre-commit: 73 | $(MAKE) build 74 | $(MAKE) test 75 | $(MAKE) clippy 76 | $(MAKE) fmt 77 | $(MAKE) cliff 78 | $(MAKE) uv-sync 79 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/adxr_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::adxr::adxr; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_adxr(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("adxr"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let input_close = generate_test_data(size); 18 | let mut output_adxr = vec![0.0; size]; 19 | let mut output_adx = vec![0.0; size]; 20 | let mut output_smoothed_plus_dm = vec![0.0; size]; 21 | let mut output_smoothed_minus_dm = vec![0.0; size]; 22 | let mut output_smoothed_tr = vec![0.0; size]; 23 | 24 | for period in &periods { 25 | group.bench_with_input( 26 | BenchmarkId::new(format!("size_{size}"), period), 27 | period, 28 | |b, &period| { 29 | b.iter(|| { 30 | let _ = adxr( 31 | black_box(&input_high), 32 | black_box(&input_low), 33 | black_box(&input_close), 34 | black_box(period), 35 | black_box(&mut output_adxr), 36 | black_box(&mut output_adx), 37 | black_box(&mut output_smoothed_plus_dm), 38 | black_box(&mut output_smoothed_minus_dm), 39 | black_box(&mut output_smoothed_tr), 40 | ); 41 | }); 42 | }, 43 | ); 44 | } 45 | } 46 | 47 | group.finish(); 48 | } 49 | 50 | criterion_group!(ohlcv, bench_adxr); 51 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.7,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "kand" 7 | description = "A high-performance technical analysis library written in Rust with Python bindings." 8 | authors = [{ name = "CtrlX", email = "gitctrlx@gmail.com" }] 9 | requires-python = ">=3.8" 10 | keywords = [ 11 | "ta", "ta-lib", "finance", "quant", "indicator", "technical-analysis" 12 | ] 13 | classifiers = [ 14 | "License :: OSI Approved :: MIT License", 15 | "License :: OSI Approved :: Apache Software License", 16 | "Programming Language :: Rust", 17 | "Programming Language :: Python :: Implementation :: CPython", 18 | "Programming Language :: Python :: Implementation :: PyPy", 19 | "Programming Language :: Python", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Programming Language :: Python :: 3.12", 25 | "Programming Language :: Python :: 3.13", 26 | "Programming Language :: Python :: 3 :: Only", 27 | ] 28 | dynamic = ["version"] 29 | readme = "README.md" 30 | dependencies = [ 31 | "numpy>=1.24.0,<1.26.0; python_version<'3.9'", 32 | "numpy>=1.26.0; python_version>='3.9'", 33 | ] 34 | 35 | [project.urls] 36 | Repository = "https://github.com/rust-ta/kand" 37 | Documentation = "https://docs.rs/kand" 38 | Changelog = "https://github.com/rust-ta/kand/blob/main/CHANGELOG.md" 39 | Releases = "https://github.com/rust-ta/kand/releases" 40 | 41 | [tool.maturin] 42 | bindings = "pyo3" 43 | manifest-path = "kand-py/Cargo.toml" 44 | module-name = "kand._kand" 45 | python-source = "kand-py/python" 46 | features = ["pyo3/extension-module", "f64", "i64", "check"] 47 | include = [ 48 | { path = "rust-toolchain.toml", format = ["sdist", "wheel"] }, 49 | { path = "LICENSE-APACHE", format = "sdist" }, 50 | { path = "LICENSE-MIT", format = "sdist" }, 51 | ] 52 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/bbands_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::bbands::bbands; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_bbands(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("bbands"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input_price = generate_test_data(size); 15 | let mut output_upper = vec![0.0; size]; 16 | let mut output_middle = vec![0.0; size]; 17 | let mut output_lower = vec![0.0; size]; 18 | let mut output_sma = vec![0.0; size]; 19 | let mut output_var = vec![0.0; size]; 20 | let mut output_sum = vec![0.0; size]; 21 | let mut output_sum_sq = vec![0.0; size]; 22 | 23 | for period in &periods { 24 | group.bench_with_input( 25 | BenchmarkId::new(format!("size_{size}"), period), 26 | period, 27 | |b, &period| { 28 | b.iter(|| { 29 | let _ = bbands( 30 | black_box(&input_price), 31 | black_box(period), 32 | black_box(2.0), 33 | black_box(2.0), 34 | black_box(&mut output_upper), 35 | black_box(&mut output_middle), 36 | black_box(&mut output_lower), 37 | black_box(&mut output_sma), 38 | black_box(&mut output_var), 39 | black_box(&mut output_sum), 40 | black_box(&mut output_sum_sq), 41 | ); 42 | }); 43 | }, 44 | ); 45 | } 46 | } 47 | 48 | group.finish(); 49 | } 50 | 51 | criterion_group!(ohlcv, bench_bbands); 52 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/aroon_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ta::ohlcv::aroon::aroon; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_aroon(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("aroon"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let periods = vec![5, 50, 200]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let mut output_aroon_up = vec![0.0; size]; 18 | let mut output_aroon_down = vec![0.0; size]; 19 | let mut output_prev_high = vec![0.0; size]; 20 | let mut output_prev_low = vec![0.0; size]; 21 | let mut output_days_since_high = vec![0; size]; 22 | let mut output_days_since_low = vec![0; size]; 23 | 24 | for period in &periods { 25 | group.bench_with_input( 26 | BenchmarkId::new(format!("size_{size}"), period), 27 | period, 28 | |b, &period| { 29 | b.iter(|| { 30 | let _ = aroon( 31 | black_box(&input_high), 32 | black_box(&input_low), 33 | black_box(period), 34 | black_box(&mut output_aroon_up), 35 | black_box(&mut output_aroon_down), 36 | black_box(&mut output_prev_high), 37 | black_box(&mut output_prev_low), 38 | black_box(&mut output_days_since_high), 39 | black_box(&mut output_days_since_low), 40 | ); 41 | }); 42 | }, 43 | ); 44 | } 45 | } 46 | 47 | group.finish(); 48 | } 49 | 50 | criterion_group!(ohlcv, bench_aroon); 51 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/mfi_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::mfi::mfi; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_mfi(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("mfi"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let periods = vec![5, 50, 200]; 12 | 13 | for size in sizes { 14 | let input_high = generate_test_data(size); 15 | let input_low = generate_test_data(size); 16 | let input_close = generate_test_data(size); 17 | let input_volume = generate_test_data(size); 18 | let mut output_mfi = vec![0.0; size]; 19 | let mut output_typ_prices = vec![0.0; size]; 20 | let mut output_money_flows = vec![0.0; size]; 21 | let mut output_pos_flows = vec![0.0; size]; 22 | let mut output_neg_flows = vec![0.0; size]; 23 | 24 | for period in &periods { 25 | group.bench_with_input( 26 | BenchmarkId::new(format!("size_{size}"), period), 27 | period, 28 | |b, &period| { 29 | b.iter(|| { 30 | let _ = mfi( 31 | black_box(&input_high), 32 | black_box(&input_low), 33 | black_box(&input_close), 34 | black_box(&input_volume), 35 | black_box(period), 36 | black_box(&mut output_mfi), 37 | black_box(&mut output_typ_prices), 38 | black_box(&mut output_money_flows), 39 | black_box(&mut output_pos_flows), 40 | black_box(&mut output_neg_flows), 41 | ); 42 | }); 43 | }, 44 | ); 45 | } 46 | } 47 | 48 | group.finish(); 49 | } 50 | 51 | criterion_group!(ohlcv, bench_mfi); 52 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/t3_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::t3::t3; 3 | 4 | use crate::helper::generate_test_data; 5 | #[allow(dead_code)] 6 | fn bench_t3(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("t3"); 8 | 9 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 10 | let periods = vec![5, 50, 200]; 11 | let vfactors = vec![0.5, 0.7, 0.9]; 12 | 13 | for size in sizes { 14 | let input = generate_test_data(size); 15 | let mut output = vec![0.0; size]; 16 | let mut ema1 = vec![0.0; size]; 17 | let mut ema2 = vec![0.0; size]; 18 | let mut ema3 = vec![0.0; size]; 19 | let mut ema4 = vec![0.0; size]; 20 | let mut ema5 = vec![0.0; size]; 21 | let mut ema6 = vec![0.0; size]; 22 | 23 | for period in &periods { 24 | for vfactor in &vfactors { 25 | group.bench_with_input( 26 | BenchmarkId::new(format!("size_{size}_vfactor_{vfactor:.1}"), period), 27 | &(*period, *vfactor), 28 | |b, &(period, vfactor)| { 29 | b.iter(|| { 30 | let _ = t3( 31 | black_box(&input), 32 | black_box(period), 33 | black_box(vfactor), 34 | black_box(&mut output), 35 | black_box(&mut ema1), 36 | black_box(&mut ema2), 37 | black_box(&mut ema3), 38 | black_box(&mut ema4), 39 | black_box(&mut ema5), 40 | black_box(&mut ema6), 41 | ); 42 | }); 43 | }, 44 | ); 45 | } 46 | } 47 | } 48 | 49 | group.finish(); 50 | } 51 | 52 | criterion_group!(ohlcv, bench_t3); 53 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/ecl_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::ecl::ecl; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | #[allow(clippy::similar_names)] 8 | fn bench_ecl(c: &mut Criterion) { 9 | let mut group = c.benchmark_group("ecl"); 10 | 11 | // Test different data sizes 12 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 13 | 14 | for size in sizes { 15 | let input_high = generate_test_data(size); 16 | let input_low = generate_test_data(size); 17 | let input_close = generate_test_data(size); 18 | let mut output_h5 = vec![0.0; size]; 19 | let mut output_h4 = vec![0.0; size]; 20 | let mut output_h3 = vec![0.0; size]; 21 | let mut output_h2 = vec![0.0; size]; 22 | let mut output_h1 = vec![0.0; size]; 23 | let mut output_l1 = vec![0.0; size]; 24 | let mut output_l2 = vec![0.0; size]; 25 | let mut output_l3 = vec![0.0; size]; 26 | let mut output_l4 = vec![0.0; size]; 27 | let mut output_l5 = vec![0.0; size]; 28 | 29 | group.bench_with_input(BenchmarkId::new("size", size), &size, |b, &_size| { 30 | b.iter(|| { 31 | let _ = ecl( 32 | black_box(&input_high), 33 | black_box(&input_low), 34 | black_box(&input_close), 35 | black_box(&mut output_h5), 36 | black_box(&mut output_h4), 37 | black_box(&mut output_h3), 38 | black_box(&mut output_h2), 39 | black_box(&mut output_h1), 40 | black_box(&mut output_l1), 41 | black_box(&mut output_l2), 42 | black_box(&mut output_l3), 43 | black_box(&mut output_l4), 44 | black_box(&mut output_l5), 45 | ); 46 | }); 47 | }); 48 | } 49 | 50 | group.finish(); 51 | } 52 | 53 | criterion_group!(ohlcv, bench_ecl); 54 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | Get started with `kand` through Python or Docker. This guide covers all installation methods and system compatibility details. 4 | 5 | ## Python Installation 6 | 7 | ### Requirements 8 | - Python 3.8+ 9 | - `pip` (Python package installer) 10 | 11 | ### Install from PyPI 12 | Install `kand` with one command—precompiled wheels available for instant setup: 13 | 14 | ```bash 15 | pip install kand 16 | ``` 17 | 18 | !!! tip "Supported Platforms & Python Versions" 19 | We provide precompiled packages on PyPI for major systems and Python versions: 20 | 21 | | Platform | Supported Python Versions | 22 | |--------------|-----------------------------------| 23 | | **Linux** | 3.8, 3.9, 3.10, 3.11, 3.12 | 24 | | **musl Linux** | 3.8, 3.9, 3.10, 3.11, 3.12 | 25 | | **Windows** | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 | 26 | | **macOS** | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 | 27 | 28 | No compilation needed—just `pip install` and go! 29 | 30 | ## Docker Usage 31 | 32 | ### Pull the Official Image 33 | Grab the latest `kand` container: 34 | 35 | ```bash 36 | docker pull ghcr.io/rust-ta/kand:latest 37 | ``` 38 | 39 | ### Run with Docker 40 | Launch it interactively: 41 | 42 | ```bash 43 | docker run -it --rm ghcr.io/rust-ta/kand:latest 44 | ``` 45 | 46 | Or build your own: 47 | 48 | ```bash 49 | docker build -t my-kand-app . 50 | docker run -it --rm my-kand-app 51 | ``` 52 | 53 | ## Troubleshooting 54 | 55 | Encounter issues? Try these steps: 56 | 57 | 1. Update `pip` or `cargo` to the latest version. 58 | 2. Verify Python (3.8+) or Rust (1.80+) compatibility. 59 | 3. Ensure the Docker daemon is running. 60 | 4. Check [GitHub Issues](https://github.com/rust-ta/kand/issues) for solutions. 61 | 62 | !!! note 63 | Still stuck? Join our community or file an issue on GitHub! 64 | 65 | ## Next Steps 66 | 67 | - Explore the [API Documentation](api.md) to dive into `kand`. 68 | - Join our community for help and updates. 69 | - Report bugs or suggestions on [GitHub](https://github.com/rust-ta/kand/issues). 70 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ad_bench; 2 | pub mod adosc_bench; 3 | pub mod adr_bench; 4 | pub mod adx_bench; 5 | pub mod adxr_bench; 6 | // pub mod apo_bench; 7 | pub mod aroon_bench; 8 | pub mod aroonosc_bench; 9 | pub mod atr_bench; 10 | pub mod bbands_bench; 11 | pub mod bop_bench; 12 | pub mod cci_bench; 13 | pub mod cdl_doji_bench; 14 | pub mod cdl_dragonfly_doji_bench; 15 | pub mod cdl_gravestone_doji_bench; 16 | pub mod cdl_hammer_bench; 17 | pub mod cdl_inverted_hammer_bench; 18 | pub mod cdl_long_shadow_bench; 19 | pub mod cdl_marubozu_bench; 20 | // pub mod cdl_spinning_top_bench; 21 | // pub mod cmo_bench; 22 | pub mod dema_bench; 23 | pub mod dx_bench; 24 | pub mod ecl_bench; 25 | pub mod ema_bench; 26 | // pub mod ht_dcperiod_bench; 27 | // pub mod ht_dcphase_bench; 28 | // pub mod ht_phasor_bench; 29 | // pub mod ht_sine_bench; 30 | // pub mod ht_trendline_bench; 31 | // pub mod ht_trendmode_bench; 32 | // pub mod kama_bench; 33 | // pub mod linearreg_angle_bench; 34 | // pub mod linearreg_bench; 35 | // pub mod linearreg_intercept_bench; 36 | // pub mod linearreg_slope_bench; 37 | pub mod macd_bench; 38 | // pub mod macdext_bench; 39 | // pub mod mama_bench; 40 | pub mod medprice_bench; 41 | pub mod mfi_bench; 42 | pub mod midpoint_bench; 43 | pub mod midprice_bench; 44 | pub mod minus_di_bench; 45 | pub mod minus_dm_bench; 46 | pub mod mom_bench; 47 | pub mod natr_bench; 48 | pub mod obv_bench; 49 | pub mod plus_di_bench; 50 | pub mod plus_dm_bench; 51 | // pub mod ppo_bench; 52 | pub mod rma_bench; 53 | pub mod roc_bench; 54 | pub mod rocp_bench; 55 | pub mod rocr100_bench; 56 | pub mod rocr_bench; 57 | pub mod rsi_bench; 58 | pub mod sar_bench; 59 | // pub mod sarext_bench; 60 | pub mod sma_bench; 61 | pub mod stoch_bench; 62 | // pub mod stochf_bench; 63 | // pub mod stochrsi_bench; 64 | pub mod supertrend_bench; 65 | pub mod t3_bench; 66 | pub mod tema_bench; 67 | pub mod trange_bench; 68 | pub mod trima_bench; 69 | pub mod trix_bench; 70 | // pub mod tsf_bench; 71 | pub mod typprice_bench; 72 | // pub mod ultosc_bench; 73 | pub mod vegas_bench; 74 | pub mod vwap_bench; 75 | pub mod wclprice_bench; 76 | pub mod willr_bench; 77 | pub mod wma_bench; 78 | -------------------------------------------------------------------------------- /kand/examples/sma.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use kand::ohlcv::sma::sma; 4 | use rayon::prelude::*; 5 | 6 | #[allow(clippy::unwrap_used)] 7 | fn main() { 8 | // Generate sample prices with more realistic data 9 | let prices: Vec = (0..10_000_000) 10 | .map(|i| f64::from(i).sin().mul_add(100.0, 1000.0)) 11 | .collect(); 12 | let mut seq_output = vec![0.0; prices.len()]; 13 | let mut par_outputs: Vec> = (0..10).map(|_| vec![0.0; prices.len()]).collect(); 14 | 15 | // Common SMA parameters 16 | let period = 14; 17 | let iterations = 16; 18 | 19 | println!("=== SMA Performance Test ==="); 20 | println!("Data points: {}", prices.len()); 21 | println!("Period: {period}"); 22 | println!("Iterations: {iterations}"); 23 | println!("-------------------------"); 24 | 25 | // Warm up the CPU 26 | for _ in 0..10 { 27 | sma(&prices, period, &mut seq_output).unwrap(); 28 | } 29 | 30 | // Time sequential execution 31 | let start = Instant::now(); 32 | for _ in 0..iterations { 33 | sma(&prices, period, &mut seq_output).unwrap(); 34 | } 35 | let seq_duration = start.elapsed(); 36 | let seq_avg = seq_duration / iterations; 37 | 38 | // Time parallel execution 39 | let start = Instant::now(); 40 | par_outputs.par_iter_mut().for_each(|output| { 41 | sma(&prices, period, output).unwrap(); 42 | }); 43 | let par_duration = start.elapsed(); 44 | let par_avg = par_duration / iterations; 45 | 46 | println!("\nSequential Execution"); 47 | println!(" Total time: {seq_duration:.2?}"); 48 | println!(" Avg time/iter: {seq_avg:.2?}"); 49 | println!( 50 | " Last 5 results: {:?}", 51 | &seq_output[seq_output.len() - 5..] 52 | ); 53 | 54 | println!("\nParallel Execution"); 55 | println!(" Total time: {par_duration:.2?}"); 56 | println!(" Avg time/iter: {par_avg:.2?}"); 57 | println!( 58 | " Last 5 results: {:?}", 59 | &par_outputs[0][par_outputs[0].len() - 5..] 60 | ); 61 | 62 | println!( 63 | "\nSpeedup: {:.2}x", 64 | seq_duration.as_secs_f64() / par_duration.as_secs_f64() 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/adosc_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::adosc::adosc; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_adosc(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("adosc"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let fast_periods = [3, 5, 10]; 13 | let slow_periods = [10, 20, 30]; 14 | 15 | for size in sizes { 16 | let high = generate_test_data(size); 17 | let low = generate_test_data(size); 18 | let close = generate_test_data(size); 19 | let volume = generate_test_data(size); 20 | let mut output_adosc = vec![0.0; size]; 21 | let mut output_ad = vec![0.0; size]; 22 | let mut output_fast_ema = vec![0.0; size]; 23 | let mut output_slow_ema = vec![0.0; size]; 24 | 25 | for (fast_period, slow_period) in fast_periods.iter().zip(slow_periods.iter()) { 26 | group.bench_with_input( 27 | BenchmarkId::new( 28 | format!("size_{size}_fast_{fast_period}_slow_{slow_period}"), 29 | format!("{fast_period}-{slow_period}"), 30 | ), 31 | &(fast_period, slow_period), 32 | |b, &(fast_period, slow_period)| { 33 | b.iter(|| { 34 | let _ = adosc( 35 | black_box(&high), 36 | black_box(&low), 37 | black_box(&close), 38 | black_box(&volume), 39 | black_box(*fast_period), 40 | black_box(*slow_period), 41 | black_box(&mut output_adosc), 42 | black_box(&mut output_ad), 43 | black_box(&mut output_fast_ema), 44 | black_box(&mut output_slow_ema), 45 | ); 46 | }); 47 | }, 48 | ); 49 | } 50 | } 51 | 52 | group.finish(); 53 | } 54 | 55 | criterion_group!(ohlcv, bench_adosc); 56 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/stoch_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::stoch::stoch; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_stoch(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("stoch"); 9 | 10 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 11 | let k_periods = [5, 14, 30]; 12 | let k_slow_periods = vec![3, 5, 9]; 13 | let d_periods = vec![3, 5, 9]; 14 | 15 | for size in sizes { 16 | let input_high = generate_test_data(size); 17 | let input_low = generate_test_data(size); 18 | let input_close = generate_test_data(size); 19 | let mut output_fast_k = vec![0.0; size]; 20 | let mut output_k = vec![0.0; size]; 21 | let mut output_d = vec![0.0; size]; 22 | 23 | for (&k_period, &k_slow_period, &d_period) in k_periods 24 | .iter() 25 | .zip(&k_slow_periods) 26 | .zip(&d_periods) 27 | .map(|((a, b), c)| (a, b, c)) 28 | { 29 | group.bench_with_input( 30 | BenchmarkId::new( 31 | format!("size_{size}_k{k_period}_ks{k_slow_period}_d{d_period}"), 32 | format!("{k_period}-{k_slow_period}-{d_period}"), 33 | ), 34 | &(k_period, k_slow_period, d_period), 35 | |b, &(k_period, k_slow_period, d_period)| { 36 | b.iter(|| { 37 | let _ = stoch( 38 | black_box(&input_high), 39 | black_box(&input_low), 40 | black_box(&input_close), 41 | black_box(k_period), 42 | black_box(k_slow_period), 43 | black_box(d_period), 44 | black_box(&mut output_fast_k), 45 | black_box(&mut output_k), 46 | black_box(&mut output_d), 47 | ); 48 | }); 49 | }, 50 | ); 51 | } 52 | } 53 | 54 | group.finish(); 55 | } 56 | 57 | criterion_group!(ohlcv, bench_stoch); 58 | -------------------------------------------------------------------------------- /kand/benches/benchmarks/ohlcv/macd_bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use kand::ohlcv::macd::macd; 3 | 4 | use crate::helper::generate_test_data; 5 | 6 | #[allow(dead_code)] 7 | fn bench_macd(c: &mut Criterion) { 8 | let mut group = c.benchmark_group("macd"); 9 | 10 | // Test different data sizes 11 | let sizes = vec![100_000, 1_000_000, 10_000_000]; 12 | let fast_periods = [12, 26, 9]; // Standard MACD periods 13 | let slow_periods = vec![26, 52, 18]; 14 | let signal_periods = vec![9, 18, 6]; 15 | 16 | for size in sizes { 17 | let input = generate_test_data(size); 18 | let mut macd_line = vec![0.0; size]; 19 | let mut signal_line = vec![0.0; size]; 20 | let mut histogram = vec![0.0; size]; 21 | let mut fast_ema = vec![0.0; size]; 22 | let mut slow_ema = vec![0.0; size]; 23 | 24 | for ((fast_period, slow_period), signal_period) in 25 | fast_periods.iter().zip(&slow_periods).zip(&signal_periods) 26 | { 27 | group.bench_with_input( 28 | BenchmarkId::new( 29 | format!( 30 | "size_{size}_fast_{fast_period}_slow_{slow_period}_signal_{signal_period}" 31 | ), 32 | format!("{fast_period}-{slow_period}-{signal_period}"), 33 | ), 34 | &(*fast_period, *slow_period, *signal_period), 35 | |b, &(fast_period, slow_period, signal_period)| { 36 | b.iter(|| { 37 | let _ = macd( 38 | black_box(&input), 39 | black_box(fast_period), 40 | black_box(slow_period), 41 | black_box(signal_period), 42 | black_box(&mut macd_line), 43 | black_box(&mut signal_line), 44 | black_box(&mut histogram), 45 | black_box(&mut fast_ema), 46 | black_box(&mut slow_ema), 47 | ); 48 | }); 49 | }, 50 | ); 51 | } 52 | } 53 | 54 | group.finish(); 55 | } 56 | 57 | criterion_group!(ohlcv, bench_macd); 58 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/medprice.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::medprice}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Calculates the Median Price (MEDPRICE) for a NumPy array. 6 | /// 7 | /// The Median Price is a technical analysis indicator that represents the middle point between 8 | /// high and low prices for each period. 9 | /// 10 | /// Args: 11 | /// high: Array of high prices as a 1-D NumPy array of type `TAFloat`. 12 | /// low: Array of low prices as a 1-D NumPy array of type `TAFloat`. 13 | /// 14 | /// Returns: 15 | /// A 1-D NumPy array containing the median price values. 16 | /// 17 | /// Examples: 18 | /// ```python 19 | /// >>> import numpy as np 20 | /// >>> import kand 21 | /// >>> high = np.array([10.0, 11.0, 12.0]) 22 | /// >>> low = np.array([8.0, 9.0, 10.0]) 23 | /// >>> result = kand.medprice(high, low) 24 | /// >>> print(result) 25 | /// [9.0, 10.0, 11.0] 26 | /// ``` 27 | #[pyfunction] 28 | #[pyo3(name = "medprice")] 29 | pub fn medprice_py( 30 | py: Python, 31 | high: PyReadonlyArray1, 32 | low: PyReadonlyArray1, 33 | ) -> PyResult>> { 34 | let high_slice = high.as_slice()?; 35 | let low_slice = low.as_slice()?; 36 | let len = high_slice.len(); 37 | let mut output = vec![0.0; len]; 38 | 39 | py.allow_threads(|| medprice::medprice(high_slice, low_slice, output.as_mut_slice())) 40 | .map_err(|e| PyErr::new::(e.to_string()))?; 41 | 42 | Ok(output.into_pyarray(py).into()) 43 | } 44 | 45 | /// Calculates a single Median Price value incrementally. 46 | /// 47 | /// Args: 48 | /// 49 | /// high: Current period's high price as `TAFloat`. 50 | /// low: Current period's low price as `TAFloat`. 51 | /// 52 | /// Returns: 53 | /// The calculated median price value. 54 | /// 55 | /// Examples: 56 | /// ```python 57 | /// >>> import kand 58 | /// >>> result = kand.medprice_inc(10.0, 8.0) 59 | /// >>> print(result) 60 | /// 9.0 61 | /// ``` 62 | #[pyfunction] 63 | #[pyo3(name = "medprice_inc")] 64 | pub fn medprice_inc_py(py: Python, high: TAFloat, low: TAFloat) -> PyResult { 65 | py.allow_threads(|| medprice::medprice_inc(high, low)) 66 | .map_err(|e| PyErr::new::(e.to_string())) 67 | } 68 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This document records all significant updates and changes to the Kand project. 4 | 5 | ## [unreleased] 6 | 7 | ### 🐛 Bug Fixes 8 | 9 | - *(ci)* Fix publish-rust 10 | - *(.editorconfig)* Fix path 11 | 12 | ## [0.2.2] - 2025-03-04 13 | 14 | ### 🚀 Features 15 | 16 | - *(precision)* Add f32 floating-point precision support (#10) 17 | 18 | ### 🐛 Bug Fixes 19 | 20 | - *(tema)* Resolve ambiguous numeric type errors in TEMA calculation 21 | - *(tema)* Resolve ambiguous numeric type errors in TEMA calculation 22 | - *(willr)* Resolve Clippy warnings for strict float comparisons 23 | - *(stats)* Resolve Clippy warnings for strict float comparisons in max/min 24 | - *(ci)* Fix test-rust 25 | 26 | ### 🚜 Refactor 27 | 28 | - Use _inc instead of _incremental 29 | 30 | ## [0.2.1] - 2025-03-02 31 | 32 | ### 🚀 Features 33 | 34 | - *(precision)* Add f32 floating-point precision support 35 | 36 | ## [0.2.0] - 2025-03-02 37 | 38 | ### 🚀 Features 39 | 40 | - [**breaking**] Release v0.2.0 with major type system refactoring 41 | 42 | ### 🐛 Bug Fixes 43 | 44 | - *(ci:publish-doc)* Update publish-doc 45 | - *(makefile)* Fix uv-sync, add params for gen_stub.py 46 | 47 | ### 💼 Other 48 | 49 | - Update the types and lib type 50 | 51 | ## [0.1.3] - 2025-02-27 52 | 53 | ### 🚜 Refactor 54 | 55 | - *(ci:release)* Refactor release ci 56 | 57 | ## [0.1.2] - 2025-02-27 58 | 59 | ### 🐛 Bug Fixes 60 | 61 | - *(makefile)* Update makefile 62 | - *(bench)* Added #[allow(clippy::expect_used)] to suppress clippy warnings 63 | - *(cdl_gravestone_doji)* Optimize T::from(100).unwrap() to T::from(100).ok_or(KandError::ConversionError)? 64 | - *(var)* Replace unwrap with safe conversion using ok_or(KandError::ConversionError)? 65 | 66 | ### 🚜 Refactor 67 | 68 | - *(ci)* Simplify release workflow and customize changelog footer 69 | - *(tpo)* Replace as f64 with f64::from(u8::try_from(i).unwrap()) for type conversion 70 | 71 | ### 📚 Documentation 72 | 73 | - Update rust doc 74 | - *(helper)* Add missing error documentation for lowest_bars and highest_bars functions 75 | 76 | ## [0.1.1] - 2025-02-27 77 | 78 | ### 🚀 Features 79 | 80 | - *(ci)* Add changelog ci. 81 | 82 | ### 🐛 Bug Fixes 83 | 84 | - *(aroonosc)* Optimize precision conversion by replacing 'as' with 'T::from' for safety 85 | 86 | --- 87 | 88 | > "Quantitative trading begins with data, thrives on strategy, and succeeds through execution. Kand, making trading simpler." 89 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ad; 2 | pub mod adosc; 3 | pub mod adr; 4 | pub mod adx; 5 | pub mod adxr; 6 | // pub mod apo; 7 | pub mod aroon; 8 | pub mod aroonosc; 9 | pub mod atr; 10 | pub mod bbands; 11 | pub mod bop; 12 | pub mod cci; 13 | pub mod cdl_doji; 14 | pub mod cdl_dragonfly_doji; 15 | pub mod cdl_gravestone_doji; 16 | pub mod cdl_hammer; 17 | pub mod cdl_inverted_hammer; 18 | pub mod cdl_long_shadow; 19 | pub mod cdl_marubozu; 20 | // pub mod cdl_spinning_top; 21 | // pub mod cmo; 22 | pub mod dema; 23 | pub mod dx; 24 | pub mod ecl; 25 | pub mod ema; 26 | // pub mod harm_5_0; 27 | // pub mod harm_abcd; 28 | // pub mod harm_abcd_alt; 29 | // pub mod harm_bat; 30 | // pub mod harm_bat_alt; 31 | // pub mod harm_butterfly; 32 | // pub mod harm_crab; 33 | // pub mod harm_cypher; 34 | // pub mod harm_deep_crab; 35 | // pub mod harm_gartley; 36 | // pub mod harm_shark; 37 | // pub mod harm_three_drives; 38 | // pub mod ht_dcperiod; 39 | // pub mod ht_dcphase; 40 | // pub mod ht_phasor; 41 | // pub mod ht_sine; 42 | // pub mod ht_trendline; 43 | // pub mod ht_trendmode; 44 | // pub mod hvol; 45 | // pub mod ict_bos; 46 | // pub mod ict_choch; 47 | // pub mod ict_fvg; 48 | // pub mod ict_liquidity; 49 | // pub mod ict_ob; 50 | // pub mod ict_session; 51 | // pub mod kama; 52 | // pub mod linearreg; 53 | // pub mod linearreg_angle; 54 | // pub mod linearreg_intercept; 55 | // pub mod linearreg_slope; 56 | pub mod macd; 57 | // pub mod macdext; 58 | // pub mod mama; 59 | pub mod medprice; 60 | pub mod mfi; 61 | pub mod midpoint; 62 | pub mod midprice; 63 | pub mod minus_di; 64 | pub mod minus_dm; 65 | pub mod mom; 66 | pub mod natr; 67 | pub mod obv; 68 | pub mod plus_di; 69 | pub mod plus_dm; 70 | // pub mod ppo; 71 | // pub mod qqe; 72 | // pub mod retracements; 73 | // pub mod rf; 74 | pub mod rma; 75 | pub mod roc; 76 | pub mod rocp; 77 | pub mod rocr; 78 | pub mod rocr100; 79 | pub mod rsi; 80 | pub mod sar; 81 | // pub mod sarext; 82 | pub mod sma; 83 | pub mod stoch; 84 | // pub mod stochf; 85 | // pub mod stochrsi; 86 | pub mod supertrend; 87 | pub mod t3; 88 | pub mod tema; 89 | // pub mod tpo; 90 | pub mod trange; 91 | pub mod trima; 92 | pub mod trix; 93 | // pub mod tsf; 94 | // pub mod twb; 95 | pub mod typprice; 96 | // pub mod ultosc; 97 | pub mod vegas; 98 | pub mod vwap; 99 | pub mod wclprice; 100 | pub mod willr; 101 | pub mod wma; 102 | // pub mod zigzag; 103 | -------------------------------------------------------------------------------- /kand-py/src/ta/stats/max.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, stats::max}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Calculate Maximum Value for a NumPy array 6 | /// 7 | /// Args: 8 | /// prices: Input prices as a 1-D NumPy array of type `TAFloat`. 9 | /// period: Period for MAX calculation (must be >= 2). 10 | /// 11 | /// Returns: 12 | /// A 1-D NumPy array containing MAX values. The first (period-1) elements contain NaN values. 13 | /// 14 | /// Examples: 15 | /// ```python 16 | /// >>> import numpy as np 17 | /// >>> import kand 18 | /// >>> prices = np.array([1.0, 2.0, 3.0, 2.5, 4.0]) 19 | /// >>> max_values = kand.max(prices, 3) 20 | /// ``` 21 | #[pyfunction] 22 | #[pyo3(name = "max", signature = (prices, period))] 23 | pub fn max_py( 24 | py: Python, 25 | prices: PyReadonlyArray1, 26 | period: usize, 27 | ) -> PyResult>> { 28 | // Convert input NumPy array to Rust slice 29 | let input_prices = prices.as_slice()?; 30 | let len = input_prices.len(); 31 | 32 | // Create output array using vec 33 | let mut output_max = vec![0.0; len]; 34 | 35 | // Perform MAX calculation while releasing the GIL 36 | py.allow_threads(|| max::max(input_prices, period, &mut output_max)) 37 | .map_err(|e| PyErr::new::(e.to_string()))?; 38 | 39 | // Convert output array to Python object 40 | Ok(output_max.into_pyarray(py).into()) 41 | } 42 | 43 | /// Calculate the latest Maximum Value incrementally 44 | /// 45 | /// Args: 46 | /// py: Python interpreter token 47 | /// price: Current period's price 48 | /// prev_max: Previous period's MAX value 49 | /// old_price: Price being removed from the period 50 | /// period: Period for MAX calculation (must be >= 2) 51 | /// 52 | /// Returns: 53 | /// The new MAX value 54 | /// 55 | /// Examples: 56 | /// ```python 57 | /// >>> import kand 58 | /// >>> new_max = kand.max_inc(10.5, 11.0, 9.0, 14) 59 | /// ``` 60 | #[pyfunction] 61 | #[pyo3(name = "max_inc")] 62 | pub fn max_inc_py( 63 | py: Python, 64 | price: TAFloat, 65 | prev_max: TAFloat, 66 | old_price: TAFloat, 67 | period: usize, 68 | ) -> PyResult { 69 | // Perform incremental MAX calculation while releasing the GIL 70 | py.allow_threads(|| max::max_inc(price, prev_max, old_price, period)) 71 | .map_err(|e| PyErr::new::(e.to_string())) 72 | } 73 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM python:3.12-slim-bullseye AS build 2 | ENV HOME="/root" 3 | WORKDIR $HOME 4 | 5 | # Install build dependencies and set up Python environment 6 | RUN apt-get update && \ 7 | apt-get install -y --no-install-recommends \ 8 | build-essential \ 9 | curl \ 10 | cmake \ 11 | gcc-aarch64-linux-gnu \ 12 | g++-aarch64-linux-gnu \ 13 | patchelf && \ 14 | rm -rf /var/lib/apt/lists/* && \ 15 | python -m venv $HOME/.venv && \ 16 | .venv/bin/pip install --no-cache-dir "maturin>=1.7,<2.0" 17 | ENV PATH="$HOME/.venv/bin:$PATH" 18 | 19 | # Set Rust target based on the target platform 20 | ARG TARGETPLATFORM 21 | RUN case "$TARGETPLATFORM" in \ 22 | "linux/arm64") echo "aarch64-unknown-linux-gnu" > rust_target.txt ;; \ 23 | "linux/amd64") echo "x86_64-unknown-linux-gnu" > rust_target.txt ;; \ 24 | *) exit 1 ;; \ 25 | esac 26 | 27 | # Install Rust toolchain 28 | COPY rust-toolchain.toml rust-toolchain.toml 29 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --target $(cat rust_target.txt) --profile minimal --default-toolchain none && \ 30 | . $HOME/.cargo/env && \ 31 | rustup target add $(cat rust_target.txt) 32 | 33 | # Copy project files and build 34 | COPY kand kand 35 | COPY kand-py kand-py 36 | COPY Cargo.toml Cargo.lock pyproject.toml README.md LICENSE-MIT LICENSE-APACHE ./ 37 | COPY python python 38 | 39 | # Build Python extension 40 | RUN case "${TARGETPLATFORM}" in \ 41 | "linux/arm64") \ 42 | export JEMALLOC_SYS_WITH_LG_PAGE=16; \ 43 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc; \ 44 | export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc; \ 45 | export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++; \ 46 | ;; \ 47 | *) \ 48 | ;; \ 49 | esac && \ 50 | . $HOME/.cargo/env && \ 51 | maturin build --release --target $(cat rust_target.txt) -i python3.12 && \ 52 | mkdir -p /wheels && \ 53 | cp target/wheels/*.whl /wheels/ && \ 54 | rm -rf target $HOME/.cargo/registry $HOME/.cargo/git 55 | 56 | # Final image: Provide Python runtime 57 | FROM python:3.12-slim-bullseye 58 | WORKDIR /app 59 | 60 | # Copy and install the wheel file 61 | COPY --from=build /wheels/ /wheels/ 62 | RUN pip install --no-cache-dir /wheels/*.whl && rm -rf \ 63 | /wheels \ 64 | /root/.cache \ 65 | /usr/share/doc \ 66 | /usr/share/man \ 67 | /usr/share/locale \ 68 | /var/lib/apt/lists/* \ 69 | /var/cache/apt/archives/* 70 | 71 | # Set Python path 72 | ENV PYTHONPATH=/app 73 | -------------------------------------------------------------------------------- /kand-py/src/ta/stats/min.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, stats::min}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Calculate Minimum Value (MIN) for a NumPy array 6 | /// 7 | /// The MIN indicator finds the lowest price value within a given time period. 8 | /// 9 | /// Args: 10 | /// prices: Input prices as a 1-D NumPy array of type `TAFloat`. 11 | /// period: Period for MIN calculation (must be >= 2). 12 | /// 13 | /// Returns: 14 | /// A 1-D NumPy array containing MIN values. First (period-1) elements contain NaN. 15 | /// 16 | /// Examples: 17 | /// ```python 18 | /// >>> import numpy as np 19 | /// >>> import kand 20 | /// >>> prices = np.array([10.0, 8.0, 6.0, 7.0, 9.0]) 21 | /// >>> min_values = kand.min(prices, 3) 22 | /// ``` 23 | #[pyfunction] 24 | #[pyo3(name = "min", signature = (prices, period))] 25 | pub fn min_py( 26 | py: Python, 27 | prices: PyReadonlyArray1, 28 | period: usize, 29 | ) -> PyResult>> { 30 | // Convert input NumPy array to Rust slice 31 | let input_prices = prices.as_slice()?; 32 | let len = input_prices.len(); 33 | 34 | // Create output array using vec 35 | let mut output_min = vec![0.0; len]; 36 | 37 | // Perform MIN calculation while releasing the GIL 38 | py.allow_threads(|| min::min(input_prices, period, &mut output_min)) 39 | .map_err(|e| PyErr::new::(e.to_string()))?; 40 | 41 | // Convert output array to Python object 42 | Ok(output_min.into_pyarray(py).into()) 43 | } 44 | 45 | /// Calculate the latest MIN value incrementally 46 | /// 47 | /// Args: 48 | /// py: Python interpreter token 49 | /// price: Current period's price 50 | /// prev_min: Previous period's MIN value 51 | /// prev_price: Price value being removed from the period 52 | /// period: Period for MIN calculation (must be >= 2) 53 | /// 54 | /// Returns: 55 | /// The new MIN value 56 | /// 57 | /// Examples: 58 | /// ```python 59 | /// >>> import kand 60 | /// >>> new_min = kand.min_inc(15.0, 12.0, 14.0, 14) 61 | /// ``` 62 | #[pyfunction] 63 | #[pyo3(name = "min_inc")] 64 | pub fn min_inc_py( 65 | py: Python, 66 | price: TAFloat, 67 | prev_min: TAFloat, 68 | prev_price: TAFloat, 69 | period: usize, 70 | ) -> PyResult { 71 | // Perform incremental MIN calculation while releasing the GIL 72 | py.allow_threads(|| min::min_inc(price, prev_min, prev_price, period)) 73 | .map_err(|e| PyErr::new::(e.to_string())) 74 | } 75 | -------------------------------------------------------------------------------- /kand-py/src/ta/stats/sum.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, stats::sum}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Calculate Sum for a NumPy array 6 | /// 7 | /// Calculates the rolling sum of values over a specified period. 8 | /// 9 | /// Args: 10 | /// input: Input values as a 1-D NumPy array of type `TAFloat`. 11 | /// period: Period for sum calculation (must be >= 2). 12 | /// 13 | /// Returns: 14 | /// A 1-D NumPy array containing the sum values. 15 | /// The first (period-1) elements contain NaN values. 16 | /// 17 | /// Examples: 18 | /// ```python 19 | /// >>> import numpy as np 20 | /// >>> import kand 21 | /// >>> data = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) 22 | /// >>> sums = kand.sum(data, 3) 23 | /// ``` 24 | #[pyfunction] 25 | #[pyo3(name = "sum", signature = (input, period))] 26 | pub fn sum_py( 27 | py: Python, 28 | input: PyReadonlyArray1, 29 | period: usize, 30 | ) -> PyResult>> { 31 | // Convert input NumPy array to Rust slice 32 | let input_data = input.as_slice()?; 33 | let len = input_data.len(); 34 | 35 | // Create output array using vec 36 | let mut output_sum = vec![0.0; len]; 37 | 38 | // Perform sum calculation while releasing the GIL 39 | py.allow_threads(|| sum::sum(input_data, period, &mut output_sum)) 40 | .map_err(|e| PyErr::new::(e.to_string()))?; 41 | 42 | // Convert output array to Python object 43 | Ok(output_sum.into_pyarray(py).into()) 44 | } 45 | 46 | /// Calculate the latest sum value incrementally 47 | /// 48 | /// Args: 49 | /// py: Python interpreter token 50 | /// new_price: The newest price value to add 51 | /// old_price: The oldest price value to remove 52 | /// prev_sum: The previous sum value 53 | /// 54 | /// Returns: 55 | /// The new sum value 56 | /// 57 | /// Examples: 58 | /// ```python 59 | /// >>> import kand 60 | /// >>> new_sum = kand.sum_inc( 61 | /// ... 5.0, # new price 62 | /// ... 3.0, # old price 63 | /// ... 10.0, # previous sum 64 | /// ... ) 65 | /// ``` 66 | #[pyfunction] 67 | #[pyo3(name = "sum_inc")] 68 | pub fn sum_inc_py( 69 | py: Python, 70 | new_price: TAFloat, 71 | old_price: TAFloat, 72 | prev_sum: TAFloat, 73 | ) -> PyResult { 74 | // Perform incremental sum calculation while releasing the GIL 75 | py.allow_threads(|| sum::sum_inc(new_price, old_price, prev_sum)) 76 | .map_err(|e| PyErr::new::(e.to_string())) 77 | } 78 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/wma.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::wma}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Weighted Moving Average (WMA) over a NumPy array. 6 | /// 7 | /// The Weighted Moving Average assigns linearly decreasing weights to each price in the period, 8 | /// giving more importance to recent prices and less to older ones. 9 | /// 10 | /// Args: 11 | /// data: Input data as a 1-D NumPy array of type `TAFloat`. 12 | /// period: Window size for WMA calculation. Must be >= 2. 13 | /// 14 | /// Returns: 15 | /// A new 1-D NumPy array containing the WMA values. The array has the same length as the input, 16 | /// with the first `period-1` elements containing NaN values. 17 | /// 18 | /// Examples: 19 | /// ```python 20 | /// >>> import numpy as np 21 | /// >>> import kand 22 | /// >>> data = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) 23 | /// >>> result = kand.wma(data, 3) 24 | /// >>> print(result) 25 | /// [nan, nan, 2.0, 3.0, 4.0] 26 | /// ``` 27 | #[pyfunction] 28 | #[pyo3(name = "wma", signature = (data, period))] 29 | pub fn wma_py( 30 | py: Python, 31 | data: PyReadonlyArray1, 32 | period: usize, 33 | ) -> PyResult>> { 34 | let input = data.as_slice()?; 35 | let len = input.len(); 36 | let mut output = vec![0.0; len]; 37 | 38 | py.allow_threads(|| wma::wma(input, period, output.as_mut_slice())) 39 | .map_err(|e| PyErr::new::(e.to_string()))?; 40 | 41 | Ok(output.into_pyarray(py).into()) 42 | } 43 | 44 | /// Incrementally calculates the next WMA value. 45 | /// 46 | /// This function provides an optimized way to calculate the latest WMA value 47 | /// by using a window of the most recent prices. 48 | /// 49 | /// Args: 50 | /// input_window: Array of price values ordered from newest to oldest. 51 | /// period: The time period for WMA calculation (must be >= 2). 52 | /// 53 | /// Returns: 54 | /// The next WMA value. 55 | /// 56 | /// Examples: 57 | /// ```python 58 | /// >>> import kand 59 | /// >>> window = [5.0, 4.0, 3.0] # newest to oldest 60 | /// >>> wma = kand.wma_inc(window, 3) 61 | /// >>> print(wma) 62 | /// 4.333333333333333 63 | /// ``` 64 | #[pyfunction] 65 | #[pyo3(name = "wma_inc", signature = (input_window, period))] 66 | pub fn wma_inc_py(input_window: Vec, period: usize) -> PyResult { 67 | wma::wma_inc(&input_window, period) 68 | .map_err(|e| PyErr::new::(e.to_string())) 69 | } 70 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/wclprice.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::wclprice}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Calculates the Weighted Close Price (WCLPRICE) for a series of price data. 6 | /// 7 | /// The Weighted Close Price is a price indicator that assigns more weight to the closing price 8 | /// compared to high and low prices. It provides a single value that reflects price action 9 | /// with emphasis on the closing price. 10 | /// 11 | /// Args: 12 | /// high: High prices as a 1-D NumPy array of type `TAFloat`. 13 | /// low: Low prices as a 1-D NumPy array of type `TAFloat`. 14 | /// close: Close prices as a 1-D NumPy array of type `TAFloat`. 15 | /// 16 | /// Returns: 17 | /// A 1-D NumPy array containing the WCLPRICE values. 18 | /// 19 | /// Examples: 20 | /// ```python 21 | /// >>> import numpy as np 22 | /// >>> import kand 23 | /// >>> high = np.array([10.0, 12.0, 15.0]) 24 | /// >>> low = np.array([8.0, 9.0, 11.0]) 25 | /// >>> close = np.array([9.0, 11.0, 14.0]) 26 | /// >>> wclprice = kand.wclprice(high, low, close) 27 | /// ``` 28 | #[pyfunction] 29 | #[pyo3(name = "wclprice", signature = (high, low, close))] 30 | pub fn wclprice_py( 31 | py: Python, 32 | high: PyReadonlyArray1, 33 | low: PyReadonlyArray1, 34 | close: PyReadonlyArray1, 35 | ) -> PyResult>> { 36 | let high_slice = high.as_slice()?; 37 | let low_slice = low.as_slice()?; 38 | let close_slice = close.as_slice()?; 39 | let len = high_slice.len(); 40 | 41 | let mut output = vec![0.0; len]; 42 | 43 | py.allow_threads(|| { 44 | wclprice::wclprice(high_slice, low_slice, close_slice, output.as_mut_slice()) 45 | }) 46 | .map_err(|e| PyErr::new::(e.to_string()))?; 47 | 48 | Ok(output.into_pyarray(py).into()) 49 | } 50 | 51 | /// Calculates a single Weighted Close Price (WCLPRICE) value from the latest price data. 52 | /// 53 | /// Args: 54 | /// high: Latest high price value as `TAFloat`. 55 | /// low: Latest low price value as `TAFloat`. 56 | /// close: Latest close price value as `TAFloat`. 57 | /// 58 | /// Returns: 59 | /// The calculated WCLPRICE value. 60 | /// 61 | /// Examples: 62 | /// ```python 63 | /// >>> import kand 64 | /// >>> wclprice = kand.wclprice_inc(15.0, 11.0, 14.0) 65 | /// ``` 66 | #[pyfunction] 67 | #[pyo3(name = "wclprice_inc", signature = (high, low, close))] 68 | pub fn wclprice_inc_py(high: TAFloat, low: TAFloat, close: TAFloat) -> PyResult { 69 | wclprice::wclprice_inc(high, low, close) 70 | .map_err(|e| PyErr::new::(e.to_string())) 71 | } 72 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/roc.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::roc}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Rate of Change (ROC) over a NumPy array. 6 | /// 7 | /// The Rate of Change (ROC) is a momentum oscillator that measures the percentage change in price 8 | /// between the current price and the price n periods ago. 9 | /// 10 | /// Args: 11 | /// data: Input price data as a 1-D NumPy array of type `TAFloat`. 12 | /// period: Number of periods to look back. Must be positive. 13 | /// 14 | /// Returns: 15 | /// A new 1-D NumPy array containing the ROC values. The array has the same length as the input, 16 | /// with the first `period` elements containing NaN values. 17 | /// 18 | /// Examples: 19 | /// ```python 20 | /// >>> import numpy as np 21 | /// >>> import kand 22 | /// >>> data = np.array([10.0, 10.5, 11.2, 10.8, 11.5]) 23 | /// >>> result = kand.roc(data, 2) 24 | /// >>> print(result) 25 | /// [nan, nan, 12.0, 2.86, 6.48] 26 | /// ``` 27 | #[pyfunction] 28 | #[pyo3(name = "roc", signature = (data, period))] 29 | pub fn roc_py( 30 | py: Python, 31 | data: PyReadonlyArray1, 32 | period: usize, 33 | ) -> PyResult>> { 34 | // Convert the input NumPy array to a Rust slice 35 | let input = data.as_slice()?; 36 | let len = input.len(); 37 | 38 | // Create a new output array using vec 39 | let mut output = vec![0.0; len]; 40 | 41 | // Perform the ROC calculation while releasing the GIL to allow other Python threads to run 42 | py.allow_threads(|| roc::roc(input, period, output.as_mut_slice())) 43 | .map_err(|e| PyErr::new::(e.to_string()))?; 44 | 45 | // Convert the output array to a Python object 46 | Ok(output.into_pyarray(py).into()) 47 | } 48 | 49 | /// Calculates a single ROC value incrementally. 50 | /// 51 | /// This function provides an optimized way to calculate the latest ROC value 52 | /// when streaming data is available, without needing the full price history. 53 | /// 54 | /// Args: 55 | /// current_price: The current period's price value. 56 | /// prev_price: The price from n periods ago. 57 | /// 58 | /// Returns: 59 | /// The calculated ROC value. 60 | /// 61 | /// Examples: 62 | /// ```python 63 | /// >>> import kand 64 | /// >>> roc = kand.roc_inc(11.5, 10.0) 65 | /// >>> print(roc) 66 | /// 15.0 67 | /// ``` 68 | #[pyfunction] 69 | #[pyo3(name = "roc_inc", signature = (current_price, prev_price))] 70 | pub fn roc_inc_py(current_price: TAFloat, prev_price: TAFloat) -> PyResult { 71 | roc::roc_inc(current_price, prev_price) 72 | .map_err(|e| PyErr::new::(e.to_string())) 73 | } 74 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/rocr.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::rocr}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Rate of Change Ratio (ROCR) over a NumPy array. 6 | /// 7 | /// The Rate of Change Ratio (ROCR) is a momentum indicator that measures the ratio between 8 | /// the current price and the price n periods ago. 9 | /// 10 | /// Args: 11 | /// data: Input price data as a 1-D NumPy array of type `TAFloat`. 12 | /// period: Number of periods to look back. Must be >= 2. 13 | /// 14 | /// Returns: 15 | /// A new 1-D NumPy array containing the ROCR values. The array has the same length as the input, 16 | /// with the first `period` elements containing NaN values. 17 | /// 18 | /// Examples: 19 | /// ```python 20 | /// >>> import numpy as np 21 | /// >>> import kand 22 | /// >>> data = np.array([10.0, 10.5, 11.2, 10.8, 11.5]) 23 | /// >>> result = kand.rocr(data, 2) 24 | /// >>> print(result) 25 | /// [nan, nan, 1.12, 1.0286, 1.0648] 26 | /// ``` 27 | #[pyfunction] 28 | #[pyo3(name = "rocr", signature = (data, period))] 29 | pub fn rocr_py( 30 | py: Python, 31 | data: PyReadonlyArray1, 32 | period: usize, 33 | ) -> PyResult>> { 34 | // Convert the input NumPy array to a Rust slice 35 | let input = data.as_slice()?; 36 | let len = input.len(); 37 | 38 | // Create a new output array using vec 39 | let mut output = vec![0.0; len]; 40 | 41 | // Perform the ROCR calculation while releasing the GIL to allow other Python threads to run 42 | py.allow_threads(|| rocr::rocr(input, period, output.as_mut_slice())) 43 | .map_err(|e| PyErr::new::(e.to_string()))?; 44 | 45 | // Convert the output array to a Python object 46 | Ok(output.into_pyarray(py).into()) 47 | } 48 | 49 | /// Calculates a single ROCR value incrementally. 50 | /// 51 | /// This function provides an optimized way to calculate the latest ROCR value 52 | /// when streaming data is available, without needing the full price history. 53 | /// 54 | /// Args: 55 | /// current_price: The current period's price value. 56 | /// prev_price: The price from n periods ago. 57 | /// 58 | /// Returns: 59 | /// The calculated ROCR value. 60 | /// 61 | /// Examples: 62 | /// ```python 63 | /// >>> import kand 64 | /// >>> rocr = kand.rocr_inc(11.5, 10.0) 65 | /// >>> print(rocr) 66 | /// 1.15 67 | /// ``` 68 | #[pyfunction] 69 | #[pyo3(name = "rocr_inc", signature = (current_price, prev_price))] 70 | pub fn rocr_inc_py(current_price: TAFloat, prev_price: TAFloat) -> PyResult { 71 | rocr::rocr_inc(current_price, prev_price) 72 | .map_err(|e| PyErr::new::(e.to_string())) 73 | } 74 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/rocp.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::rocp}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Rate of Change Percentage (ROCP) over a NumPy array. 6 | /// 7 | /// The Rate of Change Percentage (ROCP) is a momentum indicator that measures the percentage change 8 | /// between the current price and the price n periods ago. 9 | /// 10 | /// Args: 11 | /// data: Input price data as a 1-D NumPy array of type `TAFloat`. 12 | /// period: Number of periods to look back. Must be positive. 13 | /// 14 | /// Returns: 15 | /// A new 1-D NumPy array containing the ROCP values. The array has the same length as the input, 16 | /// with the first `period` elements containing NaN values. 17 | /// 18 | /// Examples: 19 | /// ```python 20 | /// >>> import numpy as np 21 | /// >>> import kand 22 | /// >>> data = np.array([10.0, 10.5, 11.2, 10.8, 11.5]) 23 | /// >>> result = kand.rocp(data, 2) 24 | /// >>> print(result) 25 | /// [nan, nan, 0.12, 0.0286, 0.0648] 26 | /// ``` 27 | #[pyfunction] 28 | #[pyo3(name = "rocp", signature = (data, period))] 29 | pub fn rocp_py( 30 | py: Python, 31 | data: PyReadonlyArray1, 32 | period: usize, 33 | ) -> PyResult>> { 34 | // Convert the input NumPy array to a Rust slice 35 | let input = data.as_slice()?; 36 | let len = input.len(); 37 | 38 | // Create a new output array using vec 39 | let mut output = vec![0.0; len]; 40 | 41 | // Perform the ROCP calculation while releasing the GIL to allow other Python threads to run 42 | py.allow_threads(|| rocp::rocp(input, period, output.as_mut_slice())) 43 | .map_err(|e| PyErr::new::(e.to_string()))?; 44 | 45 | // Convert the output array to a Python object 46 | Ok(output.into_pyarray(py).into()) 47 | } 48 | 49 | /// Calculates a single ROCP value incrementally. 50 | /// 51 | /// This function provides an optimized way to calculate the latest ROCP value 52 | /// when streaming data is available, without needing the full price history. 53 | /// 54 | /// Args: 55 | /// current_price: The current period's price value. 56 | /// prev_price: The price from n periods ago. 57 | /// 58 | /// Returns: 59 | /// The calculated ROCP value. 60 | /// 61 | /// Examples: 62 | /// ```python 63 | /// >>> import kand 64 | /// >>> rocp = kand.rocp_inc(11.5, 10.0) 65 | /// >>> print(rocp) 66 | /// 0.15 67 | /// ``` 68 | #[pyfunction] 69 | #[pyo3(name = "rocp_inc", signature = (current_price, prev_price))] 70 | pub fn rocp_inc_py(current_price: TAFloat, prev_price: TAFloat) -> PyResult { 71 | rocp::rocp_inc(current_price, prev_price) 72 | .map_err(|e| PyErr::new::(e.to_string())) 73 | } 74 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/mom.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::mom}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Momentum (MOM) over a NumPy array. 6 | /// 7 | /// Momentum measures the change in price between the current price and the price n periods ago. 8 | /// 9 | /// Args: 10 | /// data: Input data as a 1-D NumPy array of type `TAFloat`. 11 | /// period: Window size for momentum calculation. Must be positive and less than input length. 12 | /// 13 | /// Returns: 14 | /// A new 1-D NumPy array containing the momentum values. The array has the same length as the input, 15 | /// with the first `period` elements containing NaN values. 16 | /// 17 | /// Examples: 18 | /// ```python 19 | /// >>> import numpy as np 20 | /// >>> import kand 21 | /// >>> data = np.array([2.0, 4.0, 6.0, 8.0, 10.0]) 22 | /// >>> result = kand.mom(data, 2) 23 | /// >>> print(result) 24 | /// [nan, nan, 4.0, 4.0, 4.0] 25 | /// ``` 26 | #[pyfunction] 27 | #[pyo3(name = "mom", signature = (data, period))] 28 | pub fn mom_py( 29 | py: Python, 30 | data: PyReadonlyArray1, 31 | period: usize, 32 | ) -> PyResult>> { 33 | // Convert the input NumPy array to a Rust slice. 34 | let input = data.as_slice()?; 35 | let len = input.len(); 36 | 37 | // Create a new output array using vec 38 | let mut output = vec![0.0; len]; 39 | 40 | // Perform the momentum calculation while releasing the GIL to allow other Python threads to run. 41 | py.allow_threads(|| mom::mom(input, period, output.as_mut_slice())) 42 | .map_err(|e| PyErr::new::(e.to_string()))?; 43 | 44 | // Convert the output array to a Python object 45 | Ok(output.into_pyarray(py).into()) 46 | } 47 | 48 | /// Calculates the next Momentum (MOM) value incrementally. 49 | /// 50 | /// This function provides an optimized way to calculate the latest momentum value 51 | /// when streaming data is available, without needing the full price history. 52 | /// 53 | /// Args: 54 | /// 55 | /// current_price: The current period's price value. 56 | /// old_price: The price value from n periods ago. 57 | /// 58 | /// Returns: 59 | /// The calculated momentum value. 60 | /// 61 | /// Examples: 62 | /// ```python 63 | /// >>> import kand 64 | /// >>> momentum = kand.mom_inc(10.0, 6.0) 65 | /// >>> print(momentum) 66 | /// 4.0 67 | /// ``` 68 | #[pyfunction] 69 | #[pyo3(name = "mom_inc", signature = (current_price, old_price))] 70 | pub fn mom_inc_py(py: Python, current_price: TAFloat, old_price: TAFloat) -> PyResult { 71 | // Perform the incremental momentum calculation while releasing the GIL 72 | py.allow_threads(|| mom::mom_inc(current_price, old_price)) 73 | .map_err(|e| PyErr::new::(e.to_string())) 74 | } 75 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/rma.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::rma}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Running Moving Average (RMA) over a NumPy array. 6 | /// 7 | /// The Running Moving Average is similar to an Exponential Moving Average (EMA) but uses a different 8 | /// smoothing factor. It is calculated using a weighted sum of the current value and previous RMA value, 9 | /// with weights determined by the period size. 10 | /// 11 | /// Args: 12 | /// data: Input data as a 1-D NumPy array of type `TAFloat`. 13 | /// period: Window size for RMA calculation. Must be positive and less than input length. 14 | /// 15 | /// Returns: 16 | /// A new 1-D NumPy array containing the RMA values. The array has the same length as the input, 17 | /// with the first `period-1` elements containing NaN values. 18 | /// 19 | /// Examples: 20 | /// ```python 21 | /// >>> import numpy as np 22 | /// >>> import kand 23 | /// >>> data = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) 24 | /// >>> result = kand.rma(data, 3) 25 | /// >>> print(result) 26 | /// [nan, nan, 2.0, 2.67, 3.44] 27 | /// ``` 28 | #[pyfunction] 29 | #[pyo3(name = "rma", signature = (data, period))] 30 | pub fn rma_py( 31 | py: Python, 32 | data: PyReadonlyArray1, 33 | period: usize, 34 | ) -> PyResult>> { 35 | // Convert the input NumPy array to a Rust slice. 36 | let input = data.as_slice()?; 37 | let len = input.len(); 38 | 39 | // Create a new output array using vec 40 | let mut output = vec![0.0; len]; 41 | 42 | // Perform the RMA calculation while releasing the GIL to allow other Python threads to run. 43 | py.allow_threads(|| rma::rma(input, period, output.as_mut_slice())) 44 | .map_err(|e| PyErr::new::(e.to_string()))?; 45 | 46 | // Convert the output array to a Python object 47 | Ok(output.into_pyarray(py).into()) 48 | } 49 | 50 | /// Calculates the next RMA value incrementally. 51 | /// 52 | /// This function provides an optimized way to calculate the latest RMA value 53 | /// when streaming data is available, without needing the full price history. 54 | /// 55 | /// Args: 56 | /// current_price: The current period's price value. 57 | /// prev_rma: The previous period's RMA value. 58 | /// period: The smoothing period (must be >= 2). 59 | /// 60 | /// Returns: 61 | /// The calculated RMA value. 62 | /// 63 | /// Examples: 64 | /// ```python 65 | /// >>> import kand 66 | /// >>> new_rma = kand.rma_inc(10.0, 9.5, 14) 67 | /// ``` 68 | #[pyfunction] 69 | #[pyo3(name = "rma_inc", signature = (current_price, prev_rma, period))] 70 | pub fn rma_inc_py(current_price: TAFloat, prev_rma: TAFloat, period: usize) -> PyResult { 71 | rma::rma_inc(current_price, prev_rma, period) 72 | .map_err(|e| PyErr::new::(e.to_string())) 73 | } 74 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | # About Kand 2 | 3 | ## The Motivation 4 | 5 | TALib has long been a cornerstone for financial indicator calculations, valued for its comprehensive feature set. However, as modern workflows demand higher performance and flexibility, its limitations have become apparent: 6 | 7 | - **Performance Bottlenecks**: TALib’s C-based core, while fast, is constrained by Python’s Global Interpreter Lock (GIL), limiting multi-threaded potential in today’s multi-core world. This issue has been a persistent challenge, as noted in discussions around the [Python bindings](https://github.com/TA-Lib/ta-lib-python/issues/675). 8 | - **Complex Setup**: Installing TALib often involves wrangling C library dependencies, a hurdle for users seeking quick deployment. The fact that installation issues dominate their [GitHub issues](https://github.com/TA-Lib/ta-lib-python/issues) speaks volumes about this challenge. 9 | - **Batch-Only Design**: TALib focuses on full-batch computations, lacking efficient incremental updates needed for real-time systems. While its Python bindings offer a stream feature for incremental calculations, it still relies on batch processing underneath, resulting in slower performance. Even attempts to address parallelism in the [native C library](https://github.com/TA-Lib/ta-lib/issues/49) highlight its multi-threading constraints. 10 | 11 | These pain points inspired us to rethink how financial tools should work in a modern, high-performance context. 12 | 13 | ## Why We Built Kand 14 | 15 | `kand` was created to address TALib’s shortcomings and deliver a next-generation solution for financial developers. Leveraging Rust’s speed and safety, we set out to build a library that’s not just an alternative, but a leap forward: 16 | 17 | - **Elite Performance**: Written in Rust, `kand` matches or exceeds TALib’s speed while adding GIL-free multi-threading for true parallelism. 18 | - **Seamless Integration**: Powered by `rust-numpy`, `kand` shares array memory addresses directly between Python and Rust, enabling true zero-copy data access without any overhead in cross-language operations. 19 | - **Real-Time Ready**: True O(1) complexity with near-zero overhead—each update is just a pure variable computation without loops or batching, making it ideal for real-time streaming data processing. 20 | - **Frictionless Setup**: A single `pip install` command replaces TALib’s cumbersome C setup, with precompiled wheels for all major platforms. 21 | - **Cross-Platform Power**: Runs effortlessly on Linux, macOS, and Windows—musl Linux included. 22 | 23 | ## Our Vision 24 | 25 | `kand` isn’t just about fixing what’s broken—it’s about enabling what’s possible. Whether you’re a quant trader, data scientist, or developer, we aim to provide a tool that’s fast, reliable, and effortless to use, so you can focus on building, not battling your tools. 26 | 27 | To see `kand` in action, check out our [Installation Guide](install.md) or dive into the [API Documentation](api.md). 28 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/trange.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::trange}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the True Range (TR) over NumPy arrays. 6 | /// 7 | /// True Range measures the market's volatility by considering the current high-low range 8 | /// and the previous close price. 9 | /// 10 | /// Args: 11 | /// high: High prices as a 1-D NumPy array of type `TAFloat`. 12 | /// low: Low prices as a 1-D NumPy array of type `TAFloat`. 13 | /// close: Close prices as a 1-D NumPy array of type `TAFloat`. 14 | /// 15 | /// Returns: 16 | /// A new 1-D NumPy array containing the TR values. The array has the same length as the input, 17 | /// with the first element containing NaN value. 18 | 19 | /// Examples: 20 | /// ```python 21 | /// >>> import numpy as np 22 | /// >>> import kand 23 | /// >>> high = np.array([10.0, 12.0, 15.0]) 24 | /// >>> low = np.array([8.0, 9.0, 11.0]) 25 | /// >>> close = np.array([9.0, 11.0, 14.0]) 26 | /// >>> result = kand.trange(high, low, close) 27 | /// >>> print(result) 28 | /// [nan, 3.0, 4.0] 29 | /// ``` 30 | #[pyfunction] 31 | #[pyo3(name = "trange", signature = (high, low, close))] 32 | pub fn trange_py( 33 | py: Python, 34 | high: PyReadonlyArray1, 35 | low: PyReadonlyArray1, 36 | close: PyReadonlyArray1, 37 | ) -> PyResult>> { 38 | // Convert the input NumPy arrays to Rust slices 39 | let input_high = high.as_slice()?; 40 | let input_low = low.as_slice()?; 41 | let input_close = close.as_slice()?; 42 | let len = input_high.len(); 43 | 44 | // Create a new output array using vec 45 | let mut output = vec![0.0; len]; 46 | 47 | // Perform the TR calculation while releasing the GIL to allow other Python threads to run 48 | py.allow_threads(|| trange::trange(input_high, input_low, input_close, output.as_mut_slice())) 49 | .map_err(|e| PyErr::new::(e.to_string()))?; 50 | 51 | // Convert the output array to a Python object 52 | Ok(output.into_pyarray(py).into()) 53 | } 54 | 55 | /// Calculates a single True Range value for the most recent period. 56 | /// 57 | /// Args: 58 | /// high: Current period's high price. 59 | /// low: Current period's low price. 60 | /// prev_close: Previous period's closing price. 61 | /// 62 | /// Returns: 63 | /// The calculated True Range value. 64 | /// 65 | /// Examples: 66 | /// ```python 67 | /// >>> import kand 68 | /// >>> tr = kand.trange_inc(12.0, 9.0, 11.0) 69 | /// >>> print(tr) 70 | /// 3.0 # max(3, 1, 2) 71 | /// ``` 72 | #[pyfunction] 73 | #[pyo3(name = "trange_inc", signature = (high, low, prev_close))] 74 | pub fn trange_inc_py(high: TAFloat, low: TAFloat, prev_close: TAFloat) -> PyResult { 75 | trange::trange_inc(high, low, prev_close) 76 | .map_err(|e| PyErr::new::(e.to_string())) 77 | } 78 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/stoch.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::stoch}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Stochastic Oscillator indicator over NumPy arrays. 6 | /// 7 | /// The Stochastic Oscillator is a momentum indicator that shows the location of the close 8 | /// relative to the high-low range over a set number of periods. The indicator consists of 9 | /// two lines: %K (the fast line) and %D (the slow line). 10 | /// 11 | /// Args: 12 | /// high: High prices as a 1-D NumPy array of type `TAFloat`. 13 | /// low: Low prices as a 1-D NumPy array of type `TAFloat`. 14 | /// close: Close prices as a 1-D NumPy array of type `TAFloat`. 15 | /// k_period: Period for %K calculation. Must be >= 2. 16 | /// k_slow_period: Smoothing period for slow %K. Must be >= 2. 17 | /// d_period: Period for %D calculation. Must be >= 2. 18 | /// 19 | /// Returns: 20 | /// A tuple of three 1-D NumPy arrays containing: 21 | /// - Fast %K values 22 | /// - Slow %K values 23 | /// - %D values 24 | /// Each array has the same length as the input, with initial values being NaN. 25 | /// 26 | /// Examples: 27 | /// ```python 28 | /// >>> import numpy as np 29 | /// >>> import kand 30 | /// >>> high = np.array([10.0, 12.0, 15.0, 14.0, 13.0]) 31 | /// >>> low = np.array([8.0, 9.0, 11.0, 10.0, 9.0]) 32 | /// >>> close = np.array([9.0, 11.0, 14.0, 12.0, 11.0]) 33 | /// >>> fast_k, k, d = kand.stoch(high, low, close, 3, 2, 2) 34 | /// ``` 35 | #[pyfunction] 36 | #[pyo3(name = "stoch", signature = (high, low, close, k_period, k_slow_period, d_period))] 37 | pub fn stoch_py( 38 | py: Python, 39 | high: PyReadonlyArray1, 40 | low: PyReadonlyArray1, 41 | close: PyReadonlyArray1, 42 | k_period: usize, 43 | k_slow_period: usize, 44 | d_period: usize, 45 | ) -> PyResult<( 46 | Py>, 47 | Py>, 48 | Py>, 49 | )> { 50 | let high_slice = high.as_slice()?; 51 | let low_slice = low.as_slice()?; 52 | let close_slice = close.as_slice()?; 53 | let len = high_slice.len(); 54 | 55 | let mut output_fast_k = vec![0.0; len]; 56 | let mut output_k = vec![0.0; len]; 57 | let mut output_d = vec![0.0; len]; 58 | 59 | py.allow_threads(|| { 60 | stoch::stoch( 61 | high_slice, 62 | low_slice, 63 | close_slice, 64 | k_period, 65 | k_slow_period, 66 | d_period, 67 | output_fast_k.as_mut_slice(), 68 | output_k.as_mut_slice(), 69 | output_d.as_mut_slice(), 70 | ) 71 | }) 72 | .map_err(|e| PyErr::new::(e.to_string()))?; 73 | 74 | Ok(( 75 | output_fast_k.into_pyarray(py).into(), 76 | output_k.into_pyarray(py).into(), 77 | output_d.into_pyarray(py).into(), 78 | )) 79 | } 80 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/dema.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::dema}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Calculates Double Exponential Moving Average (DEMA) over NumPy arrays. 6 | /// 7 | /// Args: 8 | /// input_price: Price values as a 1-D NumPy array of type `TAFloat`. 9 | /// period: Smoothing period for EMA calculations. Must be >= 2. 10 | /// 11 | /// Returns: 12 | /// A tuple of 1-D NumPy arrays containing: 13 | /// - DEMA values 14 | /// - First EMA values 15 | /// - Second EMA values 16 | /// Each array has the same length as the input, with the first `2*(period-1)` elements containing NaN values. 17 | /// 18 | /// Examples: 19 | /// ```python 20 | /// >>> import numpy as np 21 | /// >>> import kand 22 | /// >>> prices = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]) 23 | /// >>> dema, ema1, ema2 = kand.dema(prices, 3) 24 | /// ``` 25 | #[pyfunction] 26 | #[pyo3(name = "dema", signature = (input_price, period))] 27 | pub fn dema_py( 28 | py: Python, 29 | input_price: PyReadonlyArray1, 30 | period: usize, 31 | ) -> PyResult<( 32 | Py>, 33 | Py>, 34 | Py>, 35 | )> { 36 | let input = input_price.as_slice()?; 37 | let len = input.len(); 38 | 39 | let mut output_dema = vec![0.0; len]; 40 | let mut output_ema1 = vec![0.0; len]; 41 | let mut output_ema2 = vec![0.0; len]; 42 | 43 | py.allow_threads(|| { 44 | dema::dema( 45 | input, 46 | period, 47 | output_dema.as_mut_slice(), 48 | output_ema1.as_mut_slice(), 49 | output_ema2.as_mut_slice(), 50 | ) 51 | }) 52 | .map_err(|e| PyErr::new::(e.to_string()))?; 53 | 54 | Ok(( 55 | output_dema.into_pyarray(py).into(), 56 | output_ema1.into_pyarray(py).into(), 57 | output_ema2.into_pyarray(py).into(), 58 | )) 59 | } 60 | 61 | /// Calculates the next DEMA value incrementally. 62 | /// 63 | /// Args: 64 | /// 65 | /// price: Current price value. 66 | /// prev_ema1: Previous value of first EMA. 67 | /// prev_ema2: Previous value of second EMA. 68 | /// period: Smoothing period. Must be >= 2. 69 | /// 70 | /// Returns: 71 | /// A tuple containing (DEMA, new_ema1, new_ema2). 72 | /// 73 | /// Examples: 74 | /// ```python 75 | /// >>> import kand 76 | /// >>> dema, ema1, ema2 = kand.dema_inc(10.0, 9.5, 9.0, 3) 77 | /// ``` 78 | #[pyfunction] 79 | #[pyo3(name = "dema_inc", signature = (price, prev_ema1, prev_ema2, period))] 80 | pub fn dema_inc_py( 81 | py: Python, 82 | price: TAFloat, 83 | prev_ema1: TAFloat, 84 | prev_ema2: TAFloat, 85 | period: usize, 86 | ) -> PyResult<(TAFloat, TAFloat, TAFloat)> { 87 | py.allow_threads(|| dema::dema_inc(price, prev_ema1, prev_ema2, period)) 88 | .map_err(|e| PyErr::new::(e.to_string())) 89 | } 90 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/rocr100.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::rocr100}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Rate of Change Ratio * 100 (ROCR100) over a NumPy array. 6 | /// 7 | /// ROCR100 is a momentum indicator that measures the percentage change in price over a specified period. 8 | /// It compares the current price to a past price and expresses the ratio as a percentage. 9 | /// Values above 100 indicate price increases, while values below 100 indicate price decreases. 10 | /// 11 | /// Args: 12 | /// data: Input price data as a 1-D NumPy array of type `TAFloat`. 13 | /// period: Number of periods to look back. Must be >= 2. 14 | /// 15 | /// Returns: 16 | /// A new 1-D NumPy array containing the ROCR100 values. The array has the same length as the input, 17 | /// with the first `period` elements containing NaN values. 18 | /// 19 | /// Examples: 20 | /// ```python 21 | /// >>> import numpy as np 22 | /// >>> import kand 23 | /// >>> data = np.array([10.0, 10.5, 11.2, 10.8, 11.5]) 24 | /// >>> result = kand.rocr100(data, 2) 25 | /// >>> print(result) 26 | /// [nan, nan, 106.67, 102.86, 106.48] 27 | /// ``` 28 | #[pyfunction] 29 | #[pyo3(name = "rocr100", signature = (data, period))] 30 | pub fn rocr100_py( 31 | py: Python, 32 | data: PyReadonlyArray1, 33 | period: usize, 34 | ) -> PyResult>> { 35 | // Convert the input NumPy array to a Rust slice 36 | let input = data.as_slice()?; 37 | let len = input.len(); 38 | 39 | // Create a new output array using vec 40 | let mut output = vec![0.0; len]; 41 | 42 | // Perform the ROCR100 calculation while releasing the GIL to allow other Python threads to run 43 | py.allow_threads(|| rocr100::rocr100(input, period, output.as_mut_slice())) 44 | .map_err(|e| PyErr::new::(e.to_string()))?; 45 | 46 | // Convert the output array to a Python object 47 | Ok(output.into_pyarray(py).into()) 48 | } 49 | 50 | /// Calculates a single ROCR100 value incrementally. 51 | /// 52 | /// This function provides an optimized way to calculate the latest ROCR100 value 53 | /// when streaming data is available, without needing the full price history. 54 | /// 55 | /// Args: 56 | /// current_price: The current period's price value. 57 | /// prev_price: The price from n periods ago. 58 | /// 59 | /// Returns: 60 | /// The calculated ROCR100 value. 61 | /// 62 | /// Examples: 63 | /// ```python 64 | /// >>> import kand 65 | /// >>> rocr100 = kand.rocr100_inc(11.5, 10.0) 66 | /// >>> print(rocr100) 67 | /// 115.0 68 | /// ``` 69 | #[pyfunction] 70 | #[pyo3(name = "rocr100_inc", signature = (current_price, prev_price))] 71 | pub fn rocr100_inc_py(current_price: TAFloat, prev_price: TAFloat) -> PyResult { 72 | rocr100::rocr100_inc(current_price, prev_price) 73 | .map_err(|e| PyErr::new::(e.to_string())) 74 | } 75 | -------------------------------------------------------------------------------- /kand-py/src/ta/ohlcv/typprice.rs: -------------------------------------------------------------------------------- 1 | use kand::{TAFloat, ohlcv::typprice}; 2 | use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; 3 | use pyo3::prelude::*; 4 | 5 | /// Computes the Typical Price over NumPy arrays. 6 | /// 7 | /// The Typical Price is calculated by taking the arithmetic mean of the high, low and close prices 8 | /// for each period. 9 | /// 10 | /// Args: 11 | /// high: Input high prices as a 1-D NumPy array of type `TAFloat`. 12 | /// low: Input low prices as a 1-D NumPy array of type `TAFloat`. 13 | /// close: Input close prices as a 1-D NumPy array of type `TAFloat`. 14 | /// 15 | /// Returns: 16 | /// A new 1-D NumPy array containing the Typical Price values. The array has the same length as the inputs. 17 | /// 18 | /// Examples: 19 | /// ```python 20 | /// >>> import numpy as np 21 | /// >>> import kand 22 | /// >>> high = np.array([24.20, 24.07, 24.04]) 23 | /// >>> low = np.array([23.85, 23.72, 23.64]) 24 | /// >>> close = np.array([23.89, 23.95, 23.67]) 25 | /// >>> result = kand.typprice(high, low, close) 26 | /// >>> print(result) 27 | /// [23.98, 23.91, 23.78] 28 | /// ``` 29 | #[pyfunction] 30 | #[pyo3(name = "typprice", signature = (high, low, close))] 31 | pub fn typprice_py( 32 | py: Python, 33 | high: PyReadonlyArray1, 34 | low: PyReadonlyArray1, 35 | close: PyReadonlyArray1, 36 | ) -> PyResult>> { 37 | // Convert the input NumPy arrays to Rust slices 38 | let input_high = high.as_slice()?; 39 | let input_low = low.as_slice()?; 40 | let input_close = close.as_slice()?; 41 | let len = input_high.len(); 42 | 43 | // Create a new output array using vec 44 | let mut output = vec![0.0; len]; 45 | 46 | // Perform the Typical Price calculation while releasing the GIL to allow other Python threads to run 47 | py.allow_threads(|| { 48 | typprice::typprice(input_high, input_low, input_close, output.as_mut_slice()) 49 | }) 50 | .map_err(|e| PyErr::new::(e.to_string()))?; 51 | 52 | // Convert the output array to a Python object 53 | Ok(output.into_pyarray(py).into()) 54 | } 55 | 56 | /// Calculates a single Typical Price value incrementally. 57 | /// 58 | /// Args: 59 | /// high: Current period's high price. 60 | /// low: Current period's low price. 61 | /// close: Current period's close price. 62 | /// 63 | /// Returns: 64 | /// The calculated Typical Price value. 65 | /// 66 | /// Examples: 67 | /// ```python 68 | /// >>> import kand 69 | /// >>> typ_price = kand.typprice_inc(24.20, 23.85, 23.89) 70 | /// >>> print(typ_price) 71 | /// 23.98 # (24.20 + 23.85 + 23.89) / 3 72 | /// ``` 73 | #[pyfunction] 74 | #[pyo3(name = "typprice_inc", signature = (high, low, close))] 75 | pub fn typprice_inc_py(high: TAFloat, low: TAFloat, close: TAFloat) -> PyResult { 76 | typprice::typprice_inc(high, low, close) 77 | .map_err(|e| PyErr::new::(e.to_string())) 78 | } 79 | --------------------------------------------------------------------------------