[
  {
    "path": ".github/workflows/CI.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - \"*\"\n  pull_request:\n\njobs:\n  macos:\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-python@v2\n        with:\n          python-version: 3.9\n          architecture: x64\n      - name: Install Rust toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: nightly\n          profile: minimal\n          default: true\n      - name: Build wheels - x86_64\n        uses: messense/maturin-action@v1\n        with:\n          target: x86_64\n          args: --release --out dist\n      - name: Install built wheel - x86_64\n        run: |\n          pip install numpy\n          pip install ZenithTA --no-deps --no-index --find-links dist --force-reinstall\n          python -c 'import ZenithTA'\n      - name: Build wheels - universal2\n        uses: messense/maturin-action@v1\n        with:\n          args: --release --universal2 --out dist --no-sdist\n      - name: Install built wheel - universal2\n        run: |\n          pip install ZenithTA --no-deps --no-index --find-links dist --force-reinstall\n          python -c 'import ZenithTA'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v2\n        with:\n          name: wheels\n          path: dist\n\n  windows:\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        target: [x86]\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-python@v2\n        with:\n          python-version: 3.9\n          architecture: ${{ matrix.target }}\n      - name: Install Rust toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: nightly\n          profile: minimal\n          default: true\n      - name: Build wheels\n        uses: messense/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: --release --out dist --no-sdist\n      - name: Install built wheel\n        run: |\n          pip install numpy\n          pip install ZenithTA --no-deps --no-index --find-links dist --force-reinstall\n          python -c 'import ZenithTA'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v2\n        with:\n          name: wheels\n          path: dist\n\n  linux:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        target: [x86_64, i686]\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions/setup-python@v2\n      with:\n        python-version: 3.9\n        architecture: x64\n    - name: Build wheels\n      uses: messense/maturin-action@v1\n      with:\n        rust-toolchain: nightly\n        target: ${{ matrix.target }}\n        manylinux: auto\n        args: --release --out dist --no-sdist\n    - name: Install built wheel\n      if: matrix.target == 'x86_64'\n      run: |\n        pip install numpy\n        pip install ZenithTA --no-deps --no-index --find-links dist --force-reinstall\n        python -c 'import ZenithTA'\n    - name: Upload wheels\n      uses: actions/upload-artifact@v2\n      with:\n        name: wheels\n        path: dist\n\n  linux-cross:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        target: [aarch64, armv7, s390x, ppc64le]\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions/setup-python@v2\n      with:\n        python-version: 3.9\n    - name: Build wheels\n      uses: messense/maturin-action@v1\n      with:\n        rust-toolchain: nightly\n        target: ${{ matrix.target }}\n        manylinux: auto\n        args: --release --out dist --no-sdist\n    - uses: uraimo/run-on-arch-action@v2.0.5\n      name: Install built wheel\n      if: matrix.target == 'aarch64'\n      with:\n        arch: ${{ matrix.target }}\n        distro: ubuntu20.04\n        githubToken: ${{ github.token }}\n        install: |\n          apt-get update\n          apt-get install -y --no-install-recommends python3 python3-pip\n          pip3 install -U pip numpy\n        run: |\n          pip3 install ZenithTA --no-deps --no-index --find-links dist/ --force-reinstall\n          python3 -c 'import ZenithTA'\n    - name: Upload wheels\n      uses: actions/upload-artifact@v2\n      with:\n        name: wheels\n        path: dist\n\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    if: \"startsWith(github.ref, 'refs/tags/')\"\n    needs: [ macos, windows, linux, linux-cross ]\n    steps:\n      - uses: actions/download-artifact@v3\n        with:\n          name: wheels\n      - name: Publish to PyPi\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}\n        uses: PyO3/maturin-action@v1\n        with:\n          command: upload\n          args: --skip-existing *"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Cargo Cache\n      uses: actions/cache@v1\n      with:\n        path: ~/.cargo\n        key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }}\n        restore-keys: |\n          ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }}\n          ${{ runner.os }}-cargo\n\n    - name: Cargo Target Cache\n      uses: actions/cache@v1\n      with:\n        path: target\n        key: ${{ runner.os }}-cargo-target-${{ hashFiles('Cargo.toml') }}\n        restore-keys: |\n          ${{ runner.os }}-cargo-target-${{ hashFiles('Cargo.toml') }}\n          ${{ runner.os }}-cargo-target\n\n    - name: Build\n      run: cargo build --release --verbose\n    - name: Run tests\n      run: cargo test --verbose\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n\n# Added by cargo\n#\n# already existing elements were commented out\n\n/target\n#Cargo.lock\n\n# Add for maturin\ndist/\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"ZenithTA\"\nversion = \"1.0.0\"\nedition = \"2021\"\n\n[lib]\nname = \"ZenithTA\"\ncrate-type = [\"cdylib\"]\n\n[dependencies]\npyo3 = { version = \"0.15.1\", features = [\"abi3-py36\", \"extension-module\"] }\nnumpy = \"0.15\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Greg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Rust](https://img.shields.io/github/actions/workflow/status/gregyjames/zenithta/rust.yml?style=for-the-badge)](https://github.com/gregyjames/ZenithTA/actions/workflows/rust.yml)\n[![PyPI](https://img.shields.io/pypi/v/zenithta?color=%230s0&style=for-the-badge)](https://pypi.org/project/zenithta/)\n[![License](https://img.shields.io/github/license/gregyjames/zenithta?color=%230sd&style=for-the-badge)](https://github.com/gregyjames/ZenithTA/blob/main/LICENSE)\n[![Count](https://img.shields.io/tokei/lines/github/gregyjames/zenithta?color=%230fs&style=for-the-badge)](https://github.com/gregyjames/ZenithTA/)\n\n\n\n# ZenithTA\n#### Formerly Panther\nA efficient, high-performance python technical analysis library written in Rust using PyO3 and rust-numpy. \n\n## Indicators\n- ATR\n- CMF\n- SMA\n- EMA\n- RSI\n- MACD\n- ROC\n\n## How to install\n`pip3 install zenithta`\n\n## How to build (Windows)\n- Run `cargo build --release` from the main directory.\n- Get the generated dll from the target/release directory.\n- Rename extension from .dll to .pyd.\n- Place .pyd file in the same folder as script. \n- Put `from panther import *` in python script.\n \n## Speed\nOn average, I found the Panther calculations of these indicators to be about 9x or 900% faster than the industry standard way of calculating these indicators using Pandas. Don't believe me? Install the library and run the tests in the speed_tests directory to see it for yourself :)\n\n## License\nMIT License\n\nCopyright (c) 2022 Greg James\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"maturin>=0.12,<0.13\"]\nbuild-backend = \"maturin\"\n\n[project]\nname = \"ZenithTA\"\ndependencies = [\n    \"numpy\"\n]\n"
  },
  {
    "path": "speed_tests/ema.py",
    "content": "import pandas_datareader as pdr\nimport pandas as pd\nfrom ZenithTA import *\nfrom timeit import default_timer as timer\nfrom datetime import timedelta\n\ndata = pdr.get_data_yahoo('NVDA')\n\nprint(\"Timing ZenithTA:\")\nstart = timer()\na = data['Close'].tolist()\nj = ema(a,4,2.0)\nend = timer()\nprint(j[0:10:1])\nprint(timedelta(seconds=end-start))\n\nprint(\"Timing Pandas:\")\nstart = timer()\ndata['4dayEWM'] = data['Close'].ewm(span=4, adjust=False).mean()\nb = data['4dayEWM'].tolist()\nprint(b[0:10:1])\nend = timer()\nprint(timedelta(seconds=end-start))"
  },
  {
    "path": "speed_tests/sma.py",
    "content": "import pandas_datareader as pdr\nimport pandas as pd\nfrom ZenithTA import *\nfrom timeit import default_timer as timer\nfrom datetime import timedelta\n\ndata = pdr.get_data_yahoo('NVDA')\n\nprint(\"Timing ZenithTA:\")\na = data['Close'].tolist()\nstart = timer()\nj = sma(a,5)\nend = timer()\nprint(timedelta(seconds=end-start))\n\nprint(\"Timing Pandas:\")\nstart = timer()\ndata['SMA(5)'] = data.Close.rolling(5).mean()\nend = timer()\nb = data['SMA(5)'].tolist()\nprint(timedelta(seconds=end-start))\n"
  },
  {
    "path": "src/lib.rs",
    "content": "use pyo3::prelude::*;\nuse pyo3::wrap_pyfunction;\nuse numpy::ndarray::prelude::*;\n\n//Helper functions for indicators\nfn sma_helper(price_ndarray: &Array1<f32>, period: usize) -> Array1<f32>{\n    let length = price_ndarray.len() - period +1;\n    let mut result = Array1::<f32>::zeros(length);\n\n    for i in 0..length {\n        let slice = price_ndarray.slice(s![i..i+period]);\n        result[i] = slice.sum()/(period as f32);\n    }\n    \n    result\n}\n\nfn ema_helper(price_ndarray: &Array1<f32>, period: usize) -> Array1<f32>{\n    let length = price_ndarray.len() - period +1;\n    let mut result = Array1::<f32>::zeros(length);\n    result[0] = price_ndarray.slice(s![0..period]).sum();\n    for i in 1..length{\n        result[i] = result[i-1]+(price_ndarray[i+period-1]-result[i-1])*(2.0/((period as f32)+1.0));\n    }\n    \n    result\n}\n\n//Indicator Python function wrappers\n#[pyfunction]\nfn sma(price: Vec<f32>, period: usize) -> PyResult<Vec<f32>> {\n    let price_ndarray = Array::from_vec(price);\n    let length = price_ndarray.len() - period +1;\n    let mut result = Array1::<f32>::zeros(length);\n\n    for i in 0..length {\n        let slice = price_ndarray.slice(s![i..i+period]);\n        result[i] = slice.sum()/(period as f32);\n    }\n\n    Ok(Array::to_vec(&result))\n}\n\n#[pyfunction]\nfn ema(price: Vec<f32>, period: usize, smoothing: f32) -> PyResult<Vec<f32>> {\n    let price_ndarray = Array::from_vec(price);\n    let length = price_ndarray.len();\n    let mut result = Array1::<f32>::zeros(length);\n    \n    let weight = smoothing / (period + 1) as f32;\n    result[0] = price_ndarray[0];\n\n    for i in 1..length {\n        result[i] = (price_ndarray[i]*weight) + (result[i-1] * (1.0-weight));\n    }\n\n    Ok(Array::to_vec(&result))\n}\n\n#[pyfunction]\nfn rsi(price: Vec<f32>, period: usize) -> PyResult<Vec<f32>>{\n    let price_ndarray = Array::from_vec(price);\n    let mut change = Array1::<f32>::zeros(price_ndarray.len());\n    let mut gain = Array1::<f32>::zeros(price_ndarray.len());\n    let mut loss = Array1::<f32>::zeros(price_ndarray.len());\n    let mut ag = Array1::<f32>::zeros(price_ndarray.len());\n    let mut al = Array1::<f32>::zeros(price_ndarray.len());\n    let mut result = Array1::<f32>::zeros(price_ndarray.len());\n    for i in 1..price_ndarray.len(){\n        change[i] = price_ndarray[i] - price_ndarray[i-1];\n        if change[i] == 0.0{\n            gain[i] = 0.0;\n            loss[i] = 0.0;\n        } else if change[i]<0.0{\n            gain[i] = 0.0;\n            loss[i] = (change[i]).abs();\n        }else{\n            gain[i] = change[i];\n            loss[i] = 0.0;\n        }\n    }\n    ag[period] = gain.slice(s![1..period+1]).sum()/(period as f32);\n    al[period] = loss.slice(s![1..period+1]).sum()/(period as f32);\n    \n    for i in period+1..price_ndarray.len(){\n        ag[i] = (ag[i-1]*(period as f32-1.0)+gain[i])/period as f32;\n        al[i] = (al[i-1]*(period as f32-1.0)+loss[i])/period as f32;\n    }\n    for i in period+1..price_ndarray.len(){\n        result[i] = 100.0-(100.0/(1.0+ag[i]/al[i]))\n    }\n\n    //result.slice(s![period..]).to_owned()\n    Ok(Array::to_vec(&(result.slice(s![period..]).to_owned())))\n}\n\n#[pyfunction]\nfn macd(price: Vec<f32>, period_fast: usize, period_slow: usize, period_signal: usize) -> PyResult<(Vec<f32>,Vec<f32>)>{\n    let price_ndarray = Array::from_vec(price);\n    let line = ema_helper(&price_ndarray, period_fast).slice(s![period_slow-period_fast..]).to_owned() - ema_helper(&price_ndarray,period_slow);\n    let signal = ema_helper(&price_ndarray, period_signal);\n\n    Ok((Array::to_vec(&line), Array::to_vec(&signal)))\n}\n\n#[pyfunction]\nfn roc(price: Vec<f32>, period: usize) -> PyResult<Vec<f32>> {\n    let price_ndarray = Array::from_vec(price);\n    let length = price_ndarray.len() - period;\n    let mut result = Array1::<f32>::zeros(length);\n\n    for i in period..price_ndarray.len() {\n        result[i-period] = ((price_ndarray[i]-price_ndarray[i-period])/price_ndarray[i-period])*100.0;\n    }\n\n    Ok(Array::to_vec(&result))\n}\n\n#[pyfunction]\nfn atr(high: Vec<f32>, low: Vec<f32>, close: Vec<f32>, period: usize) -> PyResult<Vec<f32>> {\n    let length = high.len();\n    let high_ndarray = Array::from_vec(high);\n    let low_ndarray = Array::from_vec(low);\n    let close_ndarray = Array::from_vec(close);\n    let mut tr  = Array1::<f32>::zeros(length);\n    let mut result = Array1::<f32>::zeros(length - period + 1);\n    \n    tr[0] = high_ndarray[0]-low_ndarray[0];\n    for i in 1..high_ndarray.len() {\n        let hl = high_ndarray[i] - low_ndarray[i];\n        let hpc = (high_ndarray[i] - close_ndarray[i-1]).abs();\n        let lpc = (low_ndarray[i] - close_ndarray[i-1]).abs();\n        tr[i] = f32::max(f32::max(hl, hpc), lpc);\n    }\n\n\n    result[0] = tr.slice(s![0..period]).sum()/period as f32;\n\n    for i in 1.. length -period+1 {\n        result[i] = (result[i-1]*(period as f32-1.0)+tr[i+period-1])/period as f32;\n    }\n    Ok(Array::to_vec(&result))\n}\n\n#[pyfunction]\nfn cmf(high: Vec<f32>, low: Vec<f32>, close: Vec<f32>, volume: Vec<f32>, period: usize) -> PyResult<Vec<f32>> {\n    let length = high.len();\n    let high_ndarray = Array::from_vec(high);\n    let low_ndarray = Array::from_vec(low);\n    let close_ndarray = Array::from_vec(close);\n    let volume_ndarray = Array::from_vec(volume);\n    let mut result = Array1::<f32>::zeros(length - period + 1);\n\n    let a = (&close_ndarray)-(&low_ndarray);\n    let b = (&high_ndarray)-(&close_ndarray);\n    let c = (&high_ndarray)-(&low_ndarray);\n\n    let mfv = ((a-b)/c)*period as f32;\n    for i in 0..length-period+1 {\n        result[i] =  mfv.slice(s![i..i+period]).sum()/ volume_ndarray.slice(s![i..i+period]).sum();\n    }\n\n    Ok(Array::to_vec(&result))\n}\n\n#[pymodule]\nfn ZenithTA(_py: Python, m: &PyModule) -> PyResult<()> {\n    m.add_function(wrap_pyfunction!(sma, m)?)?;\n    m.add_function(wrap_pyfunction!(cmf, m)?)?;\n    m.add_function(wrap_pyfunction!(atr, m)?)?;\n    m.add_function(wrap_pyfunction!(ema, m)?)?;\n    m.add_function(wrap_pyfunction!(rsi, m)?)?;\n    m.add_function(wrap_pyfunction!(roc, m)?)?;\n    m.add_function(wrap_pyfunction!(macd, m)?)?;\n    Ok(())\n}\n\n"
  }
]