├── .devcontainer ├── devcontainer.json └── setup.sh ├── .editorconfig ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── dependabot.yml └── workflows │ ├── deploy-package.yml │ ├── deploy-website.yml │ ├── lint-pull-request.yml │ ├── lock-issue-pr.yml │ ├── test-code-coverage.yml │ ├── test-indicators-full.yml │ ├── test-indicators.yml │ ├── test-localization.yml │ ├── test-performance.yml │ ├── test-website-a11y.yml │ └── test-website-links.yml ├── .gitignore ├── .pylintrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── benchmarks ├── __init__.py ├── conftest.py └── test_benchmark_indicators.py ├── docs ├── .offline │ ├── social-banner.pptx │ └── social-banner.svg ├── .pa11yci ├── 404.html ├── Gemfile ├── Gemfile.lock ├── README.md ├── _config.yml ├── _data │ ├── aliases.yml │ └── categories.yml ├── _headers ├── _includes │ ├── candle-result.md │ ├── candlepart-options.md │ ├── cyclotron.html │ ├── footer.html │ ├── head-style.html │ ├── head.html │ ├── header.html │ └── scripts.html ├── _indicators │ ├── Adl.md │ ├── Adx.md │ ├── Alligator.md │ ├── Alma.md │ ├── Aroon.md │ ├── Atr.md │ ├── AtrStop.md │ ├── Awesome.md │ ├── Beta.md │ ├── BollingerBands.md │ ├── Bop.md │ ├── Cci.md │ ├── ChaikinOsc.md │ ├── Chandelier.md │ ├── Chop.md │ ├── Cmf.md │ ├── Cmo.md │ ├── ConnorsRsi.md │ ├── Correlation.md │ ├── Dema.md │ ├── Doji.md │ ├── Donchian.md │ ├── Dpo.md │ ├── Dynamic.md │ ├── ElderRay.md │ ├── Ema.md │ ├── Epma.md │ ├── Fcb.md │ ├── FisherTransform.md │ ├── ForceIndex.md │ ├── Fractal.md │ ├── Gator.md │ ├── HeikinAshi.md │ ├── Hma.md │ ├── HtTrendline.md │ ├── Hurst.md │ ├── Ichimoku.md │ ├── Kama.md │ ├── Keltner.md │ ├── Kvo.md │ ├── MaEnvelopes.md │ ├── Macd.md │ ├── Mama.md │ ├── Marubozu.md │ ├── Mfi.md │ ├── Obv.md │ ├── ParabolicSar.md │ ├── PivotPoints.md │ ├── Pivots.md │ ├── Pmo.md │ ├── Prs.md │ ├── Pvo.md │ ├── Renko.md │ ├── Roc.md │ ├── RollingPivots.md │ ├── Rsi.md │ ├── Slope.md │ ├── Sma.md │ ├── Smi.md │ ├── Smma.md │ ├── StarcBands.md │ ├── Stc.md │ ├── StdDev.md │ ├── StdDevChannels.md │ ├── Stoch.md │ ├── StochRsi.md │ ├── SuperTrend.md │ ├── T3.md │ ├── Tema.md │ ├── Trix.md │ ├── Tsi.md │ ├── UlcerIndex.md │ ├── Ultimate.md │ ├── VolatilityStop.md │ ├── Vortex.md │ ├── Vwap.md │ ├── Vwma.md │ ├── WilliamsR.md │ ├── Wma.md │ └── ZigZag.md ├── _layouts │ ├── base.html │ ├── indicator.html │ └── page.html ├── assets │ ├── css │ │ ├── normalize.css │ │ ├── rouge-github.css │ │ └── style.scss │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── maskable_icon_x192.png │ │ ├── maskable_icon_x512.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ └── safari-pinned-tab.svg │ ├── js │ │ └── lazysizes.min.js │ ├── manifest.json │ └── social-banner.png ├── contributing.md ├── favicon.ico ├── favicon.svg └── pages │ ├── guide.md │ ├── home.md │ ├── indicators.html │ ├── performance.md │ └── utilities.md ├── pyproject.toml ├── pytest.ini ├── requirements-test.txt ├── requirements.txt ├── stock_indicators ├── __init__.py ├── _cslib │ ├── __init__.py │ └── lib │ │ └── Skender.Stock.Indicators.dll ├── _cstypes │ ├── __init__.py │ ├── datetime.py │ ├── decimal.py │ └── list.py ├── indicators │ ├── __init__.py │ ├── adl.py │ ├── adx.py │ ├── alligator.py │ ├── alma.py │ ├── aroon.py │ ├── atr.py │ ├── atr_stop.py │ ├── awesome.py │ ├── basic_quotes.py │ ├── beta.py │ ├── bollinger_bands.py │ ├── bop.py │ ├── cci.py │ ├── chaikin_oscillator.py │ ├── chandelier.py │ ├── chop.py │ ├── cmf.py │ ├── cmo.py │ ├── common │ │ ├── __init__.py │ │ ├── _contrib │ │ │ ├── __init__.py │ │ │ ├── enum.py │ │ │ └── type_resolver.py │ │ ├── candles.py │ │ ├── enums.py │ │ ├── helpers.py │ │ ├── quote.py │ │ └── results.py │ ├── connors_rsi.py │ ├── correlation.py │ ├── dema.py │ ├── doji.py │ ├── donchian.py │ ├── dpo.py │ ├── dynamic.py │ ├── elder_ray.py │ ├── ema.py │ ├── epma.py │ ├── fcb.py │ ├── fisher_transform.py │ ├── force_index.py │ ├── fractal.py │ ├── gator.py │ ├── heikin_ashi.py │ ├── hma.py │ ├── ht_trendline.py │ ├── hurst.py │ ├── ichimoku.py │ ├── kama.py │ ├── keltner.py │ ├── kvo.py │ ├── ma_envelopes.py │ ├── macd.py │ ├── mama.py │ ├── marubozu.py │ ├── mfi.py │ ├── obv.py │ ├── parabolic_sar.py │ ├── pivot_points.py │ ├── pivots.py │ ├── pmo.py │ ├── prs.py │ ├── pvo.py │ ├── renko.py │ ├── roc.py │ ├── rolling_pivots.py │ ├── rsi.py │ ├── slope.py │ ├── sma.py │ ├── smi.py │ ├── smma.py │ ├── starc_bands.py │ ├── stc.py │ ├── stdev.py │ ├── stdev_channels.py │ ├── stoch.py │ ├── stoch_rsi.py │ ├── super_trend.py │ ├── t3.py │ ├── tema.py │ ├── tr.py │ ├── trix.py │ ├── tsi.py │ ├── ulcer_index.py │ ├── ultimate.py │ ├── volatility_stop.py │ ├── vortex.py │ ├── vwap.py │ ├── vwma.py │ ├── williams_r.py │ ├── wma.py │ └── zig_zag.py └── logging_config.py ├── test_data ├── quotes │ ├── Bad.csv │ ├── Bitcoin.csv │ ├── Compare.csv │ ├── Default.csv │ ├── History.xlsx │ ├── Intraday.csv │ ├── Longest.csv │ ├── Longish.csv │ ├── MSFT.csv │ ├── Max.csv │ ├── Mismatch.csv │ ├── Penny.csv │ ├── SPX.csv │ ├── TooBig.csv │ └── ZigZag.csv └── zig_zag │ ├── data.ethusdt.json │ ├── data.issue632.json │ └── data.schrodinger.json └── tests ├── __init__.py ├── common ├── test_candle.py ├── test_common.py ├── test_cstype_conversion.py ├── test_indicator_results.py ├── test_locale.py └── test_type_compatibility.py ├── conftest.py ├── test_adl.py ├── test_adx.py ├── test_alligator.py ├── test_alma.py ├── test_aroon.py ├── test_atr.py ├── test_atr_stop.py ├── test_awesome.py ├── test_basic_quote.py ├── test_beta.py ├── test_bollinger_bands.py ├── test_bop.py ├── test_cci.py ├── test_chaikin_oscillator.py ├── test_chandelier.py ├── test_chop.py ├── test_cmf.py ├── test_cmo.py ├── test_connors_rsi.py ├── test_correlation.py ├── test_dema.py ├── test_doji.py ├── test_donchian.py ├── test_dpo.py ├── test_dynamic.py ├── test_elder_ray.py ├── test_ema.py ├── test_epma.py ├── test_fcb.py ├── test_fisher_transform.py ├── test_force_index.py ├── test_fractal.py ├── test_gator.py ├── test_heikin_ashi.py ├── test_hma.py ├── test_ht_trendline.py ├── test_hurst.py ├── test_ichimoku.py ├── test_kama.py ├── test_keltner.py ├── test_kvo.py ├── test_ma_envelopes.py ├── test_macd.py ├── test_mama.py ├── test_marubozu.py ├── test_mfi.py ├── test_obv.py ├── test_parabolic_sar.py ├── test_pivot_points.py ├── test_pivots.py ├── test_pmo.py ├── test_prs.py ├── test_pvo.py ├── test_renko.py ├── test_roc.py ├── test_rolling_pivots.py ├── test_rsi.py ├── test_slope.py ├── test_sma.py ├── test_sma_analysis.py ├── test_smi.py ├── test_smma.py ├── test_starc_bands.py ├── test_stc.py ├── test_stdev.py ├── test_stdev_channels.py ├── test_stoch.py ├── test_stoch_rsi.py ├── test_super_trend.py ├── test_t3.py ├── test_tema.py ├── test_tr.py ├── test_trix.py ├── test_tsi.py ├── test_ulcer_index.py ├── test_ultimate.py ├── test_volatility_stop.py ├── test_vortex.py ├── test_vwap.py ├── test_vwma.py ├── test_williams_r.py ├── test_wma.py ├── test_zig_zag.py └── utiltest.py /.devcontainer/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install or upgrade pip 4 | python -m ensurepip --upgrade 5 | 6 | # install core dependencies 7 | pip install -r requirements.txt 8 | 9 | # install test dependencies 10 | pip install -r requirements-test.txt 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Global settings for all files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | indent_style = space 14 | indent_size = 2 15 | tab_width = 2 16 | 17 | #### File Type Overrides #### 18 | 19 | [*.{py,java,r,R}] 20 | indent_style = space 21 | indent_size = 4 22 | 23 | [*.{md,Rmd,rst}] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize line endings. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report. 3 | type: Bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you 💖 for taking the time report a bug in the [`stock-indicators` PyPI library](https://pypi.org/project/stock-indicators)! 9 | > Before reporting, please review known [issues](https://github.com/facioquo/stock-indicators-python/issues?q=is%3Aissue), community [discussions](https://github.com/DaveSkender/Stock.Indicators/discussions), and [Help! Results don't match TradingView!](https://github.com/DaveSkender/Stock.Indicators/discussions/801) 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: What did you expect to happen? 15 | placeholder: Tell us what you see! 16 | validations: 17 | required: true 18 | - type: markdown 19 | attributes: 20 | value: If your request is urgent, please review our [sponsorship tiers](https://github.com/sponsors/facioquo). For contract custom development, contact [Skender Co.](https://skenderco.com) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project. 3 | type: Feature 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you 💖 for taking the time to make a recommendation for the [`stock-indicators` PyPI library](https://pypi.org/project/stock-indicators)! If you have an idea that you don't want us to track in [our backlog](https://github.com/orgs/facioquo/projects/16), consider posting in our community [discussions](https://github.com/DaveSkender/Stock.Indicators/discussions). 9 | - type: textarea 10 | id: request 11 | attributes: 12 | label: the problem 13 | description: What problem are you trying to solve? Or, what outcome do you want achieve? Why? 14 | placeholder: I'm always frustrated when [...] 15 | validations: 16 | required: true 17 | - type: markdown 18 | attributes: 19 | value: If your request is urgent, please review our [sponsorship tiers](https://github.com/sponsors/facioquo). For contract custom development, contact [Skender Co.](https://skenderco.com) 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### done when 2 | 3 | - [ ] Fix: # 4 | 5 | #### the details 6 | 7 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a vulnerability 2 | 3 | To report a vulnerability, please contact us privately through the NuGet [Contact Owners](https://www.nuget.org/packages/Skender.Stock.Indicators) feature. Please include a detailed description of the vulnerability. 4 | 5 | We will inform everyone of vulnerability fixes in our release notes. 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration 2 | version: 2 3 | updates: 4 | - package-ecosystem: "pip" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | - package-ecosystem: "bundler" 9 | directory: "/docs" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.github/workflows/deploy-website.yml: -------------------------------------------------------------------------------- 1 | name: Deploy website 2 | 3 | on: workflow_dispatch 4 | 5 | permissions: 6 | contents: read 7 | 8 | concurrency: 9 | group: docs-website 10 | 11 | env: 12 | JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | 14 | jobs: 15 | deploy: 16 | name: Cloudflare Pages 17 | runs-on: ubuntu-latest 18 | defaults: 19 | run: 20 | working-directory: docs 21 | env: 22 | BUNDLE_GEMFILE: ${{github.workspace}}/docs/Gemfile 23 | JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | environment: 26 | name: stockindicators.dev 27 | url: ${{ steps.deploy.outputs.pages-deployment-alias-url }} 28 | 29 | steps: 30 | - name: Checkout source 31 | uses: actions/checkout@v4 32 | 33 | - name: Setup Ruby 34 | uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: 3.3 37 | 38 | - name: Install dependencies 39 | run: bundle install 40 | 41 | - name: Define tag 42 | id: tag 43 | run: echo "version=$(date +'%Y.%m.%d')-${{ github.run_number }}" >> $GITHUB_OUTPUT 44 | 45 | - name: Replace cache markers 46 | uses: jacobtomlinson/gha-find-replace@v3 47 | with: 48 | find: "YYYY.MM.DD" 49 | replace: "${{ steps.tag.outputs.version }}" 50 | regex: false 51 | 52 | - name: Build site (production) 53 | if: github.ref == 'refs/heads/main' 54 | env: 55 | JEKYLL_ENV: production 56 | run: bundle exec jekyll build 57 | 58 | - name: Build site (preview) 59 | if: github.ref != 'refs/heads/main' 60 | env: 61 | JEKYLL_ENV: preview 62 | run: bundle exec jekyll build 63 | 64 | - name: Publish to Cloudflare Pages 65 | id: deploy 66 | uses: cloudflare/wrangler-action@v3 67 | with: 68 | apiToken: ${{ secrets.CLOUDFLARE_API_KEY }} 69 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 70 | wranglerVersion: "latest" 71 | command: > 72 | pages deploy docs/_site 73 | --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }} 74 | -------------------------------------------------------------------------------- /.github/workflows/lock-issue-pr.yml: -------------------------------------------------------------------------------- 1 | name: Lock closed threads 2 | # https://github.com/marketplace/actions/lock-threads 3 | 4 | on: 5 | schedule: 6 | - cron: "0 7 * * 3" 7 | workflow_dispatch: 8 | 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | 13 | concurrency: 14 | group: lock 15 | 16 | jobs: 17 | lock: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: dessant/lock-threads@v5 21 | with: 22 | process-only: issues, prs 23 | issue-inactive-days: "90" 24 | pr-inactive-days: "30" 25 | -------------------------------------------------------------------------------- /.github/workflows/test-code-coverage.yml: -------------------------------------------------------------------------------- 1 | # For more information see: 2 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Test code coverage 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | pull_request_target: 10 | branches: ["*"] 11 | workflow_dispatch: 12 | 13 | # Limit concurrent runs 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 16 | cancel-in-progress: true 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | test: 23 | name: indicators 24 | runs-on: ubuntu-latest 25 | 26 | env: 27 | CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} 28 | 29 | steps: 30 | - name: Checkout PR 31 | uses: actions/checkout@v4 32 | with: 33 | # Explicitly checkout PR code 34 | ref: ${{ github.event.pull_request.head.sha }} 35 | 36 | - name: Setup .NET 37 | uses: actions/setup-dotnet@v4 38 | with: 39 | dotnet-version: 9.x 40 | dotnet-quality: ga 41 | 42 | - name: Setup Python 43 | uses: actions/setup-python@v5 44 | with: 45 | python-version: 3.13 46 | cache: "pip" 47 | 48 | - name: Install dependencies 49 | run: | 50 | pip install -U --upgrade-strategy=only-if-needed pip 51 | pip install -r requirements.txt 52 | pip install -r requirements-test.txt 53 | 54 | - name: Test indicators 55 | run: | 56 | coverage run -m --source=stock_indicators pytest 57 | coverage xml 58 | 59 | - name: Publish coverage to Codacy 60 | uses: codacy/codacy-coverage-reporter-action@v1 61 | with: 62 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 63 | coverage-reports: coverage.xml 64 | -------------------------------------------------------------------------------- /.github/workflows/test-indicators-full.yml: -------------------------------------------------------------------------------- 1 | # For more information see: 2 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Unit tests (full matrix) 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | paths: 10 | - stock_indicators/** 11 | - .github/workflows/test-indicators-full.yml 12 | workflow_dispatch: 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | test: 19 | 20 | strategy: 21 | matrix: 22 | os: [windows-2025, ubuntu-24.04-arm, macos-15] 23 | python-version: ["3.8", "3.10", "3.12"] 24 | dotnet-version: ["6.x", "8.x", "9.x"] 25 | 26 | runs-on: ${{ matrix.os }} 27 | name: "Py${{ matrix.python-version }}/.NET${{ matrix.dotnet-version }} on ${{ matrix.os }}" 28 | 29 | env: 30 | 31 | # identify configurations that post test reports 32 | POST_REPORT: ${{ matrix.python-version == '3.12' && matrix.dotnet-version == '9.x' }} 33 | 34 | steps: 35 | - name: Checkout source 36 | uses: actions/checkout@v4 37 | 38 | - name: Setup .NET 39 | uses: actions/setup-dotnet@v4 40 | with: 41 | dotnet-version: ${{ matrix.dotnet-version }} 42 | dotnet-quality: ga 43 | 44 | - name: Setup Python 45 | uses: actions/setup-python@v5 46 | with: 47 | python-version: ${{ matrix.python-version }} 48 | cache: "pip" 49 | 50 | - name: Install dependencies 51 | run: | 52 | pip install -U --upgrade-strategy=only-if-needed pip 53 | pip install -r requirements.txt 54 | pip install -r requirements-test.txt 55 | 56 | - name: Test indicators 57 | run: pytest -vr A tests --junitxml=test-results.xml 58 | 59 | - name: Post test summary 60 | uses: test-summary/action@v2 61 | if: env.POST_REPORT == 'true' && always() 62 | with: 63 | paths: test-results.xml 64 | -------------------------------------------------------------------------------- /.github/workflows/test-website-a11y.yml: -------------------------------------------------------------------------------- 1 | name: Website a11y 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | paths: 7 | - docs/** 8 | - .github/workflows/test-website-a11y.yml 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: read 13 | 14 | # Prevent multiple concurrent runs 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | runs-on: ubuntu-latest 22 | defaults: 23 | run: 24 | working-directory: docs 25 | env: 26 | BUNDLE_GEMFILE: ${{github.workspace}}/docs/Gemfile 27 | JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | steps: 30 | - name: Checkout source 31 | uses: actions/checkout@v4 32 | 33 | - name: Setup Ruby 34 | uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: 3.3 37 | 38 | - name: Install dependencies 39 | run: | 40 | bundle install 41 | npm install -g pa11y-ci 42 | 43 | - name: Use 'localhost' 44 | uses: jacobtomlinson/gha-find-replace@v3 45 | with: 46 | find: "https://python.stockindicators.dev" 47 | replace: "http://127.0.0.1:4000" 48 | regex: false 49 | 50 | - name: Build site 51 | run: bundle exec jekyll build 52 | 53 | - name: Serve site 54 | run: bundle exec jekyll serve --port 4000 --detach 55 | 56 | - name: Show environment 57 | run: npx pa11y --environment 58 | 59 | - name: Test accessibility 60 | run: > 61 | pa11y-ci 62 | --sitemap http://127.0.0.1:4000/sitemap.xml 63 | --config ./.pa11yci 64 | 65 | - name: Kill site (failsafe) 66 | if: always() 67 | run: pkill -f jekyll 68 | -------------------------------------------------------------------------------- /.github/workflows/test-website-links.yml: -------------------------------------------------------------------------------- 1 | name: Website URLs 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | paths: 7 | - docs/** 8 | - .github/workflows/test-website-links.yml 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: read 13 | 14 | # Prevent multiple concurrent runs 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | runs-on: ubuntu-latest 22 | defaults: 23 | run: 24 | working-directory: docs 25 | env: 26 | BUNDLE_GEMFILE: ${{github.workspace}}/docs/Gemfile 27 | JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | steps: 30 | - name: Checkout source 31 | uses: actions/checkout@v4 32 | 33 | - name: Setup Ruby 34 | uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: 3.3 37 | 38 | - name: Install dependencies 39 | run: | 40 | bundle install 41 | gem install html-proofer 42 | 43 | - name: Replace "data-src" 44 | uses: jacobtomlinson/gha-find-replace@v3 45 | with: 46 | find: "data-src" 47 | replace: "src" 48 | regex: false 49 | 50 | - name: Use 'localhost' 51 | uses: jacobtomlinson/gha-find-replace@v3 52 | with: 53 | find: "https://python.stockindicators.dev" 54 | replace: "http://127.0.0.1:4000" 55 | regex: false 56 | 57 | - name: Build site 58 | run: bundle exec jekyll build 59 | 60 | - name: Serve site 61 | run: bundle exec jekyll serve --port 4000 --detach 62 | 63 | # see help in setup step 64 | - name: Test for broken URLs 65 | run: > 66 | htmlproofer _site 67 | --no-enforce-https 68 | --no-check-external-hash 69 | --ignore-status-codes "0,302,403,406,408,429,503,999" 70 | --ignore-urls "/fonts.gstatic.com/" 71 | 72 | - name: Kill site (failsafe) 73 | if: always() 74 | run: pkill -f jekyll 75 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | extension-pkg-allow-list= 3 | clr 4 | 5 | ignore-long-lines=yes 6 | 7 | ignore-imports=yes 8 | 9 | disable= 10 | C0103, # Variable name doesn't conform to snake_case naming style 11 | C0114, # Missing module docstring 12 | C0115, # Missing class docstring 13 | C0116, # Missing function or method docstring 14 | C0301, # Line too long 15 | C0321, # More than one statement on a single line 16 | C0413, # Import should be at the top of the file 17 | C0415, # Import outside toplevel 18 | E0401, # Import error 19 | W0212, # Access to a protected member of a client class 20 | R0903, # Too few public methods 21 | R0913 # Too many arguments 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "donjayamanne.python-extension-pack", 4 | "DavidAnson.vscode-markdownlint", 5 | "EditorConfig.EditorConfig", 6 | "ms-python.black-formatter", 7 | "ms-python.debugpy", 8 | "ms-python.isort", 9 | "ms-python.python", 10 | "ms-python.pylint", 11 | "ms-python.vscode-pylance" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Debug File", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal", 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | { 13 | "name": "Python: Run Tests", 14 | "type": "debugpy", 15 | "request": "launch", 16 | "module": "pytest", 17 | "console": "integratedTerminal", 18 | "args": ["-svr", "A"], 19 | "cwd": "${workspaceFolder}" 20 | }, 21 | { 22 | "name": "Python: Debug Current Test File", 23 | "type": "debugpy", 24 | "request": "launch", 25 | "module": "pytest", 26 | "console": "integratedTerminal", 27 | "args": ["-svr", "A", "${file}"], 28 | "cwd": "${workspaceFolder}" 29 | } 30 | ], 31 | "inputs": [ 32 | { 33 | "type": "promptString", 34 | "id": "programPath", 35 | "description": "Path to the Python file you want to debug" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "isort.args": [ 3 | "--profile", 4 | "black" 5 | ], 6 | "isort.check": true, 7 | "isort.importStrategy": "fromEnvironment", // default: "useBundled" 8 | "markdownlint.config": { 9 | "default": true, // Enable all default rules 10 | "MD013": false, // Disable line length checking entirely 11 | "MD025": false, // Allow multiple top level headers in the same document 12 | "MD033": { // Allow specific HTML elements 13 | "allowed_elements": [ 14 | "details", 15 | "summary", 16 | "h1" // we use h1 as a Jekyll-y page title 17 | ] 18 | }, 19 | "MD041": false // Allow content before first heading 20 | }, 21 | "pylint.importStrategy": "fromEnvironment", // default: "useBundled" 22 | "python.testing.pytestArgs": [ 23 | "tests" 24 | ], 25 | "python.testing.pytestEnabled": true, 26 | "python.testing.unittestEnabled": false, 27 | "[markdown]": { 28 | "editor.defaultFormatter": "DavidAnson.vscode-markdownlint", 29 | "files.trimTrailingWhitespace": true 30 | }, 31 | "[python]": { 32 | "editor.defaultFormatter": "ms-python.black-formatter" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "type": "shell", 7 | "command": "pip install -r requirements.txt && pip install -r requirements-test.txt", 8 | "group": "build", 9 | "problemMatcher": [] 10 | }, 11 | { 12 | "label": "Test: Unit (default)", 13 | "type": "shell", 14 | "command": "pytest -vr A", 15 | "group": "test", 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "label": "Test: Performance", 20 | "type": "shell", 21 | "command": "pytest -m performance", 22 | "group": "none", 23 | "problemMatcher": [] 24 | }, 25 | { 26 | "label": "Test: Localization", 27 | "type": "shell", 28 | "command": "pytest -m localization -vr A", 29 | "group": "none", 30 | "problemMatcher": [] 31 | }, 32 | { 33 | "label": "Test: All", 34 | "type": "shell", 35 | "command": "pytest -m \"\"", 36 | "group": "none", 37 | "problemMatcher": [] 38 | }, 39 | { 40 | "label": "Test: Coverage", 41 | "type": "shell", 42 | "command": "pytest --cov=stock_indicators --cov-report=term-missing", 43 | "group": "none", 44 | "problemMatcher": [] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /benchmarks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/benchmarks/__init__.py -------------------------------------------------------------------------------- /docs/.offline/social-banner.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/.offline/social-banner.pptx -------------------------------------------------------------------------------- /docs/.offline/social-banner.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | stock 26 | indicators 27 | for Python 28 | stock-indicators 29 | @pypi.org 30 | https://python.stockindicators.dev 31 | 32 | -------------------------------------------------------------------------------- /docs/.pa11yci: -------------------------------------------------------------------------------- 1 | { 2 | "defaults": { 3 | "concurrency": 1, 4 | "timeout": 30000, 5 | "wait": 1000, 6 | "standard": "WCAG2AA", 7 | "hideElements": "#searchzone", 8 | "port": 4000, 9 | "chromeLaunchConfig": { 10 | "args": [ 11 | "--no-sandbox", 12 | "--disable-setuid-sandbox" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page not found 3 | permalink: /404.html 4 | layout: base 5 | redirect: true 6 | --- 7 | 8 |
9 |

Your strategy was unprofitable

10 |

error 404 ~ page not found

11 |
12 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | # local dev: bundle exec jekyll serve -o -l 3 | 4 | # jekyll plugins 5 | group :jekyll_plugins do 6 | gem 'github-pages' 7 | gem "jekyll-github-metadata" 8 | gem 'jekyll-seo-tag' 9 | gem 'jekyll-sitemap' 10 | gem 'jekyll-last-modified-at' # used in sitemap 11 | gem 'jekyll-redirect-from' 12 | gem 'jekyll-minifier' 13 | end 14 | 15 | gem 'wdm', '>= 0.2.0' if Gem.win_platform? # windows compatibilty pack 16 | gem 'webrick' # needed for ruby > v3.0 17 | gem 'faraday' # reload/retry on file changes 18 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation website 2 | 3 | Visit [python.stockindicators.dev](https://python.stockindicators.dev) to read our documentation. 4 | 5 | It is developed with Jekyll for GitHub Pages and is not intended to be read from the code repo. 6 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Ref: https://jekyllrb.com/docs/usage/ 2 | # Local dev: bundle exec jekyll serve --livereload 3 | 4 | title: Stock Indicators for Python 5 | tagline: "Transform price quotes into trading insights." 6 | repository: facioquo/stock-indicators-python 7 | locale: en_US 8 | baseurl: "" 9 | url: "https://python.stockindicators.dev" 10 | 11 | # social media information 12 | image: 13 | path: /assets/social-banner.png 14 | height: 640 15 | width: 1280 16 | 17 | logo: /assets/icons/android-chrome-192x192.png 18 | 19 | # .NET site URL refs 20 | dotnet: 21 | repo: "https://github.com/DaveSkender/Stock.Indicators" 22 | src: "https://github.com/DaveSkender/Stock.Indicators/blob/main/src" 23 | charts: "https://raw.githubusercontent.com/DaveSkender/Stock.Indicators/main/docs/assets/charts" 24 | 25 | # this site URL refs 26 | python: 27 | src: "https://github.com/facioquo/stock-indicators-python/blob/main/stock_indicators/indicators" 28 | 29 | clarity: false 30 | 31 | # site plugins and settings 32 | plugins: 33 | - jekyll-github-metadata 34 | - jekyll-seo-tag 35 | - jekyll-sitemap 36 | - jekyll-last-modified-at 37 | - jekyll-redirect-from 38 | - jekyll-minifier 39 | 40 | include: 41 | - _headers 42 | 43 | exclude: 44 | [ 45 | "*.lock", 46 | "*.yml", 47 | "*.pptx", 48 | ".offline", 49 | "docs/README.md", 50 | "docs/dx*", 51 | "Gemfile", 52 | "node_modules", 53 | "vendor", 54 | ] 55 | 56 | permalink: pretty 57 | 58 | defaults: 59 | - scope: 60 | path: "" # an empty string here means all files in the project 61 | values: 62 | layout: "base" 63 | image: /assets/social-banner.png 64 | 65 | # page collections 66 | collections: 67 | indicators: 68 | output: true 69 | permalink: /:path 70 | sort_by: "title" 71 | -------------------------------------------------------------------------------- /docs/_data/aliases.yml: -------------------------------------------------------------------------------- 1 | - title: Bull and Bear Power 2 | permalink: /indicators/ElderRay/ 3 | type: price-characteristic 4 | 5 | - title: Dominant Cycle Periods 6 | permalink: /indicators/HtTrendline/ 7 | type: price-characteristic 8 | 9 | - title: Directional Movement Index (DMI) 10 | permalink: /indicators/Adx/ 11 | type: price-trend 12 | 13 | - title: Historical Volatility (HV) 14 | permalink: /indicators/StdDev/ 15 | type: price-characteristic 16 | 17 | - title: Hurst Exponent 18 | permalink: /indicators/Hurst/ 19 | type: price-trend 20 | 21 | - title: KDJ Index 22 | permalink: /indicators/Stoch/ 23 | type: oscillator 24 | 25 | - title: Least Squares Moving Average (LSMA) 26 | permalink: /indicators/Epma/ 27 | type: moving-average 28 | 29 | - title: Linear Regression (best-fit line) 30 | permalink: /indicators/Slope/ 31 | type: numerical-analysis 32 | 33 | - title: Mean absolute deviation 34 | permalink: /indicators/Sma/ 35 | type: numerical-analysis 36 | 37 | - title: Mean square error 38 | permalink: /indicators/Sma/ 39 | type: numerical-analysis 40 | 41 | - title: Mean absolute percentage error 42 | permalink: /indicators/Sma/ 43 | type: numerical-analysis 44 | 45 | - title: Modified Moving Average (MMA) 46 | permalink: /indicators/Smma/ 47 | type: moving-average 48 | 49 | - title: Momentum Oscillator 50 | permalink: /indicators/Roc/ 51 | type: price-characteristic 52 | 53 | - title: Normalized Average True Range 54 | permalink: /indicators/Atr/ 55 | type: price-characteristic 56 | 57 | - title: Price Channels 58 | permalink: /indicators/Donchian/ 59 | type: price-channel 60 | 61 | - title: R-Squared (Coefficient of Determination) 62 | permalink: /indicators/Correlation/ 63 | type: numerical-analysis 64 | 65 | - title: Rescaled Range Analysis 66 | permalink: /indicators/Hurst/ 67 | type: price-characteristic 68 | 69 | - title: Running Moving Average (RMA) 70 | permalink: /indicators/Smma/ 71 | type: moving-average 72 | 73 | - title: SuperTrend 74 | permalink: /indicators/SuperTrend/ 75 | type: stop-and-reverse 76 | 77 | - title: True Range (TR) 78 | permalink: /indicators/Atr/ 79 | type: price-characteristic 80 | 81 | - title: Z-Score 82 | permalink: /indicators/StdDev/ 83 | type: numerical-analysis 84 | -------------------------------------------------------------------------------- /docs/_data/categories.yml: -------------------------------------------------------------------------------- 1 | - name: Price trends 2 | type: price-trend 3 | - name: Price channels 4 | type: price-channel 5 | - name: Oscillators 6 | type: oscillator 7 | - name: Stop and reverse 8 | type: stop-and-reverse 9 | - name: Candlestick patterns 10 | type: candlestick-pattern 11 | subcategories: 12 | - name: Other price patterns 13 | type: price-pattern 14 | - name: Volume-based 15 | type: volume-based 16 | - name: Moving averages 17 | type: moving-average 18 | - name: Price transforms 19 | type: price-transform 20 | - name: Price characteristics 21 | type: price-characteristic 22 | subcategories: 23 | - name: Numerical analysis 24 | type: numerical-analysis 25 | -------------------------------------------------------------------------------- /docs/_headers: -------------------------------------------------------------------------------- 1 | https://:project.pages.dev/* 2 | X-Robots-Tag: noindex 3 | -------------------------------------------------------------------------------- /docs/_includes/candle-result.md: -------------------------------------------------------------------------------- 1 | ### CandleResult 2 | 3 | | name | type | notes 4 | | -- |-- |-- 5 | | `date` | datetime | Date 6 | | `price` | decimal, Optional | Price of the most relevant OHLC candle element when a match is present 7 | | `match` | [Match]({{site.baseurl}}/guide/#match) | Generated Match type 8 | | `candle` | [CandleProperties]({{site.baseurl}}/guide/#candle) | Candle properties 9 | -------------------------------------------------------------------------------- /docs/_includes/candlepart-options.md: -------------------------------------------------------------------------------- 1 | #### CandlePart options 2 | 3 | ```python 4 | from stock_indicators.indicators.common.enums import CandlePart 5 | ``` 6 | 7 | | type | description 8 | |-- |-- 9 | | `CandlePart.OPEN` | `open` price 10 | | `CandlePart.HIGH` | `high` price 11 | | `CandlePart.LOW` | `low` price 12 | | `CandlePart.CLOSE` | `close` price 13 | | `CandlePart.VOLUME` | `volume` 14 | | `CandlePart.HL2` | `(high+low)/2` 15 | | `CandlePart.HLC3` | `(high+low+close)/3` 16 | | `CandlePart.OC2` | `(open+close)/2` 17 | | `CandlePart.OHL3` | `(open+high+low)/3` 18 | | `CandlePart.OHLC4` | `(open+high+low+close)/4` 19 | -------------------------------------------------------------------------------- /docs/_includes/cyclotron.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Previous / Next functions 3 | Assign variables from the entire indicators collection 4 | {% endcomment %} 5 | {% assign items_raw = site.indicators %} 6 | {% assign items = items_raw | sort: 'title' | reverse %} 7 | 8 | {% if items.size > 1 %} 9 | {% comment %} 10 | Store the index position of the matching items 11 | {% endcomment %} 12 | {% for item in items %} 13 | {% if item.title == page.title %} 14 | {% assign item_index = forloop.index %} 15 | {% endif %} 16 | {% endfor %} 17 | 18 | {% assign prev_index = item_index | plus: 1 %} 19 | {% assign next_index = item_index | minus: 1 %} 20 | {% assign last_index = items.size %} 21 | 22 | {% comment %} 23 | Handle end of range scenarios 24 | {% endcomment %} 25 | {% if prev_index > last_index %} 26 | {% assign prev_index = 1 %} 27 | {% endif %} 28 | {% if next_index <= 0 %} 29 | {% assign next_index = last_index %} 30 | {% endif %} 31 | 32 | {% comment %} 33 | Lookup prev/next items 34 | {% endcomment %} 35 | {% for item in items %} 36 | {% if forloop.index == prev_index %} 37 | {% assign prev = item %} 38 | {% endif %} 39 | {% if forloop.index == next_index %} 40 | {% assign next = item %} 41 | {% endif %} 42 | {% endfor %} 43 | 44 |
45 | 46 | 47 |
48 | {% endif %} -------------------------------------------------------------------------------- /docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {%- if page.dir == '/' -%} 8 | 9 | {%- endif -%} 10 | 11 | 12 | 13 | {%- include head-style.html -%} 14 | 15 | {%- if jekyll.environment != 'production' or page.noindex == true -%} 16 | 17 | 18 | {%- endif -%} 19 | {%- if page.redirect == true -%} 20 | 21 | {%- endif -%} 22 | 23 | {% seo %} 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/_includes/header.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /docs/_includes/scripts.html: -------------------------------------------------------------------------------- 1 | {%- if page.lazy-images == true -%} 2 | 3 | {%- endif -%} 4 | 5 | {%- if jekyll.environment == 'production' -%} 6 | 7 | 8 | 14 | 15 | {%- if site.clarity == 'true' -%} 16 | 17 | 24 | {%- endif -%} 25 | {%- endif -%} 26 | -------------------------------------------------------------------------------- /docs/_layouts/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {%- include head.html -%} 5 | 6 | {%- include header.html -%} 7 | 8 | 9 |
10 | {{ content | replace: 'PREVIEW

18 | {%- endif -%} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/_layouts/indicator.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | 5 | {% include cyclotron.html %} 6 | {{ content }} 7 | {% include footer.html %} 8 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | 5 | {{ content }} 6 | {% include footer.html %} 7 | -------------------------------------------------------------------------------- /docs/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #424242 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /docs/assets/icons/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/maskable_icon_x192.png -------------------------------------------------------------------------------- /docs/assets/icons/maskable_icon_x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/maskable_icon_x512.png -------------------------------------------------------------------------------- /docs/assets/icons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/mstile-144x144.png -------------------------------------------------------------------------------- /docs/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /docs/assets/icons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/mstile-310x150.png -------------------------------------------------------------------------------- /docs/assets/icons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/mstile-310x310.png -------------------------------------------------------------------------------- /docs/assets/icons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/icons/mstile-70x70.png -------------------------------------------------------------------------------- /docs/assets/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | Created by potrace 1.14, written by Peter Selinger 2001-2017 -------------------------------------------------------------------------------- /docs/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stock Indicators for Python", 3 | "short_name": "Stock Indies", 4 | "description": "Transform price quotes into trade indicators and market insights.", 5 | "theme_color": "#424242", 6 | "background_color": "#424242", 7 | "icons": [ 8 | { 9 | "src": "icons/maskable_icon_x192.png", 10 | "sizes": "192x192", 11 | "type": "image/png", 12 | "purpose": "maskable" 13 | }, 14 | { 15 | "src": "icons/maskable_icon_x512.png", 16 | "sizes": "512x512", 17 | "type": "image/png", 18 | "purpose": "maskable" 19 | }, 20 | { 21 | "src": "icons/android-chrome-192x192.png", 22 | "sizes": "192x192", 23 | "type": "image/png", 24 | "purpose": "any" 25 | }, 26 | { 27 | "src": "icons/android-chrome-512x512.png", 28 | "sizes": "512x512", 29 | "type": "image/png", 30 | "purpose": "any" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /docs/assets/social-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/assets/social-banner.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/docs/favicon.ico -------------------------------------------------------------------------------- /docs/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/pages/indicators.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Indicators and overlays 3 | description: The Stock Indicators for Python library contains financial market technical analysis methods to view price patterns or to develop your own trading strategies in Python programming languages and developer platforms. Categories include price trends, price channels, oscillators, stop and reverse, candlestick patterns, volume and momentum, moving averages, price transforms, price characteristics, and many classic numerical methods. 4 | permalink: /indicators/ 5 | layout: base 6 | --- 7 | 8 |

{{ page.title }}

9 | 10 | 11 |
22 | 23 | 24 | {% for c in site.data.categories %} 25 |

{{c.name}}

26 | {% assign indicators = site.indicators | where: "type", c.type %} 27 | {% assign aliases = site.data.aliases | where: "type", c.type %} 28 | {% assign all = indicators | concat: aliases | sort: "title" %} 29 | 34 | 35 | 36 | {% for s in c.subcategories %} 37 |

{{s.name}}

38 | {% assign indicators = site.indicators | where: "type", s.type %} 39 | {% assign aliases = site.data.aliases | where: "type", s.type %} 40 | {% assign all = indicators | concat: aliases | sort: "title" %} 41 | 46 | {% endfor %} 47 | {% endfor %} 48 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | 3 | # Markers for non-standard test categories 4 | markers = 5 | localization: tests for non-English locales environments 6 | performance: tests for performance benchmarks 7 | 8 | # Default: allows `pytest` to run all tests 9 | # except those marked as `localization` or `performance` 10 | addopts = -m "not localization and not performance" 11 | 12 | # Usage comments: 13 | ; Run only unit tests (default): 14 | ; pytest 15 | ; pytest -svr A # verbose, show summary, stop on first failure 16 | ; 17 | ; Run localization tests: 18 | ; pytest -m "localization" 19 | ; 20 | ; Run performance tests: 21 | ; pytest -m "performance" 22 | ; 23 | ; Run all tests: 24 | ; pytest -m "" 25 | ; 26 | ; Run unit and performance (no localization): 27 | ; pytest -m "not localization" 28 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | # These packages are needed for testing. 2 | pytest 3 | pytest-cov 4 | pytest-benchmark 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # _cslib 2 | pythonnet>=3.0.0 3 | typing_extensions>=4.4.0 4 | -------------------------------------------------------------------------------- /stock_indicators/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stock Indicators for Python 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Stock Indicators for Python is a library that produces financial 6 | market technical indicators. Send in historical price quotes and 7 | get back desired indicators such as moving averages, Relative Strength 8 | Index, Stochastic Oscillator, Parabolic SAR, etc. Nothing more. 9 | 10 | It can be used in any market analysis software using standard OHLCV 11 | price quotes for equities, commodities, forex, cryptocurrencies, 12 | and others. We had private trading algorithms, machine learning, 13 | and charting systems in mind when originally creating this community 14 | library. A Stock Indicators for .NET is also available. 15 | """ 16 | 17 | from stock_indicators import indicators 18 | from stock_indicators.indicators.common import * 19 | -------------------------------------------------------------------------------- /stock_indicators/_cslib/lib/Skender.Stock.Indicators.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/stock_indicators/_cslib/lib/Skender.Stock.Indicators.dll -------------------------------------------------------------------------------- /stock_indicators/_cstypes/__init__.py: -------------------------------------------------------------------------------- 1 | """Module for converting to C# object.""" 2 | 3 | from stock_indicators import _cslib 4 | 5 | from .datetime import (DateTime, to_pydatetime) 6 | from .decimal import (Decimal, to_pydecimal) 7 | from .list import (List) 8 | -------------------------------------------------------------------------------- /stock_indicators/_cstypes/datetime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime as PyDateTime 2 | 3 | from stock_indicators._cslib import CsDateTime 4 | from stock_indicators._cslib import CsCultureInfo 5 | 6 | 7 | class DateTime: 8 | """ 9 | Class for constructing C#'s `System.DateTime` object from Python's `datetime.datetime` instance. 10 | 11 | Parameters: 12 | datetime : `datetime.datetime`. 13 | 14 | Example: 15 | Constructing `System.DateTime` from `datetime.datetime` of Python. 16 | 17 | >>> now = datetime.now() 18 | >>> cs_now = DateTime(now) 19 | >>> cs_now 20 | 3/26/2021 10:02:22 PM 21 | """ 22 | def __new__(cls, datetime: PyDateTime) -> CsDateTime: 23 | return CsDateTime.Parse(datetime.isoformat()) 24 | 25 | 26 | def to_pydatetime(cs_datetime: CsDateTime) -> PyDateTime: 27 | """ 28 | Converts C#'s `System.DateTime` struct to a native Python datetime object. 29 | 30 | Parameter: 31 | cs_datetime : `System.DateTime` of C#. 32 | """ 33 | return PyDateTime.fromisoformat(cs_datetime.ToString("s", CsCultureInfo.InvariantCulture)) 34 | -------------------------------------------------------------------------------- /stock_indicators/_cstypes/decimal.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal as PyDecimal 2 | 3 | from stock_indicators._cslib import CsDecimal, CsCultureInfo, CsNumberStyles 4 | 5 | 6 | class Decimal: 7 | """ 8 | Class for converting a number into C#'s `System.Decimal` class. 9 | 10 | Parameters: 11 | decimal : `int`, `float` or any `object` that can be represented as a number string. 12 | 13 | Example: 14 | Constructing `System.Decimal` from `float` of Python. 15 | 16 | >>> cs_decimal = Decimal(2.5) 17 | >>> cs_decimal 18 | 2.5 19 | """ 20 | cs_number_styles = CsNumberStyles.AllowDecimalPoint | CsNumberStyles.AllowExponent \ 21 | | CsNumberStyles.AllowLeadingSign | CsNumberStyles.AllowThousands 22 | 23 | def __new__(cls, decimal) -> CsDecimal: 24 | return CsDecimal.Parse(str(decimal), cls.cs_number_styles, CsCultureInfo.InvariantCulture) 25 | 26 | 27 | def to_pydecimal(cs_decimal: CsDecimal) -> PyDecimal: 28 | """ 29 | Converts an object to a native Python decimal object. 30 | 31 | Parameter: 32 | cs_decimal : `System.Decimal` of C# or any `object` that can be represented as a number. 33 | """ 34 | if cs_decimal is not None: 35 | return PyDecimal(cs_decimal.ToString(CsCultureInfo.InvariantCulture)) 36 | -------------------------------------------------------------------------------- /stock_indicators/_cstypes/list.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | from stock_indicators._cslib import CsList 4 | 5 | 6 | class List: 7 | """ 8 | Class for converting Python's iterator type into C#'s `System.Collections.Generic.List` class. 9 | 10 | Parameters: 11 | generic : generic type for `System.Collections.Generic.List`. 12 | 13 | sequence : iterator types. (e.g. `list`, `tuple`, `range`) 14 | 15 | See Also: 16 | [Iterator Types](https://docs.python.org/3/library/stdtypes.html#iterator-types) 17 | 18 | Examples: 19 | Constructing `System.Collections.Generic.List` from `list` of Python. 20 | 21 | >>> py_list = [1, 2, 3] 22 | >>> cs_list = List(int, py_list) 23 | >>> cs_list 24 | System.Collections.Generic.List`1[System.Int32] 25 | 26 | Notice that It can be iterated like other iterable types in Python. 27 | 28 | >>> cs_list = List(int, [1, 2, 3]) 29 | >>> for i in cs_list: 30 | >>> print(i, end='') 31 | 123 32 | """ 33 | def __new__(cls, generic, sequence) -> CsList: 34 | cs_list = CsList[generic]() 35 | deque(map(cs_list.Add, sequence), maxlen=0) 36 | 37 | return cs_list 38 | -------------------------------------------------------------------------------- /stock_indicators/indicators/alma.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.quote import Quote 7 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 8 | 9 | 10 | def get_alma(quotes: Iterable[Quote], lookback_periods: int = 9, offset: float = .85, sigma : float = 6): 11 | """Get ALMA calculated. 12 | 13 | Arnaud Legoux Moving Average (ALMA) is a Gaussian distribution 14 | weighted moving average of Close price over a lookback window. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 9 21 | Number of periods in the lookback window. 22 | 23 | `offset` : float, defaults 0.85 24 | Adjusts smoothness versus responsiveness. 25 | 26 | `sigma` : float, defaults 6 27 | Defines the width of the Gaussian normal distribution. 28 | 29 | Returns: 30 | `ALMAResults[ALMAResult]` 31 | ALMAResults is list of ALMAResult with providing useful helper methods. 32 | 33 | See more: 34 | - [ALMA Reference](https://python.stockindicators.dev/indicators/Alma/#content) 35 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 36 | """ 37 | alma_results = CsIndicator.GetAlma[Quote](CsList(Quote, quotes), lookback_periods, offset, sigma) 38 | return ALMAResults(alma_results, ALMAResult) 39 | 40 | 41 | class ALMAResult(ResultBase): 42 | """ 43 | A wrapper class for a single unit of ALMA results. 44 | """ 45 | 46 | @property 47 | def alma(self) -> Optional[float]: 48 | return self._csdata.Alma 49 | 50 | @alma.setter 51 | def alma(self, value): 52 | self._csdata.Alma = value 53 | 54 | 55 | _T = TypeVar("_T", bound=ALMAResult) 56 | class ALMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 57 | """ 58 | A wrapper class for the list of ALMA(Arnaud Legoux Moving Average) results. 59 | It is exactly same with built-in `list` except for that it provides 60 | some useful helper methods written in CSharp implementation. 61 | """ 62 | -------------------------------------------------------------------------------- /stock_indicators/indicators/aroon.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_aroon(quotes: Iterable[Quote], lookback_periods: int = 25): 11 | """Get Aroon calculated. 12 | 13 | Aroon is a simple oscillator view of how long the new high or low price occured over a lookback window. 14 | 15 | Parameters: 16 | `quotes` : Iterable[Quote] 17 | Historical price quotes. 18 | 19 | `lookback_periods` : int, defaults 25 20 | Number of periods in the lookback window. 21 | 22 | Returns: 23 | `AroonResults[AroonResult]` 24 | AroonResults is list of AroonResult with providing useful helper methods. 25 | 26 | See more: 27 | - [Aroon Reference](https://python.stockindicators.dev/indicators/Aroon/#content) 28 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 29 | """ 30 | aroon_results = CsIndicator.GetAroon[Quote](CsList(Quote, quotes), lookback_periods) 31 | return AroonResults(aroon_results, AroonResult) 32 | 33 | 34 | class AroonResult(ResultBase): 35 | """ 36 | A wrapper class for a single unit of Aroon results. 37 | """ 38 | 39 | @property 40 | def aroon_up(self) -> Optional[float]: 41 | return self._csdata.AroonUp 42 | 43 | @aroon_up.setter 44 | def aroon_up(self, value): 45 | self._csdata.AroonUp = value 46 | 47 | @property 48 | def aroon_down(self) -> Optional[float]: 49 | return self._csdata.AroonDown 50 | 51 | @aroon_down.setter 52 | def aroon_down(self, value): 53 | self._csdata.AroonDown = value 54 | 55 | @property 56 | def oscillator(self) -> Optional[float]: 57 | return self._csdata.Oscillator 58 | 59 | @oscillator.setter 60 | def oscillator(self, value): 61 | self._csdata.Oscillator = value 62 | 63 | 64 | _T = TypeVar("_T", bound=AroonResult) 65 | class AroonResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 66 | """ 67 | A wrapper class for the list of Aroon results. 68 | It is exactly same with built-in `list` except for that it provides 69 | some useful helper methods written in C# implementation. 70 | """ 71 | -------------------------------------------------------------------------------- /stock_indicators/indicators/atr.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_atr(quotes: Iterable[Quote], lookback_periods: int = 14): 11 | """Get ATR calculated. 12 | 13 | Average True Range (ATR) is a measure of volatility that captures gaps and limits between periods. 14 | 15 | Parameters: 16 | `quotes` : Iterable[Quote] 17 | Historical price quotes. 18 | 19 | `lookback_periods` : int, defaults 14 20 | Number of periods in the lookback window. 21 | 22 | Returns: 23 | `ATRResults[ATRResult]` 24 | ATRResults is list of ATRResult with providing useful helper methods. 25 | 26 | See more: 27 | - [ATR Reference](https://python.stockindicators.dev/indicators/Atr/#content) 28 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 29 | """ 30 | atr_results = CsIndicator.GetAtr[Quote](CsList(Quote, quotes), lookback_periods) 31 | return ATRResults(atr_results, ATRResult) 32 | 33 | 34 | class ATRResult(ResultBase): 35 | """ 36 | A wrapper class for a single unit of ATR results. 37 | """ 38 | 39 | @property 40 | def tr(self) -> Optional[float]: 41 | return self._csdata.Tr 42 | 43 | @tr.setter 44 | def tr(self, value): 45 | self._csdata.Tr = value 46 | 47 | @property 48 | def atr(self) -> Optional[float]: 49 | return self._csdata.Atr 50 | 51 | @atr.setter 52 | def atr(self, value): 53 | self._csdata.Atr = value 54 | 55 | @property 56 | def atrp(self) -> Optional[float]: 57 | return self._csdata.Atrp 58 | 59 | @atrp.setter 60 | def atrp(self, value): 61 | self._csdata.Atrp = value 62 | 63 | 64 | _T = TypeVar("_T", bound=ATRResult) 65 | class ATRResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 66 | """ 67 | A wrapper class for the list of ATR(Average True Range) results. 68 | It is exactly same with built-in `list` except for that it provides 69 | some useful helper methods written in C# implementation. 70 | """ 71 | -------------------------------------------------------------------------------- /stock_indicators/indicators/basic_quotes.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.enums import CandlePart 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_basic_quote(quotes: Iterable[Quote], candle_part: CandlePart = CandlePart.CLOSE): 11 | """Get Basic Quote calculated. 12 | 13 | A simple quote transform (e.g. HL2, OHL3, etc.) and isolation of individual 14 | price quote candle parts from a full OHLCV quote. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `candle_part` : CandlePart, defaults CandlePart.CLOSE 21 | The OHLCV element or simply calculated value type. 22 | 23 | Returns: 24 | `BasicQuoteResults[BasicQuoteResult]` 25 | BasicQuoteResults is list of BasicQuoteResult with providing useful helper methods. 26 | 27 | See more: 28 | - [Basic Quote Reference](https://python.stockindicators.dev/indicators/BasicQuote/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetBaseQuote[Quote](CsList(Quote, quotes), candle_part.cs_value) 32 | return BasicQuoteResults(results, BasicQuoteResult) 33 | 34 | 35 | class BasicQuoteResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Basic Quote results. 38 | """ 39 | 40 | @property 41 | def value(self) -> Optional[float]: 42 | return self._csdata.Value 43 | 44 | @value.setter 45 | def jaw(self, value): 46 | self._csdata.Value = value 47 | 48 | 49 | _T = TypeVar("_T", bound=BasicQuoteResult) 50 | class BasicQuoteResults(IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Basic Quote results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in C# implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/bop.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_bop(quotes: Iterable[Quote], smooth_periods: int = 14): 11 | """Get BOP calculated. 12 | 13 | Balance of Power (aka Balance of Market Power) is a momentum oscillator 14 | that depicts the strength of buying and selling pressure. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `smooth_periods` : int, defaults 14 21 | Number of periods for smoothing. 22 | 23 | Returns: 24 | `BOPResults[BOPResult]` 25 | BOPResults is list of BOPResult with providing useful helper methods. 26 | 27 | See more: 28 | - [BOP Reference](https://python.stockindicators.dev/indicators/Bop/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetBop[Quote](CsList(Quote, quotes), smooth_periods) 32 | return BOPResults(results, BOPResult) 33 | 34 | 35 | class BOPResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of 38 | Balance of Power (aka Balance of Market Power) results. 39 | """ 40 | 41 | @property 42 | def bop(self) -> Optional[float]: 43 | return self._csdata.Bop 44 | 45 | @bop.setter 46 | def bop(self, value): 47 | self._csdata.Bop = value 48 | 49 | 50 | _T = TypeVar("_T", bound=BOPResult) 51 | class BOPResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 52 | """ 53 | A wrapper class for the list of Balance of Power (aka Balance of Market Power) results. 54 | It is exactly same with built-in `list` except for that it provides 55 | some useful helper methods written in C# implementation. 56 | """ 57 | -------------------------------------------------------------------------------- /stock_indicators/indicators/cci.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_cci(quotes: Iterable[Quote], lookback_periods: int = 20): 11 | """Get CCI calculated. 12 | 13 | Commodity Channel Index (CCI) is an oscillator depicting deviation 14 | from typical price range, often used to identify cyclical trends. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 20 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `CCIResults[CCIResult]` 25 | CCIResults is list of CCIResult with providing useful helper methods. 26 | 27 | See more: 28 | - [CCI Reference](https://python.stockindicators.dev/indicators/Cci/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetCci[Quote](CsList(Quote, quotes), lookback_periods) 32 | return CCIResults(results, CCIResult) 33 | 34 | 35 | class CCIResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Commodity Channel Index (CCI) results. 38 | """ 39 | 40 | @property 41 | def cci(self) -> Optional[float]: 42 | return self._csdata.Cci 43 | 44 | @cci.setter 45 | def cci(self, value): 46 | self._csdata.Cci = value 47 | 48 | 49 | _T = TypeVar("_T", bound=CCIResult) 50 | class CCIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Commodity Channel Index (CCI) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/chop.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_chop(quotes: Iterable[Quote], lookback_periods: int = 14): 11 | """Get Choppiness Index calculated. 12 | 13 | Choppiness Index (CHOP) measures the trendiness or choppiness 14 | over N lookback periods on a scale of 0 to 100. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 14 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `ChopResults[ChopResult]` 25 | ChopResults is list of ChopResult with providing useful helper methods. 26 | 27 | See more: 28 | - [Choppiness Index Reference](https://python.stockindicators.dev/indicators/Chop/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetChop[Quote](CsList(Quote, quotes), lookback_periods) 32 | return ChopResults(results, ChopResult) 33 | 34 | 35 | class ChopResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Choppiness Index (CHOP) results. 38 | """ 39 | 40 | @property 41 | def chop(self) -> Optional[float]: 42 | return self._csdata.Chop 43 | 44 | @chop.setter 45 | def chop(self, value): 46 | self._csdata.Chop = value 47 | 48 | 49 | _T = TypeVar("_T", bound=ChopResult) 50 | class ChopResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Choppiness Index (CHOP) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/cmo.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_cmo(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get CMO calculated. 12 | 13 | The Chande Momentum Oscillator (CMO) is a momentum indicator 14 | depicting the weighted percentof higher prices in financial markets. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `CMOResults[CMOResult]` 25 | CMOResults is list of CMOResult with providing useful helper methods. 26 | 27 | See more: 28 | - [CMO Reference](https://python.stockindicators.dev/indicators/Cmo/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetCmo[Quote](CsList(Quote, quotes), lookback_periods) 32 | return CMOResults(results, CMOResult) 33 | 34 | 35 | class CMOResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Chande Momentum Oscillator (CMO) results. 38 | """ 39 | 40 | @property 41 | def cmo(self) -> Optional[float]: 42 | return self._csdata.Cmo 43 | 44 | @cmo.setter 45 | def cmo(self, value): 46 | self._csdata.Cmo = value 47 | 48 | 49 | _T = TypeVar("_T", bound=CMOResult) 50 | class CMOResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Chande Momentum Oscillator (CMO) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/common/__init__.py: -------------------------------------------------------------------------------- 1 | from .quote import Quote 2 | from .results import ( 3 | ResultBase, 4 | IndicatorResults 5 | ) 6 | from .candles import ( 7 | CandleProperties, 8 | ) 9 | from .enums import ( 10 | BetaType, 11 | ChandelierType, 12 | CandlePart, 13 | EndType, 14 | MAType, 15 | PeriodSize, 16 | PivotPointType, 17 | PivotTrend, 18 | Match 19 | ) 20 | 21 | __all__ = [ 22 | "Quote", 23 | "ResultBase", 24 | "IndicatorResults", 25 | "CandleProperties", 26 | "BetaType", 27 | "ChandelierType", 28 | "CandlePart", 29 | "EndType", 30 | "MAType", 31 | "PeriodSize", 32 | "PivotPointType", 33 | "PivotTrend", 34 | "Match" 35 | ] 36 | -------------------------------------------------------------------------------- /stock_indicators/indicators/common/_contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/stock_indicators/indicators/common/_contrib/__init__.py -------------------------------------------------------------------------------- /stock_indicators/indicators/common/_contrib/enum.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class CsCompatibleIntEnum(IntEnum): 5 | def __init__(self, value) -> None: 6 | super().__init__() 7 | self.cs_value = value 8 | -------------------------------------------------------------------------------- /stock_indicators/indicators/common/_contrib/type_resolver.py: -------------------------------------------------------------------------------- 1 | from typing import Type, TypeVar, cast 2 | 3 | _T = TypeVar("_T") 4 | def generate_cs_inherited_class(child: Type[_T], cs_parent: Type, class_name="_Wrapper"): 5 | return cast(Type[_T], type(class_name, (cs_parent, ), {attr: getattr(child, attr) for attr in dir(child)})) 6 | -------------------------------------------------------------------------------- /stock_indicators/indicators/common/helpers.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from typing_extensions import Self 4 | 5 | from stock_indicators._cslib import CsIndicator, CsIEnumerable, CsResultUtility 6 | from stock_indicators._cstypes import List as CsList 7 | from stock_indicators.indicators.common.results import IndicatorResults 8 | 9 | 10 | class RemoveWarmupMixin: 11 | """IndicatorResults Mixin for remove_warmup_periods().""" 12 | @IndicatorResults._verify_data 13 | def remove_warmup_periods(self: IndicatorResults, remove_periods: Optional[int] = None) -> Self: 14 | """ 15 | Remove the recommended(or specified) quantity of results from the beginning of the results list. 16 | """ 17 | if remove_periods is not None: 18 | return super().remove_warmup_periods(remove_periods) 19 | 20 | removed_results = CsIndicator.RemoveWarmupPeriods(CsList(self._get_csdata_type(), self._csdata)) 21 | return self.__class__(removed_results, self._wrapper_class) 22 | 23 | 24 | class CondenseMixin: 25 | """IndicatorResults Mixin for condense().""" 26 | @IndicatorResults._verify_data 27 | def condense(self: IndicatorResults) -> Self: 28 | """ 29 | Removes non-essential records containing null values with unique consideration for this indicator. 30 | """ 31 | cs_results_type = self._get_csdata_type() 32 | try: # to check whether there's matched overloaded method. 33 | condense_method = CsIndicator.Condense.Overloads[CsIEnumerable[cs_results_type]] 34 | except TypeError: 35 | condense_method = CsResultUtility.Condense[cs_results_type] 36 | 37 | condensed_results = condense_method(CsList(cs_results_type, self._csdata)) 38 | return self.__class__(condensed_results, self._wrapper_class) 39 | -------------------------------------------------------------------------------- /stock_indicators/indicators/dema.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_dema(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get DEMA calculated. 12 | 13 | Double Exponential Moving Average (DEMA) of the Close price. 14 | 15 | Parameters: 16 | `quotes` : Iterable[Quote] 17 | Historical price quotes. 18 | 19 | `lookback_periods` : int 20 | Number of periods in the lookback window. 21 | 22 | Returns: 23 | `DEMAResults[DEMAResult]` 24 | DEMAResults is list of DEMAResult with providing useful helper methods. 25 | 26 | See more: 27 | - [DEMA Reference](https://python.stockindicators.dev/indicators/DoubleEma/#content) 28 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 29 | """ 30 | results = CsIndicator.GetDema[Quote](CsList(Quote, quotes), lookback_periods) 31 | return DEMAResults(results, DEMAResult) 32 | 33 | 34 | class DEMAResult(ResultBase): 35 | """ 36 | A wrapper class for a single unit of Double Exponential Moving Average (DEMA) results. 37 | """ 38 | 39 | @property 40 | def dema(self) -> Optional[float]: 41 | return self._csdata.Dema 42 | 43 | @dema.setter 44 | def dema(self, value): 45 | self._csdata.Dema = value 46 | 47 | 48 | _T = TypeVar("_T", bound=DEMAResult) 49 | class DEMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 50 | """ 51 | A wrapper class for the list of Double Exponential Moving Average (DEMA) results. 52 | It is exactly same with built-in `list` except for that it provides 53 | some useful helper methods written in C# implementation. 54 | """ 55 | -------------------------------------------------------------------------------- /stock_indicators/indicators/doji.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.candles import CandleResult, CandleResults 6 | from stock_indicators.indicators.common.quote import Quote 7 | 8 | 9 | def get_doji(quotes: Iterable[Quote], max_price_change_percent: float = 0.1): 10 | """Get Doji calculated. 11 | 12 | (preview) 13 | Doji is a single candlestick pattern where open and 14 | close price are virtually identical, representing market indecision. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `max_price_change_percent` : float, defaults 0.1 21 | Maximum absolute decimalized percent difference in open and close price. 22 | 23 | Returns: 24 | `CandleResults[CandleResult]` 25 | CandleResults is list of CandleResult with providing useful helper methods. 26 | 27 | See more: 28 | - [Doji Reference](https://python.stockindicators.dev/indicators/Doji/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetDoji[Quote](CsList(Quote, quotes), max_price_change_percent) 32 | return CandleResults(results, CandleResult) 33 | -------------------------------------------------------------------------------- /stock_indicators/indicators/dpo.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_dpo(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get DPO calculated. 12 | 13 | Detrended Price Oscillator (DPO) depicts the difference 14 | between price and an offset simple moving average. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `DPOResults[DPOResult]` 25 | DPOResults is list of DPOResult with providing useful helper methods. 26 | 27 | See more: 28 | - [DPO Reference](https://python.stockindicators.dev/indicators/Dpo/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetDpo[Quote](CsList(Quote, quotes), lookback_periods) 32 | return DPOResults(results, DPOResult) 33 | 34 | 35 | class DPOResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Detrended Price Oscillator (DPO) results. 38 | """ 39 | 40 | @property 41 | def sma(self) -> Optional[float]: 42 | return self._csdata.Sma 43 | 44 | @sma.setter 45 | def sma(self, value): 46 | self._csdata.Sma = value 47 | 48 | @property 49 | def dpo(self) -> Optional[float]: 50 | return self._csdata.Dpo 51 | 52 | @dpo.setter 53 | def dpo(self, value): 54 | self._csdata.Dpo = value 55 | 56 | 57 | _T = TypeVar("_T", bound=DPOResult) 58 | class DPOResults(CondenseMixin, IndicatorResults[_T]): 59 | """ 60 | A wrapper class for the list of Detrended Price Oscillator (DPO) results. 61 | It is exactly same with built-in `list` except for that it provides 62 | some useful helper methods written in CSharp implementation. 63 | """ 64 | -------------------------------------------------------------------------------- /stock_indicators/indicators/dynamic.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_dynamic(quotes: Iterable[Quote], lookback_periods: int, k_factor: float = 0.6): 11 | """Get McGinley Dynamic calculated. 12 | 13 | McGinley Dynamic is a more responsive variant of exponential moving average. 14 | 15 | Parameters: 16 | `quotes` : Iterable[Quote] 17 | Historical price quotes. 18 | 19 | `lookback_periods` : int 20 | Number of periods in the lookback window. 21 | 22 | `k_factor` : float, defaults 0.6 23 | Range adjustment factor. 24 | 25 | Returns: 26 | `DynamicResults[DynamicResult]` 27 | DynamicResults is list of DynamicResult with providing useful helper methods. 28 | 29 | See more: 30 | - [McGinley Dynamic Reference](https://python.stockindicators.dev/indicators/Dynamic/#content) 31 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 32 | """ 33 | results = CsIndicator.GetDynamic[Quote](CsList(Quote, quotes), lookback_periods, k_factor) 34 | return DynamicResults(results, DynamicResult) 35 | 36 | 37 | class DynamicResult(ResultBase): 38 | """ 39 | A wrapper class for a single unit of McGinley Dynamic results. 40 | """ 41 | 42 | @property 43 | def dynamic(self) -> Optional[float]: 44 | return self._csdata.Dynamic 45 | 46 | @dynamic.setter 47 | def dynamic(self, value): 48 | self._csdata.Dynamic = value 49 | 50 | 51 | _T = TypeVar("_T", bound=DynamicResult) 52 | class DynamicResults(CondenseMixin, IndicatorResults[_T]): 53 | """ 54 | A wrapper class for the list of McGinley Dynamic results. 55 | It is exactly same with built-in `list` except for that it provides 56 | some useful helper methods written in CSharp implementation. 57 | """ 58 | -------------------------------------------------------------------------------- /stock_indicators/indicators/ema.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators.indicators.common.enums import CandlePart 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_ema(quotes: Iterable[Quote], lookback_periods: int, 11 | candle_part: CandlePart = CandlePart.CLOSE): 12 | """Get EMA calculated. 13 | 14 | Exponential Moving Average (EMA) of the Close price. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | `candle_part` : CandlePart, defaults CandlePart.CLOSE 24 | Selected OHLCV part. 25 | 26 | Returns: 27 | `EMAResults[EMAResult]` 28 | EMAResults is list of EMAResult with providing useful helper methods. 29 | 30 | See more: 31 | - [EMA Reference](https://python.stockindicators.dev/indicators/Ema/#content) 32 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 33 | """ 34 | quotes = Quote.use(quotes, candle_part) # Error occurs if not assigned to local var. 35 | ema_list = CsIndicator.GetEma(quotes, lookback_periods) 36 | return EMAResults(ema_list, EMAResult) 37 | 38 | 39 | class EMAResult(ResultBase): 40 | """ 41 | A wrapper class for a single unit of EMA results. 42 | """ 43 | 44 | @property 45 | def ema(self) -> Optional[float]: 46 | return self._csdata.Ema 47 | 48 | @ema.setter 49 | def ema(self, value): 50 | self._csdata.Ema = value 51 | 52 | 53 | _T = TypeVar("_T", bound=EMAResult) 54 | class EMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 55 | """ 56 | A wrapper class for the list of EMA(Exponential Moving Average) results. 57 | It is exactly same with built-in `list` except for that it provides 58 | some useful helper methods written in CSharp implementation. 59 | """ 60 | -------------------------------------------------------------------------------- /stock_indicators/indicators/epma.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_epma(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get EPMA calculated. 12 | 13 | Endpoint Moving Average (EPMA), also known as Least Squares 14 | Moving Average (LSMA), plots the projected last point of a linear 15 | regression lookback window. 16 | 17 | Parameters: 18 | `quotes` : Iterable[Quote] 19 | Historical price quotes. 20 | 21 | `lookback_periods` : int 22 | Number of periods in the lookback window. 23 | 24 | Returns: 25 | `EPMAResults[EPMAResult]` 26 | EPMAResults is list of EPMAResult with providing useful helper methods. 27 | 28 | See more: 29 | - [EPMA Reference](https://python.stockindicators.dev/indicators/Epma/#content) 30 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 31 | """ 32 | results = CsIndicator.GetEpma[Quote](CsList(Quote, quotes), lookback_periods) 33 | return EPMAResults(results, EPMAResult) 34 | 35 | 36 | class EPMAResult(ResultBase): 37 | """ 38 | A wrapper class for a single unit of Endpoint Moving Average (EPMA) results. 39 | """ 40 | 41 | @property 42 | def epma(self) -> Optional[float]: 43 | return self._csdata.Epma 44 | 45 | @epma.setter 46 | def epma(self, value): 47 | self._csdata.Epma = value 48 | 49 | 50 | _T = TypeVar("_T", bound=EPMAResult) 51 | class EPMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 52 | """ 53 | A wrapper class for the list of Endpoint Moving Average (EPMA) results. 54 | It is exactly same with built-in `list` except for that it provides 55 | some useful helper methods written in CSharp implementation. 56 | """ 57 | -------------------------------------------------------------------------------- /stock_indicators/indicators/fisher_transform.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_fisher_transform(quotes: Iterable[Quote], lookback_periods: int = 10): 11 | """Get Ehlers Fisher Transform calculated. 12 | 13 | Ehlers Fisher Transform converts prices 14 | into a Gaussian normal distribution. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 10 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `FisherTransformResults[FisherTransformResult]` 25 | FisherTransformResults is list of 26 | FisherTransformResult with providing useful helper methods. 27 | 28 | See more: 29 | - [Fisher Transform Reference](https://python.stockindicators.dev/indicators/FisherTransform/#content) 30 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 31 | """ 32 | results = CsIndicator.GetFisherTransform[Quote](CsList(Quote, quotes), lookback_periods) 33 | return FisherTransformResults(results, FisherTransformResult) 34 | 35 | 36 | class FisherTransformResult(ResultBase): 37 | """ 38 | A wrapper class for a single unit of Ehlers Fisher Transform results. 39 | """ 40 | 41 | @property 42 | def fisher(self) -> Optional[float]: 43 | return self._csdata.Fisher 44 | 45 | @fisher.setter 46 | def fisher(self, value): 47 | self._csdata.Fisher = value 48 | 49 | @property 50 | def trigger(self) -> Optional[float]: 51 | return self._csdata.Trigger 52 | 53 | @trigger.setter 54 | def trigger(self, value): 55 | self._csdata.Trigger = value 56 | 57 | 58 | _T = TypeVar("_T", bound=FisherTransformResult) 59 | class FisherTransformResults(CondenseMixin, IndicatorResults[_T]): 60 | """ 61 | A wrapper class for the list of Ehlers Fisher Transform results. 62 | It is exactly same with built-in `list` except for that it provides 63 | some useful helper methods written in CSharp implementation. 64 | """ 65 | -------------------------------------------------------------------------------- /stock_indicators/indicators/force_index.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_force_index(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get Force Index calculated. 12 | 13 | The Force Index depicts volume-based buying and selling pressure. 14 | 15 | Parameters: 16 | `quotes` : Iterable[Quote] 17 | Historical price quotes. 18 | 19 | `lookback_periods` : int 20 | Number of periods for the EMA of Force Index. 21 | 22 | Returns: 23 | `ForceIndexResults[ForceIndexResult]` 24 | ForceIndexResults is list of ForceIndexResult with providing useful helper methods. 25 | 26 | See more: 27 | - [Force Index Reference](https://python.stockindicators.dev/indicators/ForceIndex/#content) 28 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 29 | """ 30 | results = CsIndicator.GetForceIndex[Quote](CsList(Quote, quotes), lookback_periods) 31 | return ForceIndexResults(results, ForceIndexResult) 32 | 33 | 34 | class ForceIndexResult(ResultBase): 35 | """ 36 | A wrapper class for a single unit of Force Index results. 37 | """ 38 | 39 | @property 40 | def force_index(self) -> Optional[float]: 41 | return self._csdata.ForceIndex 42 | 43 | @force_index.setter 44 | def force_index(self, value): 45 | self._csdata.ForceIndex = value 46 | 47 | 48 | _T = TypeVar("_T", bound=ForceIndexResult) 49 | class ForceIndexResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 50 | """ 51 | A wrapper class for the list of Force Index results. 52 | It is exactly same with built-in `list` except for that it provides 53 | some useful helper methods written in CSharp implementation. 54 | """ 55 | -------------------------------------------------------------------------------- /stock_indicators/indicators/hma.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_hma(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get HMA calculated. 12 | 13 | Hull Moving Average (HMA) is a modified weighted average 14 | of Close price over N lookback periods that reduces lag. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `HMAResults[HMAResult]` 25 | HMAResults is list of HMAResult with providing useful helper methods. 26 | 27 | See more: 28 | - [HMA Reference](https://python.stockindicators.dev/indicators/Hma/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetHma[Quote](CsList(Quote, quotes), lookback_periods) 32 | return HMAResults(results, HMAResult) 33 | 34 | 35 | class HMAResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Hull Moving Average (HMA) results. 38 | """ 39 | 40 | @property 41 | def hma(self) -> Optional[float]: 42 | return self._csdata.Hma 43 | 44 | @hma.setter 45 | def hma(self, value): 46 | self._csdata.Hma = value 47 | 48 | 49 | _T = TypeVar("_T", bound=HMAResult) 50 | class HMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Hull Moving Average (HMA) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/hurst.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_hurst(quotes: Iterable[Quote], lookback_periods: int = 100): 11 | """Get Hurst Exponent calculated. 12 | 13 | Hurst Exponent is a measure of randomness, trending, and 14 | mean-reverting tendencies of incremental return values. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 100 21 | Number of lookback periods. 22 | 23 | Returns: 24 | `HurstResults[HurstResult]` 25 | HurstResults is list of HurstResult with providing useful helper methods. 26 | 27 | See more: 28 | - [Hurst Exponent Reference](https://python.stockindicators.dev/indicators/Hurst/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetHurst[Quote](CsList(Quote, quotes), lookback_periods) 32 | return HurstResults(results, HurstResult) 33 | 34 | 35 | class HurstResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Hurst Exponent results. 38 | """ 39 | 40 | @property 41 | def hurst_exponent(self) -> Optional[float]: 42 | return self._csdata.HurstExponent 43 | 44 | @hurst_exponent.setter 45 | def hurst_exponent(self, value): 46 | self._csdata.HurstExponent = value 47 | 48 | 49 | _T = TypeVar("_T", bound=HurstResult) 50 | class HurstResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Hurst Exponent results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/mama.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_mama(quotes: Iterable[Quote], fast_limit: float = 0.5, 11 | slow_limit: float = 0.05): 12 | """Get MAMA calculated. 13 | 14 | MESA Adaptive Moving Average (MAMA) is a 5-period 15 | adaptive moving average of high/low price. 16 | 17 | Parameters: 18 | `quotes` : Iterable[Quote] 19 | Historical price quotes. 20 | 21 | `fast_limit` : float, defaults 0.5 22 | Fast limit threshold. 23 | 24 | `slow_limit` : float, defaults 0.05 25 | Slow limit threshold. 26 | 27 | Returns: 28 | `MAMAResults[MAMAResult]` 29 | MAMAResults is list of MAMAResult with providing useful helper methods. 30 | 31 | See more: 32 | - [MAMA Reference](https://python.stockindicators.dev/indicators/Mama/#content) 33 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 34 | """ 35 | results = CsIndicator.GetMama[Quote](CsList(Quote, quotes), fast_limit, 36 | slow_limit) 37 | return MAMAResults(results, MAMAResult) 38 | 39 | 40 | class MAMAResult(ResultBase): 41 | """ 42 | A wrapper class for a single unit of MESA Adaptive Moving Average (MAMA) results. 43 | """ 44 | 45 | @property 46 | def mama(self) -> Optional[float]: 47 | return self._csdata.Mama 48 | 49 | @mama.setter 50 | def mama(self, value): 51 | self._csdata.Mama = value 52 | 53 | @property 54 | def fama(self) -> Optional[float]: 55 | return self._csdata.Fama 56 | 57 | @fama.setter 58 | def fama(self, value): 59 | self._csdata.Fama = value 60 | 61 | 62 | _T = TypeVar("_T", bound=MAMAResult) 63 | class MAMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 64 | """ 65 | A wrapper class for the list of MESA Adaptive Moving Average (MAMA) results. 66 | It is exactly same with built-in `list` except for that it provides 67 | some useful helper methods written in CSharp implementation. 68 | """ 69 | -------------------------------------------------------------------------------- /stock_indicators/indicators/marubozu.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.candles import CandleResult, CandleResults 6 | from stock_indicators.indicators.common.quote import Quote 7 | 8 | 9 | def get_marubozu(quotes: Iterable[Quote], min_body_percent: float = 95): 10 | """Get Marubozu calculated. 11 | 12 | (preview) 13 | Marubozu is a single candlestick pattern that has no wicks, 14 | representing consistent directional movement. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `min_body_percent` : float, defaults 95 21 | Minimum candle body size as decimalized percentage. 22 | 23 | Returns: 24 | `CandleResults[CandleResult]` 25 | CandleResults is list of CandleResult with providing useful helper methods. 26 | 27 | See more: 28 | - [Marubozu Reference](https://python.stockindicators.dev/indicators/Marubozu/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetMarubozu[Quote](CsList(Quote, quotes), min_body_percent) 32 | return CandleResults(results, CandleResult) 33 | -------------------------------------------------------------------------------- /stock_indicators/indicators/mfi.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_mfi(quotes: Iterable[Quote], lookback_periods: int = 14): 11 | """Get MFI calculated. 12 | 13 | Money Flow Index (MFI) is a price-volume oscillator 14 | that shows buying and selling momentum. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 14 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `MFIResults[MFIResult]` 25 | MFIResults is list of MFIResult with providing useful helper methods. 26 | 27 | See more: 28 | - [MFI Reference](https://python.stockindicators.dev/indicators/Mfi/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetMfi[Quote](CsList(Quote, quotes), lookback_periods) 32 | return MFIResults(results, MFIResult) 33 | 34 | 35 | class MFIResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Money Flow Index (MFI) results. 38 | """ 39 | 40 | @property 41 | def mfi(self) -> Optional[float]: 42 | return self._csdata.Mfi 43 | 44 | @mfi.setter 45 | def mfi(self, value): 46 | self._csdata.Mfi = value 47 | 48 | 49 | _T = TypeVar("_T", bound=MFIResult) 50 | class MFIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Money Flow Index (MFI) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/obv.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_obv(quotes: Iterable[Quote], sma_periods: Optional[int] = None): 11 | """Get OBV calculated. 12 | 13 | On-balance Volume (OBV) is a rolling accumulation of 14 | volume based on Close price direction. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `sma_periods` : int, optional 21 | Number of periods for an SMA of the OBV line. 22 | 23 | Returns: 24 | `OBVResults[OBVResult]` 25 | OBVResults is list of OBVResult with providing useful helper methods. 26 | 27 | See more: 28 | - [OBV Reference](https://python.stockindicators.dev/indicators/Obv/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetObv[Quote](CsList(Quote, quotes), sma_periods) 32 | return OBVResults(results, OBVResult) 33 | 34 | 35 | class OBVResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of On-balance Volume (OBV) results. 38 | """ 39 | 40 | @property 41 | def obv(self) -> float: 42 | return self._csdata.Obv 43 | 44 | @obv.setter 45 | def obv(self, value): 46 | self._csdata.Obv = value 47 | 48 | @property 49 | def obv_sma(self) -> Optional[float]: 50 | return self._csdata.ObvSma 51 | 52 | @obv_sma.setter 53 | def obv_sma(self, value): 54 | self._csdata.ObvSma = value 55 | 56 | 57 | _T = TypeVar("_T", bound=OBVResult) 58 | class OBVResults(CondenseMixin, IndicatorResults[_T]): 59 | """ 60 | A wrapper class for the list of On-balance Volume (OBV) results. 61 | It is exactly same with built-in `list` except for that it provides 62 | some useful helper methods written in CSharp implementation. 63 | """ 64 | -------------------------------------------------------------------------------- /stock_indicators/indicators/rsi.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_rsi(quotes: Iterable[Quote], lookback_periods: int = 14): 11 | """Get RSI calculated. 12 | 13 | Relative Strength Index (RSI) measures strength of the winning/losing streak over N lookback periods 14 | on a scale of 0 to 100, to depict overbought and oversold conditions. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 14 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `RSIResults[RSIResult]` 25 | RSIResults is list of RSIResult with providing useful helper methods. 26 | 27 | See more: 28 | - [RSI Reference](https://python.stockindicators.dev/indicators/Rsi/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | rsi_list = CsIndicator.GetRsi[Quote](CsList(Quote, quotes), lookback_periods) 32 | return RSIResults(rsi_list, RSIResult) 33 | 34 | 35 | class RSIResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of RSI results. 38 | """ 39 | 40 | @property 41 | def rsi(self) -> Optional[float]: 42 | return self._csdata.Rsi 43 | 44 | @rsi.setter 45 | def rsi(self, value): 46 | self._csdata.Rsi = value 47 | 48 | 49 | _T = TypeVar("_T", bound=RSIResult) 50 | class RSIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of RSI(Relative Strength Index) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/smma.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_smma(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get SMMA calculated. 12 | 13 | Smoothed Moving Average (SMMA) is the average of Close price 14 | over a lookback window using a smoothing method. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `SMMAResults[SMMAResult]` 25 | SMMAResults is list of SMMAResult with providing useful helper methods. 26 | 27 | See more: 28 | - [SMMA Reference](https://python.stockindicators.dev/indicators/Smma/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetSmma[Quote](CsList(Quote, quotes), lookback_periods) 32 | return SMMAResults(results, SMMAResult) 33 | 34 | 35 | class SMMAResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Smoothed Moving Average (SMMA) results. 38 | """ 39 | 40 | @property 41 | def smma(self) -> Optional[float]: 42 | return self._csdata.Smma 43 | 44 | @smma.setter 45 | def smma(self, value): 46 | self._csdata.Smma = value 47 | 48 | 49 | _T = TypeVar("_T", bound=SMMAResult) 50 | class SMMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Smoothed Moving Average (SMMA) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/stc.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_stc(quotes: Iterable[Quote], cycle_periods: int = 10, 11 | fast_periods: int = 23, slow_periods: int = 50): 12 | """Get STC calculated. 13 | 14 | Schaff Trend Cycle (STC) is a stochastic oscillator view 15 | of two converging/diverging exponential moving averages. 16 | 17 | Parameters: 18 | `quotes` : Iterable[Quote] 19 | Historical price quotes. 20 | 21 | `cycle_periods` : int, defaults 10 22 | Number of periods for the Trend Cycle. 23 | 24 | `fast_periods` : int, defaults 23 25 | Number of periods in the Fast EMA. 26 | 27 | `slow_periods` : int, defaults 50 28 | Number of periods in the Slow EMA. 29 | 30 | Returns: 31 | `STCResults[STCResult]` 32 | STCResults is list of STCResult with providing useful helper methods. 33 | 34 | See more: 35 | - [STC Reference](https://python.stockindicators.dev/indicators/Stc/#content) 36 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 37 | """ 38 | results = CsIndicator.GetStc[Quote](CsList(Quote, quotes), cycle_periods, 39 | fast_periods, slow_periods) 40 | return STCResults(results, STCResult) 41 | 42 | 43 | class STCResult(ResultBase): 44 | """ 45 | A wrapper class for a single unit of Schaff Trend Cycle (STC) results. 46 | """ 47 | 48 | @property 49 | def stc(self) -> Optional[float]: 50 | return self._csdata.Stc 51 | 52 | @stc.setter 53 | def stc(self, value): 54 | self._csdata.Stc = value 55 | 56 | 57 | _T = TypeVar("_T", bound=STCResult) 58 | class STCResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 59 | """ 60 | A wrapper class for the list of Schaff Trend Cycle (STC) results. 61 | It is exactly same with built-in `list` except for that it provides 62 | some useful helper methods written in CSharp implementation. 63 | """ 64 | -------------------------------------------------------------------------------- /stock_indicators/indicators/t3.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_t3(quotes: Iterable[Quote], lookback_periods: int = 5, 11 | volume_factor: float = 0.7): 12 | """Get T3 calculated. 13 | 14 | Tillson T3 is a smooth moving average that reduces 15 | both lag and overshooting. 16 | 17 | Parameters: 18 | `quotes` : Iterable[Quote] 19 | Historical price quotes. 20 | 21 | `lookback_periods` : int, defaults 5 22 | Number of periods for the EMA smoothing. 23 | 24 | `volume_factor` : float, defaults 0.7 25 | Size of the Volume Factor. 26 | 27 | Returns: 28 | `T3Results[T3Result]` 29 | T3Results is list of T3Result with providing useful helper methods. 30 | 31 | See more: 32 | - [T3 Reference](https://python.stockindicators.dev/indicators/T3/#content) 33 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 34 | """ 35 | results = CsIndicator.GetT3[Quote](CsList(Quote, quotes), lookback_periods, 36 | volume_factor) 37 | return T3Results(results, T3Result) 38 | 39 | 40 | class T3Result(ResultBase): 41 | """ 42 | A wrapper class for a single unit of Tillson T3 results. 43 | """ 44 | 45 | @property 46 | def t3(self) -> Optional[float]: 47 | return self._csdata.T3 48 | 49 | @t3.setter 50 | def t3(self, value): 51 | self._csdata.T3 = value 52 | 53 | 54 | _T = TypeVar("_T", bound=T3Result) 55 | class T3Results(CondenseMixin, IndicatorResults[_T]): 56 | """ 57 | A wrapper class for the list of Tillson T3 results. 58 | It is exactly same with built-in `list` except for that it provides 59 | some useful helper methods written in CSharp implementation. 60 | """ 61 | -------------------------------------------------------------------------------- /stock_indicators/indicators/tema.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_tema(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get TEMA calculated. 12 | 13 | Triple Exponential Moving Average (TEMA) of the Close price. 14 | Note: TEMA is often confused with the alternative TRIX oscillator. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `TEMAResults[TEMAResult]` 25 | TEMAResults is list of TEMAResult with providing useful helper methods. 26 | 27 | See more: 28 | - [TEMA Reference](https://python.stockindicators.dev/indicators/TripleEma/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetTema[Quote](CsList(Quote, quotes), lookback_periods) 32 | return TEMAResults(results, TEMAResult) 33 | 34 | 35 | class TEMAResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Triple Exponential Moving Average (TEMA) results. 38 | """ 39 | 40 | @property 41 | def tema(self) -> Optional[float]: 42 | return self._csdata.Tema 43 | 44 | @tema.setter 45 | def tema(self, value): 46 | self._csdata.Tema = value 47 | 48 | 49 | _T = TypeVar("_T", bound=TEMAResult) 50 | class TEMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Triple Exponential Moving Average (TEMA) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in C# implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/tr.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_tr(quotes: Iterable[Quote]): 11 | """Get TR calculated. 12 | 13 | True Range (TR) is a measure of volatility that captures gaps and limits between periods. 14 | 15 | Parameters: 16 | `quotes` : Iterable[Quote] 17 | Historical price quotes. 18 | 19 | Returns: 20 | `TrResults[TrResult]` 21 | TrResults is list of TrResult with providing useful helper methods. 22 | 23 | See more: 24 | - [True Range Reference](https://python.stockindicators.dev/indicators/Atr/#content) 25 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 26 | """ 27 | results = CsIndicator.GetTr[Quote](CsList(Quote, quotes)) 28 | return TrResults(results, TrResult) 29 | 30 | 31 | class TrResult(ResultBase): 32 | """ 33 | A wrapper class for a single unit of True Range (TR) results. 34 | """ 35 | 36 | @property 37 | def tr(self) -> Optional[float]: 38 | return self._csdata.Tr 39 | 40 | @tr.setter 41 | def tr(self, value): 42 | self._csdata.Tr = value 43 | 44 | 45 | _T = TypeVar("_T", bound=TrResult) 46 | class TrResults(CondenseMixin, IndicatorResults[_T]): 47 | """ 48 | A wrapper class for the list of True Range (TR) results. 49 | It is exactly same with built-in `list` except for that it provides 50 | some useful helper methods written in C# implementation. 51 | """ 52 | -------------------------------------------------------------------------------- /stock_indicators/indicators/ulcer_index.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_ulcer_index(quotes: Iterable[Quote], lookback_periods: int = 14): 11 | """Get Ulcer Index calculated. 12 | 13 | Ulcer Index (UI) is a measure of downside Close price volatility 14 | over a lookback window. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int, defaults 14 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `UlcerIndexResults[UlcerIndexResult]` 25 | UlcerIndexResults is list of UlcerIndexResult with providing useful helper methods. 26 | 27 | See more: 28 | - [Ulcer Index Reference](https://python.stockindicators.dev/indicators/UlcerIndex/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetUlcerIndex[Quote](CsList(Quote, quotes), lookback_periods) 32 | return UlcerIndexResults(results, UlcerIndexResult) 33 | 34 | 35 | class UlcerIndexResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Ulcer Index (UI) results. 38 | """ 39 | 40 | @property 41 | def ui(self) -> Optional[float]: 42 | return self._csdata.UI 43 | 44 | @ui.setter 45 | def ui(self, value): 46 | self._csdata.UI = value 47 | 48 | 49 | _T = TypeVar("_T", bound=UlcerIndexResult) 50 | class UlcerIndexResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Ulcer Index (UI) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/vortex.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_vortex(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get VI calculated. 12 | 13 | Vortex Indicator (VI) is a measure of price directional movement. 14 | It includes positive and negative indicators, and is often used 15 | to identify trends and reversals. 16 | 17 | Parameters: 18 | `quotes` : Iterable[Quote] 19 | Historical price quotes. 20 | 21 | `lookback_periods` : int 22 | Number of periods in the lookback window. 23 | 24 | Returns: 25 | `VortexResults[VortexResult]` 26 | VortexResults is list of VortexResult with providing useful helper methods. 27 | 28 | See more: 29 | - [Vortex Reference](https://python.stockindicators.dev/indicators/Vortex/#content) 30 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 31 | """ 32 | results = CsIndicator.GetVortex[Quote](CsList(Quote, quotes), lookback_periods) 33 | return VortexResults(results, VortexResult) 34 | 35 | 36 | class VortexResult(ResultBase): 37 | """ 38 | A wrapper class for a single unit of Vortex Indicator (VI) results. 39 | """ 40 | 41 | @property 42 | def pvi(self) -> Optional[float]: 43 | return self._csdata.Pvi 44 | 45 | @pvi.setter 46 | def pvi(self, value): 47 | self._csdata.Pvi = value 48 | 49 | @property 50 | def nvi(self) -> Optional[float]: 51 | return self._csdata.Nvi 52 | 53 | @nvi.setter 54 | def nvi(self, value): 55 | self._csdata.Nvi = value 56 | 57 | 58 | _T = TypeVar("_T", bound=VortexResult) 59 | class VortexResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 60 | """ 61 | A wrapper class for the list of Vortex Indicator (VI) results. 62 | It is exactly same with built-in `list` except for that it provides 63 | some useful helper methods written in CSharp implementation. 64 | """ 65 | -------------------------------------------------------------------------------- /stock_indicators/indicators/vwma.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_vwma(quotes: Iterable[Quote], lookback_periods: int): 11 | """Get VWMA calculated. 12 | 13 | Volume Weighted Moving Average (VWMA) is the volume adjusted average price 14 | over a lookback window. 15 | 16 | Parameters: 17 | `quotes` : Iterable[Quote] 18 | Historical price quotes. 19 | 20 | `lookback_periods` : int 21 | Number of periods in the lookback window. 22 | 23 | Returns: 24 | `VWMAResults[VWMAResult]` 25 | VWMAResults is list of VWMAResult with providing useful helper methods. 26 | 27 | See more: 28 | - [VWMA Reference](https://python.stockindicators.dev/indicators/Vwma/#content) 29 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 30 | """ 31 | results = CsIndicator.GetVwma[Quote](CsList(Quote, quotes), lookback_periods) 32 | return VWMAResults(results, VWMAResult) 33 | 34 | 35 | class VWMAResult(ResultBase): 36 | """ 37 | A wrapper class for a single unit of Volume Weighted Moving Average (VWMA) results. 38 | """ 39 | 40 | @property 41 | def vwma(self) -> Optional[float]: 42 | return self._csdata.Vwma 43 | 44 | @vwma.setter 45 | def vwma(self, value): 46 | self._csdata.Vwma = value 47 | 48 | 49 | _T = TypeVar("_T", bound=VWMAResult) 50 | class VWMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 51 | """ 52 | A wrapper class for the list of Volume Weighted Moving Average (VWMA) results. 53 | It is exactly same with built-in `list` except for that it provides 54 | some useful helper methods written in CSharp implementation. 55 | """ 56 | -------------------------------------------------------------------------------- /stock_indicators/indicators/williams_r.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators._cstypes import List as CsList 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_williams_r(quotes: Iterable[Quote], lookback_periods: int = 14): 11 | """Get Williams %R calculated. 12 | 13 | Williams %R momentum indicator is a stochastic oscillator 14 | with scale of -100 to 0. It is exactly the same as the Fast variant 15 | of Stochastic Oscillator, but with a different scaling. 16 | 17 | Parameters: 18 | `quotes` : Iterable[Quote] 19 | Historical price quotes. 20 | 21 | `lookback_periods` : int, defaults 14 22 | Number of periods in the lookback window. 23 | 24 | Returns: 25 | `WilliamsResults[WilliamsResult]` 26 | WilliamsResults is list of WilliamsResult with providing useful helper methods. 27 | 28 | See more: 29 | - [Williams %R Reference](https://python.stockindicators.dev/indicators/WilliamsR/#content) 30 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 31 | """ 32 | results = CsIndicator.GetWilliamsR[Quote](CsList(Quote, quotes), lookback_periods) 33 | return WilliamsResults(results, WilliamsResult) 34 | 35 | 36 | class WilliamsResult(ResultBase): 37 | """ 38 | A wrapper class for a single unit of Williams %R results. 39 | """ 40 | 41 | @property 42 | def williams_r(self) -> Optional[float]: 43 | return self._csdata.WilliamsR 44 | 45 | @williams_r.setter 46 | def williams_r(self, value): 47 | self._csdata.WilliamsR = value 48 | 49 | 50 | _T = TypeVar("_T", bound=WilliamsResult) 51 | class WilliamsResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 52 | """ 53 | A wrapper class for the list of Williams %R results. 54 | It is exactly same with built-in `list` except for that it provides 55 | some useful helper methods written in CSharp implementation. 56 | """ 57 | -------------------------------------------------------------------------------- /stock_indicators/indicators/wma.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Optional, TypeVar 2 | 3 | from stock_indicators._cslib import CsIndicator 4 | from stock_indicators.indicators.common.enums import CandlePart 5 | from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin 6 | from stock_indicators.indicators.common.results import IndicatorResults, ResultBase 7 | from stock_indicators.indicators.common.quote import Quote 8 | 9 | 10 | def get_wma(quotes: Iterable[Quote], lookback_periods: int, 11 | candle_part: CandlePart = CandlePart.CLOSE): 12 | """Get WMA calculated. 13 | 14 | Weighted Moving Average (WMA) is the linear weighted average 15 | of Close price over N lookback periods. 16 | This also called Linear Weighted Moving Average (LWMA). 17 | 18 | Parameters: 19 | `quotes` : Iterable[Quote] 20 | Historical price quotes. 21 | 22 | `lookback_periods` : int 23 | Number of periods in the lookback window. 24 | 25 | `candle_part` : CandlePart, defaults CandlePart.CLOSE 26 | Selected OHLCV part. 27 | 28 | Returns: 29 | `WMAResults[WMAResult]` 30 | WMAResults is list of WMAResult with providing useful helper methods. 31 | 32 | See more: 33 | - [WMA Reference](https://python.stockindicators.dev/indicators/Wma/#content) 34 | - [Helper Methods](https://python.stockindicators.dev/utilities/#content) 35 | """ 36 | quotes = Quote.use(quotes, candle_part) # Error occurs if not assigned to local var. 37 | results = CsIndicator.GetWma(quotes, lookback_periods) 38 | return WMAResults(results, WMAResult) 39 | 40 | 41 | class WMAResult(ResultBase): 42 | """ 43 | A wrapper class for a single unit of Weighted Moving Average (WMA) results. 44 | """ 45 | 46 | @property 47 | def wma(self) -> Optional[float]: 48 | return self._csdata.Wma 49 | 50 | @wma.setter 51 | def wma(self, value): 52 | self._csdata.Wma = value 53 | 54 | 55 | _T = TypeVar("_T", bound=WMAResult) 56 | class WMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): 57 | """ 58 | A wrapper class for the list of Weighted Moving Average (WMA) results. 59 | It is exactly same with built-in `list` except for that it provides 60 | some useful helper methods written in CSharp implementation. 61 | """ 62 | -------------------------------------------------------------------------------- /stock_indicators/logging_config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def configure_logging(debug=False): 5 | """Configure logging for the stock_indicators package.""" 6 | # Get the logger for the package 7 | logger = logging.getLogger("stock_indicators") 8 | 9 | # Set the logging level based on the debug flag 10 | log_level = logging.DEBUG if debug else logging.WARNING 11 | logger.setLevel(log_level) 12 | 13 | # Avoid adding multiple handlers if it's already set up 14 | if not logger.handlers: 15 | handler = logging.StreamHandler() # Print logs to console 16 | handler.setFormatter( 17 | logging.Formatter("%(name)s - %(levelname)s - %(message)s") 18 | ) 19 | logger.addHandler(handler) 20 | 21 | # Warn if DEBUG is enabled 22 | if debug: 23 | logger.warning("DEBUG logging is enabled.") 24 | -------------------------------------------------------------------------------- /test_data/quotes/History.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/test_data/quotes/History.xlsx -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facioquo/stock-indicators-python/da3ea4b104c82a8b827ef495af95f46d729423ca/tests/__init__.py -------------------------------------------------------------------------------- /tests/common/test_candle.py: -------------------------------------------------------------------------------- 1 | from stock_indicators import indicators 2 | 3 | class TestCandleResults: 4 | def test_standard(self, quotes): 5 | results = indicators.get_doji(quotes, 0.1) 6 | 7 | r = results[0] 8 | assert 212.80 == round(float(r.candle.close), 2) 9 | assert 1.83 == round(float(r.candle.size), 2) 10 | assert 0.19 == round(float(r.candle.body), 2) 11 | assert 0.10 == round(float(r.candle.body_pct), 2) 12 | assert 1.09 == round(float(r.candle.lower_wick), 2) 13 | assert 0.60 == round(float(r.candle.lower_wick_pct), 2) 14 | assert 0.55 == round(float(r.candle.upper_wick), 2) 15 | assert 0.30 == round(float(r.candle.upper_wick_pct), 2) 16 | assert r.candle.is_bearish == False 17 | assert r.candle.is_bullish == True 18 | 19 | r = results[351] 20 | assert 263.16 == round(float(r.candle.close), 2) 21 | assert 1.24 == round(float(r.candle.size), 2) 22 | assert 0.00 == round(float(r.candle.body), 2) 23 | assert 0.00 == round(float(r.candle.body_pct), 2) 24 | assert 0.55 == round(float(r.candle.lower_wick), 2) 25 | assert 0.44 == round(float(r.candle.lower_wick_pct), 2) 26 | assert 0.69 == round(float(r.candle.upper_wick), 2) 27 | assert 0.56 == round(float(r.candle.upper_wick_pct), 2) 28 | assert r.candle.is_bearish == False 29 | assert r.candle.is_bullish == False 30 | 31 | r = results[501] 32 | assert 245.28 == round(float(r.candle.close), 2) 33 | assert 2.67 == round(float(r.candle.size), 2) 34 | assert 0.36 == round(float(r.candle.body), 2) 35 | assert 0.13 == round(float(r.candle.body_pct), 2) 36 | assert 2.05 == round(float(r.candle.lower_wick), 2) 37 | assert 0.77 == round(float(r.candle.lower_wick_pct), 2) 38 | assert 0.26 == round(float(r.candle.upper_wick), 2) 39 | assert 0.10 == round(float(r.candle.upper_wick_pct), 2) 40 | assert r.candle.is_bearish == False 41 | assert r.candle.is_bullish == True 42 | -------------------------------------------------------------------------------- /tests/common/test_common.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from stock_indicators import indicators 3 | 4 | class TestCommon: 5 | def test_find(self, quotes): 6 | results = indicators.get_bollinger_bands(quotes) 7 | 8 | r = results.find(datetime(2018, 12, 28)) 9 | assert 252.9625 == round(float(r.sma), 4) 10 | assert 230.3495 == round(float(r.lower_band), 4) 11 | 12 | r = results.find(datetime(2018, 12, 31)) 13 | assert 251.8600 == round(float(r.sma), 4) 14 | assert 230.0196 == round(float(r.lower_band), 4) 15 | 16 | 17 | def test_remove_warmup_periods(self, quotes): 18 | results = indicators.get_adl(quotes) 19 | assert 502 == len(results) 20 | 21 | results = results.remove_warmup_periods(200) 22 | assert 302 == len(results) 23 | 24 | results = results.remove_warmup_periods(1000) 25 | assert 0 == len(results) 26 | -------------------------------------------------------------------------------- /tests/common/test_indicator_results.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import pytest 4 | from stock_indicators import indicators 5 | 6 | class TestIndicatorResults: 7 | def test_add_results(self, quotes): 8 | results = indicators.get_sma(quotes, 20) 9 | r4 = results + results + results + results 10 | 11 | assert len(r4) == len(results) * 4 12 | 13 | for i in range(4): 14 | idx = len(results)*i 15 | assert r4[18+idx].sma is None 16 | assert 214.5250 == round(float(r4[19+idx].sma), 4) 17 | assert 215.0310 == round(float(r4[24+idx].sma), 4) 18 | assert 234.9350 == round(float(r4[149+idx].sma), 4) 19 | assert 255.5500 == round(float(r4[249+idx].sma), 4) 20 | assert 251.8600 == round(float(r4[501+idx].sma), 4) 21 | 22 | def test_mul_results(self, quotes): 23 | results = indicators.get_sma(quotes, 20) 24 | r4 = results * 4 25 | 26 | assert len(r4) == len(results) * 4 27 | 28 | for i in range(4): 29 | idx = len(results)*i 30 | assert r4[18+idx].sma is None 31 | assert 214.5250 == round(float(r4[19+idx].sma), 4) 32 | assert 215.0310 == round(float(r4[24+idx].sma), 4) 33 | assert 234.9350 == round(float(r4[149+idx].sma), 4) 34 | assert 255.5500 == round(float(r4[249+idx].sma), 4) 35 | assert 251.8600 == round(float(r4[501+idx].sma), 4) 36 | 37 | def test_done_and_reload(self, quotes): 38 | results = indicators.get_sma(quotes, 20) 39 | results.done() 40 | 41 | with pytest.raises(ValueError): 42 | results * 2 43 | 44 | results.reload() 45 | r2 = results * 2 46 | 47 | assert len(r2) == len(results) * 2 48 | 49 | def test_find(self, quotes): 50 | results = indicators.get_sma(quotes, 20) 51 | 52 | # r[19] 53 | r = results.find(datetime(2017, 1, 31)) 54 | assert 214.5250 == round(float(r.sma), 4) 55 | 56 | def test_not_found(self, quotes): 57 | results = indicators.get_sma(quotes, 20) 58 | 59 | # returns None 60 | r = results.find(datetime(1996, 10, 12)) 61 | assert r is None 62 | 63 | def test_remove_with_period(self, quotes): 64 | results = indicators.get_sma(quotes, 20) 65 | length = len(results) 66 | 67 | results = results.remove_warmup_periods(50) 68 | assert len(results) == length - 50 69 | -------------------------------------------------------------------------------- /tests/common/test_locale.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal as PyDecimal 2 | 3 | import pytest 4 | 5 | from stock_indicators._cslib import CsDecimal 6 | from stock_indicators._cstypes import Decimal as CsDecimalConverter 7 | from stock_indicators._cstypes.decimal import to_pydecimal 8 | 9 | 10 | @pytest.mark.localization 11 | class TestLocale: 12 | """ 13 | These tests are intended for environments where a comma is used as the decimal separator, 14 | such as when the current system locale is ru_RU.UTF-8. 15 | """ 16 | 17 | def test_conversion_to_Python_decimal_with_comma_decimal_separator(self): 18 | cs_decimal = CsDecimal.Parse("1996,1012") 19 | assert "1996,1012" == str(cs_decimal) 20 | assert PyDecimal("1996.1012") == to_pydecimal(cs_decimal) 21 | 22 | def test_conversion_to_CSharp_decimal_with_comma_decimal_separator(self): 23 | # Applied CultureInfo.InvariantCulture, comma as a decimal separator should be ignored. 24 | cs_decimal = CsDecimalConverter("1996,10.12") 25 | assert "199610,12" == str(cs_decimal) 26 | assert PyDecimal("199610.12") == to_pydecimal(cs_decimal) 27 | 28 | def test_re_conversion_to_CSharp_decimal_with_comma_decimal_separator(self): 29 | # result value will be distorted 30 | # if CsDecimal is converted into CsDecimal again, since comma as a decimal separator would be ignored. 31 | # Note: did not add defensive logic to avoid performance loss. 32 | cs_decimal = CsDecimalConverter("1996,10.12") 33 | assert "199610,12" == str(cs_decimal) 34 | 35 | cs_decimal = CsDecimalConverter(cs_decimal) 36 | assert "19961012" == str(cs_decimal) 37 | 38 | def test_conversion_to_double_with_comma_decimal_separator(self): 39 | from System import Double as CsDouble 40 | 41 | cs_double = CsDouble.Parse("1996,1012") 42 | assert 1996.1012 == cs_double # should be period-separated float. 43 | -------------------------------------------------------------------------------- /tests/common/test_type_compatibility.py: -------------------------------------------------------------------------------- 1 | 2 | from enum import Enum, IntEnum 3 | from stock_indicators._cslib import CsQuote, CsCandleProperties 4 | from stock_indicators.indicators.common.candles import CandleProperties 5 | from stock_indicators.indicators.common.enums import PivotPointType 6 | from stock_indicators.indicators.common.quote import Quote 7 | 8 | class TestTypeCompat: 9 | def test_quote_based_class(self): 10 | # Quote 11 | assert issubclass(Quote, CsQuote) 12 | 13 | # CandleProperties 14 | assert issubclass(CandleProperties, Quote) 15 | assert issubclass(CandleProperties, CsQuote) 16 | assert issubclass(CandleProperties, CsCandleProperties) 17 | 18 | def test_cs_compatible_enum(self): 19 | assert isinstance(PivotPointType.STANDARD, Enum) 20 | assert isinstance(PivotPointType.STANDARD, IntEnum) 21 | assert isinstance(PivotPointType.STANDARD.value, int) 22 | 23 | assert int(PivotPointType.STANDARD) == 0 24 | assert PivotPointType.STANDARD == 0 25 | assert PivotPointType.STANDARD == PivotPointType.STANDARD 26 | -------------------------------------------------------------------------------- /tests/test_alma.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestALMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_alma(quotes, lookback_periods=10, offset=0.85, sigma=6) 9 | 10 | # assertions 11 | 12 | # proper quantities 13 | # should always be the same number of results as there is quotes 14 | r = results[8] 15 | assert r.alma is None 16 | 17 | r = results[9] 18 | assert 214.1839 == round(float(r.alma), 4) 19 | 20 | r = results[24] 21 | assert 216.0619 == round(float(r.alma), 4) 22 | 23 | r = results[149] 24 | assert 235.8609 == round(float(r.alma), 4) 25 | 26 | r = results[249] 27 | assert 257.5787 == round(float(r.alma), 4) 28 | 29 | r = results[501] 30 | assert 242.1871 == round(float(r.alma), 4) 31 | 32 | def test_bad_data(self, quotes_bad): 33 | r = indicators.get_alma(quotes_bad, 14, 0.5, 3) 34 | 35 | assert 502 == len(r) 36 | 37 | def test_removed(self, quotes): 38 | results = indicators.get_alma(quotes, 10, 0.85, 6).remove_warmup_periods() 39 | 40 | assert 502 - 9 == len(results) 41 | 42 | last = results.pop() 43 | assert 242.1871 == round(float(last.alma), 4) 44 | 45 | def test_condense(self, quotes): 46 | results = indicators.get_alma(quotes, 10, 0.85, 6).condense() 47 | 48 | assert 493 == len(results) 49 | 50 | r = results[-1] 51 | assert 242.1871 == round(float(r.alma), 4) 52 | 53 | def test_exceptions(self, quotes): 54 | from System import ArgumentOutOfRangeException 55 | 56 | with pytest.raises(ArgumentOutOfRangeException): 57 | indicators.get_alma(quotes, 0, 1, 5) 58 | 59 | with pytest.raises(ArgumentOutOfRangeException): 60 | indicators.get_alma(quotes, 15, 1.1, 3) 61 | 62 | with pytest.raises(ArgumentOutOfRangeException): 63 | indicators.get_alma(quotes, 10, 0.5, 0) 64 | -------------------------------------------------------------------------------- /tests/test_aroon.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestAroon: 7 | def test_standard(self, quotes): 8 | results = indicators.get_aroon(quotes, 25) 9 | 10 | assert 502 == len(results) 11 | assert 477 == len(list(filter(lambda x: x.aroon_up is not None, results))) 12 | assert 477 == len(list(filter(lambda x: x.aroon_down is not None, results))) 13 | assert 477 == len(list(filter(lambda x: x.oscillator is not None, results))) 14 | 15 | r = results[210] 16 | assert 100 == float(r.aroon_up) 17 | assert 000 == float(r.aroon_down) 18 | assert 100 == float(r.oscillator) 19 | 20 | r = results[293] 21 | assert 000 == float(r.aroon_up) 22 | assert +40 == float(r.aroon_down) 23 | assert -40 == float(r.oscillator) 24 | 25 | r = results[298] 26 | assert 000 == float(r.aroon_up) 27 | assert +20 == float(r.aroon_down) 28 | assert -20 == float(r.oscillator) 29 | 30 | r = results[458] 31 | assert 0000 == float(r.aroon_up) 32 | assert +100 == float(r.aroon_down) 33 | assert -100 == float(r.oscillator) 34 | 35 | r = results[501] 36 | assert +28 == float(r.aroon_up) 37 | assert +88 == float(r.aroon_down) 38 | assert -60 == float(r.oscillator) 39 | 40 | def test_bad_data(self, quotes_bad): 41 | r = indicators.get_aroon(quotes_bad, 20) 42 | 43 | assert 502 == len(r) 44 | 45 | def test_removed(self, quotes): 46 | results = indicators.get_aroon(quotes, 25).remove_warmup_periods() 47 | 48 | assert 502 - 25 == len(results) 49 | 50 | last = results.pop() 51 | assert +28 == float(last.aroon_up) 52 | assert +88 == float(last.aroon_down) 53 | assert -60 == float(last.oscillator) 54 | 55 | def test_condense(self, quotes): 56 | results = indicators.get_aroon(quotes, 25).condense() 57 | 58 | assert 477 == len(results) 59 | 60 | r = results[-1] 61 | assert 28 == float(r.aroon_up) 62 | assert 88 == float(r.aroon_down) 63 | assert -60 == float(r.oscillator) 64 | 65 | def test_exceptions(self, quotes): 66 | from System import ArgumentOutOfRangeException 67 | 68 | with pytest.raises(ArgumentOutOfRangeException): 69 | indicators.get_aroon(quotes, 0) 70 | -------------------------------------------------------------------------------- /tests/test_atr.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestATR: 7 | def test_standard(self, quotes): 8 | results = indicators.get_atr(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 488 == len(list(filter(lambda x: x.atr, results))) 12 | 13 | r = results[13] 14 | assert 1.4500 == round(float(r.tr), 2) 15 | assert r.atr is None 16 | assert r.atrp is None 17 | 18 | r = results[14] 19 | assert 1.8200 == round(float(r.tr), 4) 20 | assert 1.3364 == round(float(r.atr), 4) 21 | assert 0.6215 == round(float(r.atrp), 4) 22 | 23 | r = results[24] 24 | assert 0.8800 == round(float(r.tr), 4) 25 | assert 1.3034 == round(float(r.atr), 4) 26 | assert 0.6026 == round(float(r.atrp), 4) 27 | 28 | r = results[249] 29 | assert 0.5800 == round(float(r.tr), 4) 30 | assert 1.3381 == round(float(r.atr), 4) 31 | assert 0.5187 == round(float(r.atrp), 4) 32 | 33 | r = results[501] 34 | assert 2.6700 == round(float(r.tr), 4) 35 | assert 6.1497 == round(float(r.atr), 4) 36 | assert 2.5072 == round(float(r.atrp), 4) 37 | 38 | def test_bad_data(self, quotes_bad): 39 | r = indicators.get_atr(quotes_bad, 20) 40 | 41 | assert 502 == len(r) 42 | 43 | def test_removed(self, quotes): 44 | results = indicators.get_atr(quotes, 14).remove_warmup_periods() 45 | 46 | assert 488 == len(results) 47 | 48 | last = results.pop() 49 | assert 2.6700 == round(float(last.tr), 4) 50 | assert 6.1497 == round(float(last.atr), 4) 51 | assert 2.5072 == round(float(last.atrp), 4) 52 | 53 | def test_condense(self, quotes): 54 | results = indicators.get_atr(quotes, 14).condense() 55 | 56 | assert 488 == len(results) 57 | 58 | last = results.pop() 59 | assert 2.6700 == round(float(last.tr), 4) 60 | assert 6.1497 == round(float(last.atr), 4) 61 | assert 2.5072 == round(float(last.atrp), 4) 62 | 63 | def test_exceptions(self, quotes): 64 | from System import ArgumentOutOfRangeException 65 | 66 | with pytest.raises(ArgumentOutOfRangeException): 67 | indicators.get_atr(quotes, 1) 68 | -------------------------------------------------------------------------------- /tests/test_awesome.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestAwesome: 7 | def test_standard(self, quotes): 8 | results = indicators.get_awesome(quotes, 5, 34) 9 | 10 | assert 502 == len(results) 11 | assert 469 == len(list(filter(lambda x: x.oscillator is not None, results))) 12 | 13 | r = results[32] 14 | assert r.oscillator is None 15 | assert r.normalized is None 16 | 17 | r = results[33] 18 | assert 5.4756 == round(float(r.oscillator), 4) 19 | assert 2.4548 == round(float(r.normalized), 4) 20 | 21 | r = results[249] 22 | assert 5.0618 == round(float(r.oscillator), 4) 23 | assert 1.9634 == round(float(r.normalized), 4) 24 | 25 | r = results[501] 26 | assert -17.7692 == round(float(r.oscillator), 4) 27 | assert -7.2763 == round(float(r.normalized), 4) 28 | 29 | def test_bad_data(self, quotes_bad): 30 | r = indicators.get_awesome(quotes_bad, 5, 34) 31 | 32 | assert 502 == len(r) 33 | 34 | def test_removed(self, quotes): 35 | results = indicators.get_awesome(quotes, 5, 34).remove_warmup_periods() 36 | 37 | assert 502 - 33 == len(results) 38 | 39 | last = results.pop() 40 | assert -17.7692 == round(float(last.oscillator), 4) 41 | assert -7.2763 == round(float(last.normalized), 4) 42 | 43 | def test_condense(self, quotes): 44 | results = indicators.get_awesome(quotes, 5, 34).condense() 45 | 46 | assert 469 == len(results) 47 | 48 | last = results.pop() 49 | assert -17.7692 == round(float(last.oscillator), 4) 50 | assert -7.2763 == round(float(last.normalized), 4) 51 | 52 | def test_exceptions(self, quotes): 53 | from System import ArgumentOutOfRangeException 54 | 55 | with pytest.raises(ArgumentOutOfRangeException): 56 | indicators.get_awesome(quotes, 0, 34) 57 | 58 | with pytest.raises(ArgumentOutOfRangeException): 59 | indicators.get_awesome(quotes, 25, 25) 60 | -------------------------------------------------------------------------------- /tests/test_basic_quote.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from stock_indicators import indicators 4 | from stock_indicators.indicators.common.enums import CandlePart 5 | from stock_indicators.indicators.common.quote import Quote 6 | 7 | class TestBasicQuote: 8 | def test_standard(self, quotes): 9 | o = indicators.get_basic_quote(quotes, CandlePart.OPEN) 10 | h = indicators.get_basic_quote(quotes, CandlePart.HIGH) 11 | l = indicators.get_basic_quote(quotes, CandlePart.LOW) 12 | c = indicators.get_basic_quote(quotes, CandlePart.CLOSE) 13 | v = indicators.get_basic_quote(quotes, CandlePart.VOLUME) 14 | hl = indicators.get_basic_quote(quotes, CandlePart.HL2) 15 | hlc = indicators.get_basic_quote(quotes, CandlePart.HLC3) 16 | oc = indicators.get_basic_quote(quotes, CandlePart.OC2) 17 | ohl = indicators.get_basic_quote(quotes, CandlePart.OHL3) 18 | ohlc = indicators.get_basic_quote(quotes, CandlePart.OHLC4) 19 | 20 | assert 502 == len(c) 21 | 22 | assert datetime(2018, 12, 31) == c[-1].date 23 | 24 | assert 244.92 == o[-1].value 25 | assert 245.54 == h[-1].value 26 | assert 242.87 == l[-1].value 27 | assert 245.28 == c[-1].value 28 | assert 147031456 == v[-1].value 29 | assert 244.205 == hl[-1].value 30 | assert 244.5633 == round(float(hlc[-1].value), 4) 31 | assert 245.1 == oc[-1].value 32 | assert 244.4433 == round(float(ohl[-1].value), 4) 33 | assert 244.6525 == ohlc[-1].value 34 | 35 | def test_use(self, quotes): 36 | results = Quote.use(quotes, CandlePart.CLOSE) 37 | results = list(results) 38 | 39 | assert 502 == len(results) 40 | -------------------------------------------------------------------------------- /tests/test_bop.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestBOP: 7 | def test_standard(self, quotes): 8 | results = indicators.get_bop(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 489 == len(list(filter(lambda x: x.bop is not None, results))) 12 | 13 | r = results[12] 14 | assert r.bop is None 15 | 16 | r = results[13] 17 | assert 0.081822 == round(float(r.bop), 6) 18 | 19 | r = results[149] 20 | assert -0.016203 == round(float(r.bop), 6) 21 | 22 | r = results[249] 23 | assert -0.058682 == round(float(r.bop), 6) 24 | 25 | r = results[501] 26 | assert -0.292788 == round(float(r.bop), 6) 27 | 28 | def test_bad_data(self, quotes_bad): 29 | r = indicators.get_bop(quotes_bad) 30 | assert 502 == len(r) 31 | 32 | def test_removed(self, quotes): 33 | results = indicators.get_bop(quotes).remove_warmup_periods() 34 | 35 | assert 502 - 13 == len(results) 36 | 37 | last = results.pop() 38 | assert -0.292788 == round(float(last.bop), 6) 39 | 40 | def test_condense(self, quotes): 41 | results = indicators.get_bop(quotes).condense() 42 | 43 | assert 489 == len(results) 44 | 45 | last = results.pop() 46 | assert -0.292788 == round(float(last.bop), 6) 47 | 48 | def test_exceptions(self, quotes): 49 | from System import ArgumentOutOfRangeException 50 | 51 | with pytest.raises(ArgumentOutOfRangeException): 52 | indicators.get_bop(quotes, 0) 53 | -------------------------------------------------------------------------------- /tests/test_cci.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestCCI: 7 | def test_standard(self, quotes): 8 | results = indicators.get_cci(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.cci is not None, results))) 12 | 13 | r = results[501] 14 | assert -52.9946 == round(float(r.cci), 4) 15 | 16 | def test_bad_data(self, quotes_bad): 17 | r = indicators.get_cci(quotes_bad, 15) 18 | assert 502 == len(r) 19 | 20 | def test_removed(self, quotes): 21 | results = indicators.get_cci(quotes, 20).remove_warmup_periods() 22 | 23 | assert 502 - 19 == len(results) 24 | 25 | last = results.pop() 26 | assert -52.9946 == round(float(last.cci), 4) 27 | 28 | def test_condense(self, quotes): 29 | results = indicators.get_cci(quotes, 20).condense() 30 | 31 | assert 483 == len(results) 32 | 33 | last = results.pop() 34 | assert -52.9946 == round(float(last.cci), 4) 35 | 36 | def test_exceptions(self, quotes): 37 | from System import ArgumentOutOfRangeException 38 | 39 | with pytest.raises(ArgumentOutOfRangeException): 40 | indicators.get_cci(quotes, 0) 41 | -------------------------------------------------------------------------------- /tests/test_chaikin_oscillator.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestChaikinOsc: 7 | def test_standard(self, quotes): 8 | fast_periods = 3 9 | slow_periods = 10 10 | 11 | results = indicators.get_chaikin_osc(quotes, fast_periods, slow_periods) 12 | 13 | assert 502 == len(results) 14 | assert 502 - slow_periods + 1 == len( 15 | list(filter(lambda x: x.oscillator is not None, results)) 16 | ) 17 | 18 | r = results[501] 19 | assert 3439986548.42 == round(float(r.adl), 2) 20 | assert 0.8052 == round(float(r.money_flow_multiplier), 4) 21 | assert 118396116.25 == round(float(r.money_flow_volume), 2) 22 | assert -19135200.72 == round(float(r.oscillator), 2) 23 | 24 | def test_bad_data(self, quotes_bad): 25 | r = indicators.get_chaikin_osc(quotes_bad, 5, 15) 26 | assert 502 == len(r) 27 | 28 | def test_removed(self, quotes): 29 | fast_periods = 3 30 | slow_periods = 10 31 | 32 | results = indicators.get_chaikin_osc( 33 | quotes, fast_periods, slow_periods 34 | ).remove_warmup_periods() 35 | 36 | assert 502 - (slow_periods + 100) == len(results) 37 | 38 | last = results.pop() 39 | assert 3439986548.42 == round(float(last.adl), 2) 40 | assert 0.8052 == round(float(last.money_flow_multiplier), 4) 41 | assert 118396116.25 == round(float(last.money_flow_volume), 2) 42 | assert -19135200.72 == round(float(last.oscillator), 2) 43 | 44 | def test_condense(self, quotes): 45 | results = indicators.get_chaikin_osc(quotes, 3, 10).condense() 46 | 47 | assert 493 == len(results) 48 | 49 | last = results.pop() 50 | assert 3439986548.42 == round(float(last.adl), 2) 51 | assert 0.8052 == round(float(last.money_flow_multiplier), 4) 52 | assert 118396116.25 == round(float(last.money_flow_volume), 2) 53 | assert -19135200.72 == round(float(last.oscillator), 2) 54 | 55 | def test_exceptions(self, quotes): 56 | from System import ArgumentOutOfRangeException 57 | 58 | with pytest.raises(ArgumentOutOfRangeException): 59 | indicators.get_chaikin_osc(quotes, 0) 60 | 61 | with pytest.raises(ArgumentOutOfRangeException): 62 | indicators.get_chaikin_osc(quotes, 10, 5) 63 | -------------------------------------------------------------------------------- /tests/test_chandelier.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | from stock_indicators.indicators.common.enums import ChandelierType 5 | 6 | 7 | class TestChandelier: 8 | def test_standard(self, quotes): 9 | long_results = indicators.get_chandelier(quotes, 22, 3) 10 | 11 | assert 502 == len(long_results) 12 | assert 480 == len( 13 | list(filter(lambda x: x.chandelier_exit is not None, long_results)) 14 | ) 15 | 16 | r = long_results[501] 17 | assert 256.5860 == round(float(r.chandelier_exit), 4) 18 | 19 | r = long_results[492] 20 | assert 259.0480 == round(float(r.chandelier_exit), 4) 21 | 22 | short_results = indicators.get_chandelier(quotes, 22, 3, ChandelierType.SHORT) 23 | 24 | r = short_results[501] 25 | assert 246.4240 == round(float(r.chandelier_exit), 4) 26 | 27 | def test_bad_data(self, quotes_bad): 28 | r = indicators.get_chandelier(quotes_bad, 15, 2) 29 | assert 502 == len(r) 30 | 31 | def test_no_data(self, quotes): 32 | r = indicators.get_chandelier([]) 33 | assert 0 == len(r) 34 | 35 | r = indicators.get_chandelier(quotes[:1]) 36 | assert 1 == len(r) 37 | 38 | def test_removed(self, quotes): 39 | long_results = indicators.get_chandelier(quotes, 22, 3).remove_warmup_periods() 40 | 41 | assert 480 == len(long_results) 42 | 43 | last = long_results.pop() 44 | assert 256.5860 == round(float(last.chandelier_exit), 4) 45 | 46 | def test_condense(self, quotes): 47 | long_results = indicators.get_chandelier(quotes, 22, 3).condense() 48 | 49 | assert 480 == len(long_results) 50 | 51 | last = long_results.pop() 52 | assert 256.5860 == round(float(last.chandelier_exit), 4) 53 | 54 | def test_exceptions(self, quotes): 55 | from System import ArgumentOutOfRangeException 56 | 57 | with pytest.raises(ArgumentOutOfRangeException): 58 | indicators.get_chandelier(quotes, 0) 59 | 60 | with pytest.raises(ArgumentOutOfRangeException): 61 | indicators.get_chandelier(quotes, 25, 0) 62 | -------------------------------------------------------------------------------- /tests/test_chop.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestChop: 7 | def test_standard(self, quotes): 8 | results = indicators.get_chop(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 488 == len(list(filter(lambda x: x.chop is not None, results))) 12 | 13 | r = results[13] 14 | assert r.chop is None 15 | 16 | r = results[14] 17 | assert 69.9967 == round(float(r.chop), 4) 18 | 19 | r = results[249] 20 | assert 41.8499 == round(float(r.chop), 4) 21 | 22 | r = results[501] 23 | assert 38.6526 == round(float(r.chop), 4) 24 | 25 | def test_small_lookback(self, quotes): 26 | results = indicators.get_chop(quotes, 2) 27 | 28 | assert 502 == len(results) 29 | assert 500 == len(list(filter(lambda x: x.chop is not None, results))) 30 | 31 | def test_bad_data(self, quotes_bad): 32 | r = indicators.get_chop(quotes_bad, 20) 33 | assert 502 == len(r) 34 | 35 | def test_removed(self, quotes): 36 | results = indicators.get_chop(quotes, 14).remove_warmup_periods() 37 | 38 | assert 502 - 14 == len(results) 39 | 40 | last = results.pop() 41 | assert 38.6526 == round(float(last.chop), 4) 42 | 43 | def test_condense(self, quotes): 44 | results = indicators.get_chop(quotes, 14).condense() 45 | 46 | assert 488 == len(results) 47 | 48 | last = results.pop() 49 | assert 38.6526 == round(float(last.chop), 4) 50 | 51 | def test_exceptions(self, quotes): 52 | from System import ArgumentOutOfRangeException 53 | 54 | with pytest.raises(ArgumentOutOfRangeException): 55 | indicators.get_chop(quotes, 1) 56 | -------------------------------------------------------------------------------- /tests/test_cmf.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestCMF: 7 | def test_standard(self, quotes): 8 | results = indicators.get_cmf(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.cmf is not None, results))) 12 | 13 | r = results[49] 14 | assert 000.5468 == round(float(r.money_flow_multiplier), 4) 15 | assert 55609259 == round(float(r.money_flow_volume), 2) 16 | assert 0.350596 == round(float(r.cmf), 6) 17 | 18 | r = results[249] 19 | assert 0.7778 == round(float(r.money_flow_multiplier), 4) 20 | assert 36433792.89 == round(float(r.money_flow_volume), 2) 21 | assert -0.040226 == round(float(r.cmf), 6) 22 | 23 | r = results[501] 24 | assert 0.8052 == round(float(r.money_flow_multiplier), 4) 25 | assert 118396116.25 == round(float(r.money_flow_volume), 2) 26 | assert -0.123754 == round(float(r.cmf), 6) 27 | 28 | def test_bad_data(self, quotes_bad): 29 | r = indicators.get_cmf(quotes_bad, 15) 30 | assert 502 == len(r) 31 | 32 | def test_big_data(self, quotes_big): 33 | r = indicators.get_cmf(quotes_big, 150) 34 | assert 1246 == len(r) 35 | 36 | def test_removed(self, quotes): 37 | results = indicators.get_cmf(quotes, 20).remove_warmup_periods() 38 | 39 | assert 502 - 19 == len(results) 40 | 41 | last = results.pop() 42 | assert 0.8052 == round(float(last.money_flow_multiplier), 4) 43 | assert 118396116.25 == round(float(last.money_flow_volume), 2) 44 | assert -0.123754 == round(float(last.cmf), 6) 45 | 46 | def test_condense(self, quotes): 47 | results = indicators.get_cmf(quotes, 20).condense() 48 | 49 | assert 483 == len(results) 50 | 51 | last = results.pop() 52 | assert 0.8052 == round(float(last.money_flow_multiplier), 4) 53 | assert 118396116.25 == round(float(last.money_flow_volume), 2) 54 | assert -0.123754 == round(float(last.cmf), 6) 55 | 56 | def test_exceptions(self, quotes): 57 | from System import ArgumentOutOfRangeException 58 | 59 | with pytest.raises(ArgumentOutOfRangeException): 60 | indicators.get_cmf(quotes, 0) 61 | -------------------------------------------------------------------------------- /tests/test_cmo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestCMO: 7 | def test_standard(self, quotes): 8 | results = indicators.get_cmo(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 488 == len(list(filter(lambda x: x.cmo is not None, results))) 12 | 13 | r = results[13] 14 | assert r.cmo is None 15 | 16 | r = results[14] 17 | assert 24.1081 == round(float(r.cmo), 4) 18 | 19 | r = results[249] 20 | assert 48.9614 == round(float(r.cmo), 4) 21 | 22 | r = results[501] 23 | assert -26.7502 == round(float(r.cmo), 4) 24 | 25 | def test_bad_data(self, quotes_bad): 26 | r = indicators.get_cmo(quotes_bad, 15) 27 | 28 | assert 502 == len(r) 29 | 30 | def test_quotes_no(self, quotes): 31 | r = indicators.get_cmo([], 5) 32 | assert 0 == len(r) 33 | 34 | r = indicators.get_cmo(quotes[:1], 5) 35 | assert 1 == len(r) 36 | 37 | def test_removed(self, quotes): 38 | results = indicators.get_cmo(quotes, 14).remove_warmup_periods() 39 | 40 | assert 488 == len(results) 41 | 42 | last = results.pop() 43 | assert -26.7502 == round(float(last.cmo), 4) 44 | 45 | def test_condense(self, quotes): 46 | results = indicators.get_cmo(quotes, 14).condense() 47 | 48 | assert 488 == len(results) 49 | 50 | last = results.pop() 51 | assert -26.7502 == round(float(last.cmo), 4) 52 | 53 | def test_exceptions(self, quotes): 54 | from System import ArgumentOutOfRangeException 55 | 56 | with pytest.raises(ArgumentOutOfRangeException): 57 | indicators.get_cmo(quotes, 0) 58 | -------------------------------------------------------------------------------- /tests/test_correlation.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestCorrelation: 7 | def test_standard(self, quotes, quotes_other): 8 | results = indicators.get_correlation(quotes, quotes_other, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.correlation is not None, results))) 12 | 13 | r = results[18] 14 | assert r.correlation is None 15 | assert r.r_squared is None 16 | 17 | r = results[19] 18 | assert 0.6933 == round(float(r.correlation), 4) 19 | assert 0.4806 == round(float(r.r_squared), 4) 20 | 21 | r = results[257] 22 | assert -0.1347 == round(float(r.correlation), 4) 23 | assert 00.0181 == round(float(r.r_squared), 4) 24 | 25 | r = results[501] 26 | assert 0.8460 == round(float(r.correlation), 4) 27 | assert 0.7157 == round(float(r.r_squared), 4) 28 | 29 | def test_bad_data(self, quotes_bad): 30 | r = indicators.get_correlation(quotes_bad, quotes_bad, 15) 31 | assert 502 == len(r) 32 | 33 | def test_big_data(self, quotes_big): 34 | r = indicators.get_correlation(quotes_big, quotes_big, 150) 35 | assert 1246 == len(r) 36 | 37 | def test_removed(self, quotes, quotes_other): 38 | results = indicators.get_correlation(quotes, quotes_other, 20) 39 | results = results.remove_warmup_periods() 40 | 41 | assert 502 - 19 == len(results) 42 | 43 | last = results.pop() 44 | assert 0.8460 == round(float(last.correlation), 4) 45 | assert 0.7157 == round(float(last.r_squared), 4) 46 | 47 | def test_condense(self, quotes, quotes_other): 48 | results = indicators.get_correlation(quotes, quotes_other, 20).condense() 49 | 50 | assert 483 == len(results) 51 | 52 | last = results.pop() 53 | assert 0.8460 == round(float(last.correlation), 4) 54 | assert 0.7157 == round(float(last.r_squared), 4) 55 | 56 | def test_exceptions(self, quotes, quotes_other): 57 | from System import ArgumentOutOfRangeException 58 | 59 | with pytest.raises(ArgumentOutOfRangeException): 60 | indicators.get_correlation(quotes, quotes_other, 0) 61 | -------------------------------------------------------------------------------- /tests/test_dema.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestDoubleEMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_dema(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.dema is not None, results))) 12 | 13 | r = results[51] 14 | assert 225.8259 == round(float(r.dema), 4) 15 | 16 | r = results[249] 17 | assert 258.4452 == round(float(r.dema), 4) 18 | 19 | r = results[501] 20 | assert 241.1677 == round(float(r.dema), 4) 21 | 22 | def test_bad_data(self, quotes_bad): 23 | r = indicators.get_dema(quotes_bad, 15) 24 | assert 502 == len(r) 25 | 26 | def test_removed(self, quotes): 27 | results = indicators.get_dema(quotes, 20).remove_warmup_periods() 28 | 29 | assert 502 - (2 * 20 + 100) == len(results) 30 | 31 | last = results.pop() 32 | assert 241.1677 == round(float(last.dema), 4) 33 | 34 | def test_condense(self, quotes): 35 | results = indicators.get_dema(quotes, 20).condense() 36 | 37 | assert 483 == len(results) 38 | 39 | last = results.pop() 40 | assert 241.1677 == round(float(last.dema), 4) 41 | 42 | def test_exceptions(self, quotes): 43 | from System import ArgumentOutOfRangeException 44 | 45 | with pytest.raises(ArgumentOutOfRangeException): 46 | indicators.get_dema(quotes, 0) 47 | -------------------------------------------------------------------------------- /tests/test_doji.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | from stock_indicators.indicators.common.enums import Match 5 | 6 | 7 | class TestDoji: 8 | def test_standard(self, quotes): 9 | results = indicators.get_doji(quotes, 0.1) 10 | 11 | assert 502 == len(results) 12 | assert 112 == len(list(filter(lambda x: x.match != Match.NONE, results))) 13 | 14 | r = results[1] 15 | assert r.price is None 16 | assert Match.NONE == r.match 17 | 18 | r = results[23] 19 | assert 216.28 == round(float(r.price), 2) 20 | assert Match.NEUTRAL == r.match 21 | 22 | r = results[46] 23 | assert r.price is None 24 | assert Match.NONE == r.match 25 | 26 | r = results[34] 27 | assert r.price is None 28 | assert Match.NONE == r.match 29 | 30 | r = results[392] 31 | assert r.price is None 32 | assert Match.NONE == r.match 33 | 34 | r = results[451] 35 | assert 273.64 == round(float(r.price), 2) 36 | assert Match.NEUTRAL == r.match 37 | 38 | r = results[477] 39 | assert 256.86 == round(float(r.price), 2) 40 | assert Match.NEUTRAL == r.match 41 | 42 | def test_bad_data(self, quotes_bad): 43 | r = indicators.get_doji(quotes_bad) 44 | assert 502 == len(r) 45 | 46 | def test_quotes_no(self, quotes): 47 | r = indicators.get_doji([]) 48 | assert 0 == len(r) 49 | 50 | r = indicators.get_doji(quotes[:1]) 51 | assert 1 == len(r) 52 | 53 | def test_condense(self, quotes): 54 | r = indicators.get_doji(quotes, 0.1).condense() 55 | assert 112 == len(r) 56 | 57 | def test_exceptions(self, quotes): 58 | from System import ArgumentOutOfRangeException 59 | 60 | with pytest.raises(ArgumentOutOfRangeException): 61 | indicators.get_doji(quotes, -0.001) 62 | 63 | with pytest.raises(ArgumentOutOfRangeException): 64 | indicators.get_doji(quotes, 0.50001) 65 | -------------------------------------------------------------------------------- /tests/test_dpo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestDPO: 7 | def test_standard(self, quotes): 8 | results = indicators.get_dpo(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 489 == len(list(filter(lambda x: x.dpo is not None, results))) 12 | 13 | r = results[51] 14 | assert 1.3264 == round(float(r.dpo), 4) 15 | assert 223.5836 == round(float(r.sma), 4) 16 | 17 | r = results[249] 18 | assert -1.9307 == round(float(r.dpo), 4) 19 | assert 259.9207 == round(float(r.sma), 4) 20 | 21 | r = results[501] 22 | assert r.dpo is None 23 | assert r.sma is None 24 | 25 | # def test_to_quotes(self, quotes): 26 | # new_quotes = indicators.get_dpo(quotes, 14).to_quotes() 27 | 28 | # assert 489 == len(new_quotes) 29 | 30 | # q = new_quotes.pop() 31 | # assert 2.18214 == round(float(q.close), 5) 32 | 33 | def test_bad_data(self, quotes_bad): 34 | r = indicators.get_dpo(quotes_bad, 5) 35 | 36 | assert 502 == len(r) 37 | 38 | def test_condense(self, quotes): 39 | results = indicators.get_dpo(quotes, 14).condense() 40 | 41 | assert 489 == len(results) 42 | 43 | last = results.pop() 44 | assert 2.1821 == round(float(last.dpo), 4) 45 | assert 246.7079 == round(float(last.sma), 4) 46 | 47 | def test_exceptions(self, quotes): 48 | from System import ArgumentOutOfRangeException 49 | 50 | with pytest.raises(ArgumentOutOfRangeException): 51 | indicators.get_dpo(quotes, 0) 52 | -------------------------------------------------------------------------------- /tests/test_dynamic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestDynamic: 7 | def test_standard(self, quotes): 8 | results = indicators.get_dynamic(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 501 == len(list(filter(lambda x: x.dynamic is not None, results))) 12 | 13 | r = results[1] 14 | assert 212.9465 == round(float(r.dynamic), 4) 15 | 16 | r = results[25] 17 | assert 215.4801 == round(float(r.dynamic), 4) 18 | 19 | r = results[250] 20 | assert 256.0554 == round(float(r.dynamic), 4) 21 | 22 | r = results[501] 23 | assert 245.7356 == round(float(r.dynamic), 4) 24 | 25 | def test_bad_data(self, quotes_bad): 26 | r = indicators.get_dynamic(quotes_bad, 15) 27 | 28 | assert 502 == len(r) 29 | 30 | def test_quotes_no(self, quotes): 31 | r = indicators.get_dynamic([], 14) 32 | assert 0 == len(r) 33 | 34 | r = indicators.get_dynamic(quotes[:1], 14) 35 | assert 1 == len(r) 36 | 37 | def test_condense(self, quotes): 38 | results = indicators.get_dynamic(quotes, 14).condense() 39 | 40 | assert 501 == len(results) 41 | 42 | last = results.pop() 43 | assert 245.7356 == round(float(last.dynamic), 4) 44 | 45 | def test_exceptions(self, quotes): 46 | from System import ArgumentOutOfRangeException 47 | 48 | with pytest.raises(ArgumentOutOfRangeException): 49 | indicators.get_dynamic(quotes, 0) 50 | 51 | with pytest.raises(ArgumentOutOfRangeException): 52 | indicators.get_dynamic(quotes, 14, 0) 53 | -------------------------------------------------------------------------------- /tests/test_ema.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | from stock_indicators.indicators.common.enums import CandlePart 5 | 6 | 7 | class TestEMA: 8 | def test_standard(self, quotes): 9 | results = indicators.get_ema(quotes, 20) 10 | 11 | assert 502 == len(results) 12 | assert 483 == len(list(filter(lambda x: x.ema is not None, results))) 13 | 14 | r = results[29] 15 | assert 216.6228 == round(float(r.ema), 4) 16 | 17 | r = results[249] 18 | assert 255.3873 == round(float(r.ema), 4) 19 | 20 | r = results[501] 21 | assert 249.3519 == round(float(r.ema), 4) 22 | 23 | def test_custom(self, quotes): 24 | results = indicators.get_ema(quotes, 20, CandlePart.OPEN) 25 | 26 | assert 502 == len(results) 27 | assert 483 == len(list(filter(lambda x: x.ema is not None, results))) 28 | 29 | r = results[29] 30 | assert 216.2643 == round(float(r.ema), 4) 31 | 32 | r = results[249] 33 | assert 255.4875 == round(float(r.ema), 4) 34 | 35 | r = results[501] 36 | assert 249.9157 == round(float(r.ema), 4) 37 | 38 | def test_bad_data(self, quotes_bad): 39 | r = indicators.get_ema(quotes_bad, 15) 40 | 41 | assert 502 == len(r) 42 | 43 | def test_quotes_no(self, quotes): 44 | r = indicators.get_ema([], 10) 45 | assert 0 == len(r) 46 | 47 | r = indicators.get_ema(quotes[:1], 10) 48 | assert 1 == len(r) 49 | 50 | def test_removed(self, quotes): 51 | results = indicators.get_ema(quotes, 20).remove_warmup_periods() 52 | 53 | assert 502 - (20 + 100) == len(results) 54 | 55 | last = results.pop() 56 | assert 249.3519 == round(float(last.ema), 4) 57 | 58 | def test_condense(self, quotes): 59 | results = indicators.get_ema(quotes, 20).condense() 60 | 61 | assert 483 == len(results) 62 | 63 | last = results.pop() 64 | assert 249.3519 == round(float(last.ema), 4) 65 | 66 | def test_exceptions(self, quotes): 67 | from System import ArgumentOutOfRangeException 68 | 69 | with pytest.raises(ArgumentOutOfRangeException): 70 | indicators.get_ema(quotes, 0) 71 | -------------------------------------------------------------------------------- /tests/test_epma.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestEPMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_epma(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.epma is not None, results))) 12 | 13 | r = results[18] 14 | assert r.epma is None 15 | 16 | r = results[19] 17 | assert 215.6189 == round(float(r.epma), 4) 18 | 19 | r = results[149] 20 | assert 236.7060 == round(float(r.epma), 4) 21 | 22 | r = results[249] 23 | assert 258.5179 == round(float(r.epma), 4) 24 | 25 | r = results[501] 26 | assert 235.8131 == round(float(r.epma), 4) 27 | 28 | def test_bad_data(self, quotes_bad): 29 | r = indicators.get_epma(quotes_bad, 15) 30 | 31 | assert 502 == len(r) 32 | 33 | def test_removed(self, quotes): 34 | results = indicators.get_epma(quotes, 20).remove_warmup_periods() 35 | 36 | assert 502 - 19 == len(results) 37 | 38 | last = results.pop() 39 | assert 235.8131 == round(float(last.epma), 4) 40 | 41 | def test_condense(self, quotes): 42 | results = indicators.get_epma(quotes, 20).condense() 43 | 44 | assert 483 == len(results) 45 | 46 | last = results.pop() 47 | assert 235.8131 == round(float(last.epma), 4) 48 | 49 | def test_exceptions(self, quotes): 50 | from System import ArgumentOutOfRangeException 51 | 52 | with pytest.raises(ArgumentOutOfRangeException): 53 | indicators.get_epma(quotes, 0) 54 | -------------------------------------------------------------------------------- /tests/test_fcb.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestFCB: 7 | def test_standard(self, quotes): 8 | results = indicators.get_fcb(quotes, 2) 9 | 10 | assert 502 == len(results) 11 | assert 497 == len(list(filter(lambda x: x.upper_band is not None, results))) 12 | assert 493 == len(list(filter(lambda x: x.lower_band is not None, results))) 13 | 14 | r = results[4] 15 | assert r.upper_band is None 16 | assert r.lower_band is None 17 | 18 | r = results[10] 19 | assert 214.84 == round(float(r.upper_band), 2) 20 | assert 212.53 == round(float(r.lower_band), 2) 21 | 22 | r = results[120] 23 | assert 233.35 == round(float(r.upper_band), 2) 24 | assert 231.14 == round(float(r.lower_band), 2) 25 | 26 | r = results[180] 27 | assert 236.78 == round(float(r.upper_band), 2) 28 | assert 233.56 == round(float(r.lower_band), 2) 29 | 30 | r = results[250] 31 | assert 258.70 == round(float(r.upper_band), 2) 32 | assert 257.04 == round(float(r.lower_band), 2) 33 | 34 | r = results[501] 35 | assert 262.47 == round(float(r.upper_band), 2) 36 | assert 229.42 == round(float(r.lower_band), 2) 37 | 38 | def test_bad_data(self, quotes_bad): 39 | r = indicators.get_fcb(quotes_bad) 40 | 41 | assert 502 == len(r) 42 | 43 | def test_removed(self, quotes): 44 | results = indicators.get_fcb(quotes, 2).remove_warmup_periods() 45 | 46 | assert 502 - 5 == len(results) 47 | 48 | last = results.pop() 49 | assert 262.47 == round(float(last.upper_band), 2) 50 | assert 229.42 == round(float(last.lower_band), 2) 51 | 52 | def test_condense(self, quotes): 53 | results = indicators.get_fcb(quotes, 2).condense() 54 | 55 | assert 497 == len(results) 56 | 57 | last = results.pop() 58 | assert 262.47 == round(float(last.upper_band), 2) 59 | assert 229.42 == round(float(last.lower_band), 2) 60 | 61 | def test_exceptions(self, quotes): 62 | from System import ArgumentOutOfRangeException 63 | 64 | with pytest.raises(ArgumentOutOfRangeException): 65 | indicators.get_fcb(quotes, 1) 66 | -------------------------------------------------------------------------------- /tests/test_force_index.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestForceIndex: 7 | def test_standard(self, quotes): 8 | r = indicators.get_force_index(quotes, 13) 9 | 10 | assert 502 == len(r) 11 | assert 489 == len(list(filter(lambda x: x.force_index is not None, r))) 12 | 13 | assert r[12].force_index is None 14 | 15 | assert 10668240.778 == round(float(r[13].force_index), 3) 16 | assert 15883211.364 == round(float(r[24].force_index), 3) 17 | assert 7598218.196 == round(float(r[149].force_index), 3) 18 | assert 23612118.994 == round(float(r[249].force_index), 3) 19 | assert -16824018.428 == round(float(r[501].force_index), 3) 20 | 21 | def test_bad_data(self, quotes_bad): 22 | r = indicators.get_force_index(quotes_bad, 2) 23 | assert 502 == len(r) 24 | 25 | def test_quotes_no(self, quotes): 26 | r = indicators.get_force_index([], 5) 27 | assert 0 == len(r) 28 | 29 | r = indicators.get_force_index(quotes[:1], 5) 30 | assert 1 == len(r) 31 | 32 | def test_removed(self, quotes): 33 | results = indicators.get_force_index(quotes, 13).remove_warmup_periods() 34 | 35 | assert 502 - (13 + 100) == len(results) 36 | 37 | last = results.pop() 38 | assert -16824018.428 == round(float(last.force_index), 3) 39 | 40 | def test_condense(self, quotes): 41 | results = indicators.get_force_index(quotes, 13).condense() 42 | 43 | assert 489 == len(results) 44 | 45 | last = results.pop() 46 | assert -16824018.428 == round(float(last.force_index), 3) 47 | 48 | def test_exceptions(self, quotes): 49 | from System import ArgumentOutOfRangeException 50 | 51 | with pytest.raises(ArgumentOutOfRangeException): 52 | indicators.get_force_index(quotes, 0) 53 | -------------------------------------------------------------------------------- /tests/test_heikin_ashi.py: -------------------------------------------------------------------------------- 1 | from stock_indicators import indicators 2 | 3 | 4 | class TestHeikinAshi: 5 | def test_standard(self, quotes): 6 | results = indicators.get_heikin_ashi(quotes) 7 | 8 | assert 502 == len(results) 9 | 10 | r = results[501] 11 | assert 241.3018 == round(float(r.open), 4) 12 | assert 245.5400 == round(float(r.high), 4) 13 | assert 241.3018 == round(float(r.low), 4) 14 | assert 244.6525 == round(float(r.close), 4) 15 | assert 147031456 == r.volume 16 | 17 | # def test_to_quotes(self, quotes): 18 | # new_quotes = indicators.get_heikin_ashi(quotes).to_quotes() 19 | 20 | # assert 502 == len(new_quotes) 21 | # q = new_quotes[501] 22 | # assert 241.3018 == round(float(q.open), 4) 23 | # assert 245.5400 == round(float(q.high), 4) 24 | # assert 241.3018 == round(float(q.low), 4) 25 | # assert 244.6525 == round(float(q.close), 4) 26 | # assert 147031456 == q.volume 27 | 28 | def test_bad_data(self, quotes_bad): 29 | r = indicators.get_heikin_ashi(quotes_bad) 30 | assert 502 == len(r) 31 | -------------------------------------------------------------------------------- /tests/test_hma.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestHMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_hma(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 480 == len(list(filter(lambda x: x.hma is not None, results))) 12 | 13 | r = results[149] 14 | assert 236.0835 == round(float(r.hma), 4) 15 | 16 | r = results[501] 17 | assert 235.6972 == round(float(r.hma), 4) 18 | 19 | def test_bad_data(self, quotes_bad): 20 | r = indicators.get_hma(quotes_bad, 15) 21 | assert 502 == len(r) 22 | 23 | def test_quotes_no(self, quotes): 24 | r = indicators.get_hma([], 5) 25 | assert 0 == len(r) 26 | 27 | r = indicators.get_hma(quotes[:1], 5) 28 | assert 1 == len(r) 29 | 30 | def test_removed(self, quotes): 31 | results = indicators.get_hma(quotes, 20).remove_warmup_periods() 32 | 33 | assert 480 == len(results) 34 | 35 | last = results.pop() 36 | assert 235.6972 == round(float(last.hma), 4) 37 | 38 | def test_condense(self, quotes): 39 | results = indicators.get_hma(quotes, 20).condense() 40 | 41 | assert 480 == len(results) 42 | 43 | last = results.pop() 44 | assert 235.6972 == round(float(last.hma), 4) 45 | 46 | def test_exceptions(self, quotes): 47 | from System import ArgumentOutOfRangeException 48 | 49 | with pytest.raises(ArgumentOutOfRangeException): 50 | indicators.get_hma(quotes, 1) 51 | -------------------------------------------------------------------------------- /tests/test_hurst.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestHurst: 7 | def test_standard_long(self, quotes_longest): 8 | results = indicators.get_hurst(quotes_longest, len(quotes_longest) - 1) 9 | 10 | assert 15821 == len(results) 11 | assert 1 == len(list(filter(lambda x: x.hurst_exponent is not None, results))) 12 | 13 | r = results[15820] 14 | assert 0.483563 == round(float(r.hurst_exponent), 6) 15 | 16 | # def test_to_quotes(self, quotes_longest): 17 | # new_quotes = indicators.get_hurst(quotes_longest, len(quotes_longest) - 1).to_quotes() 18 | 19 | # assert 1 == len(new_quotes) 20 | 21 | # q = new_quotes.pop() 22 | # assert 0.483563 == round(float(q.open), 6) 23 | # assert 0.483563 == round(float(q.high), 6) 24 | # assert 0.483563 == round(float(q.low), 6) 25 | # assert 0.483563 == round(float(q.close), 6) 26 | 27 | def test_bad_data(self, quotes_bad): 28 | r = indicators.get_hurst(quotes_bad, 150) 29 | assert 502 == len(r) 30 | 31 | def test_quotes_no(self, quotes): 32 | r = indicators.get_hurst([]) 33 | assert 0 == len(r) 34 | 35 | r = indicators.get_hurst(quotes[:1]) 36 | assert 1 == len(r) 37 | 38 | def test_removed(self, quotes_longest): 39 | results = indicators.get_hurst(quotes_longest, len(quotes_longest) - 1) 40 | results = results.remove_warmup_periods() 41 | 42 | assert 1 == len(results) 43 | 44 | last = results.pop() 45 | assert 0.483563 == round(float(last.hurst_exponent), 6) 46 | 47 | def test_condense(self, quotes_longest): 48 | results = indicators.get_hurst( 49 | quotes_longest, len(quotes_longest) - 1 50 | ).condense() 51 | 52 | assert 1 == len(results) 53 | 54 | last = results.pop() 55 | assert 0.483563 == round(float(last.hurst_exponent), 6) 56 | 57 | def test_exceptions(self, quotes): 58 | from System import ArgumentOutOfRangeException 59 | 60 | with pytest.raises(ArgumentOutOfRangeException): 61 | indicators.get_hurst(quotes, 19) 62 | -------------------------------------------------------------------------------- /tests/test_marubozu.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | from stock_indicators.indicators.common.enums import Match 5 | 6 | 7 | class TestMarubozu: 8 | def test_standard(self, quotes): 9 | results = indicators.get_marubozu(quotes, 95) 10 | 11 | assert 502 == len(results) 12 | assert 6 == len(list(filter(lambda x: x.match != Match.NONE, results))) 13 | 14 | r = results[31] 15 | assert r.price is None 16 | assert r.match == Match.NONE 17 | 18 | r = results[32] 19 | assert 222.10 == round(float(r.price), 2) 20 | assert Match.BULL_SIGNAL == r.match 21 | 22 | r = results[33] 23 | assert r.price is None 24 | assert Match.NONE == r.match 25 | 26 | r = results[34] 27 | assert r.price is None 28 | assert Match.NONE == r.match 29 | 30 | r = results[274] 31 | assert r.price is None 32 | assert Match.NONE == r.match 33 | 34 | r = results[277] 35 | assert 248.13 == round(float(r.price), 2) 36 | assert Match.BEAR_SIGNAL == r.match 37 | 38 | def test_bad_data(self, quotes_bad): 39 | r = indicators.get_marubozu(quotes_bad) 40 | assert 502 == len(r) 41 | 42 | def test_quotes_no(self, quotes): 43 | r = indicators.get_marubozu([]) 44 | assert 0 == len(r) 45 | 46 | r = indicators.get_marubozu(quotes[:1]) 47 | assert 1 == len(r) 48 | 49 | def test_condense(self, quotes): 50 | r = indicators.get_marubozu(quotes, 95).condense() 51 | assert 6 == len(r) 52 | 53 | def test_exceptions(self, quotes): 54 | from System import ArgumentOutOfRangeException 55 | 56 | with pytest.raises(ArgumentOutOfRangeException): 57 | indicators.get_marubozu(quotes, 79.9) 58 | 59 | with pytest.raises(ArgumentOutOfRangeException): 60 | indicators.get_marubozu(quotes, 100.1) 61 | -------------------------------------------------------------------------------- /tests/test_mfi.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestMFI: 7 | def test_standard(self, quotes): 8 | results = indicators.get_mfi(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 488 == len(list(filter(lambda x: x.mfi is not None, results))) 12 | 13 | r = results[439] 14 | assert 69.0622 == round(float(r.mfi), 4) 15 | 16 | r = results[501] 17 | assert 39.9494 == round(float(r.mfi), 4) 18 | 19 | def test_small_lookback(self, quotes): 20 | results = indicators.get_mfi(quotes, 4) 21 | 22 | assert 502 == len(results) 23 | assert 498 == len(list(filter(lambda x: x.mfi is not None, results))) 24 | 25 | r = results[31] 26 | assert 100 == round(float(r.mfi), 4) 27 | 28 | r = results[43] 29 | assert 0 == round(float(r.mfi), 4) 30 | 31 | def test_bad_data(self, quotes_bad): 32 | r = indicators.get_mfi(quotes_bad, 15) 33 | assert 502 == len(r) 34 | 35 | def test_quotes_no(self, quotes): 36 | r = indicators.get_mfi([]) 37 | assert 0 == len(r) 38 | 39 | r = indicators.get_mfi(quotes[:1]) 40 | assert 1 == len(r) 41 | 42 | def test_removed(self, quotes): 43 | results = indicators.get_mfi(quotes, 14).remove_warmup_periods() 44 | 45 | assert 502 - 14 == len(results) 46 | 47 | last = results.pop() 48 | assert 39.9494 == round(float(last.mfi), 4) 49 | 50 | def test_condense(self, quotes): 51 | results = indicators.get_mfi(quotes, 14).condense() 52 | 53 | assert 488 == len(results) 54 | 55 | last = results.pop() 56 | assert 39.9494 == round(float(last.mfi), 4) 57 | 58 | def test_exceptions(self, quotes): 59 | from System import ArgumentOutOfRangeException 60 | 61 | with pytest.raises(ArgumentOutOfRangeException): 62 | indicators.get_mfi(quotes, 1) 63 | -------------------------------------------------------------------------------- /tests/test_obv.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestOBV: 7 | def test_standard(self, quotes): 8 | results = indicators.get_obv(quotes) 9 | 10 | assert 502 == len(results) 11 | assert 502 == len(list(filter(lambda x: x.obv_sma is None, results))) 12 | 13 | r = results[249] 14 | assert 1780918888 == r.obv 15 | assert r.obv_sma is None 16 | 17 | r = results[501] 18 | assert 539843504 == r.obv 19 | assert r.obv_sma is None 20 | 21 | def test_with_sma(self, quotes): 22 | results = indicators.get_obv(quotes, 20) 23 | 24 | assert 502 == len(results) 25 | assert 482 == len(list(filter(lambda x: x.obv_sma, results))) 26 | 27 | r = results[501] 28 | assert 539843504 == r.obv 29 | assert 1016208844.40 == r.obv_sma 30 | 31 | # def test_convert_to_quotes(self, quotes): 32 | # new_quotes = indicators.get_obv(quotes).to_quotes() 33 | 34 | # assert 502 == len(new_quotes) 35 | 36 | # q = new_quotes[249] 37 | # assert 1780918888 == q.close 38 | 39 | # q = new_quotes[501] 40 | # assert 539843504 == q.close 41 | 42 | def test_bad_data(self, quotes_bad): 43 | r = indicators.get_obv(quotes_bad) 44 | assert 502 == len(r) 45 | 46 | def test_big_data(self, quotes_big): 47 | r = indicators.get_obv(quotes_big) 48 | assert 1246 == len(r) 49 | 50 | def test_quotes_no(self, quotes): 51 | r = indicators.get_obv([]) 52 | assert 0 == len(r) 53 | 54 | r = indicators.get_obv(quotes[:1]) 55 | assert 1 == len(r) 56 | 57 | def test_condense(self, quotes): 58 | results = indicators.get_obv(quotes).condense() 59 | 60 | assert 502 == len(results) 61 | 62 | last = results.pop() 63 | assert 539843504 == last.obv 64 | assert last.obv_sma is None 65 | 66 | def test_exceptions(self, quotes): 67 | from System import ArgumentOutOfRangeException 68 | 69 | with pytest.raises(ArgumentOutOfRangeException): 70 | indicators.get_obv(quotes, 0) 71 | -------------------------------------------------------------------------------- /tests/test_pmo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestPMO: 7 | def test_standard(self, quotes): 8 | results = indicators.get_pmo(quotes, 35, 20, 10) 9 | 10 | assert 502 == len(results) 11 | assert 448 == len(list(filter(lambda x: x.pmo is not None, results))) 12 | assert 439 == len(list(filter(lambda x: x.signal is not None, results))) 13 | 14 | r = results[92] 15 | assert 0.6159 == round(float(r.pmo), 4) 16 | assert 0.5582 == round(float(r.signal), 4) 17 | 18 | r = results[501] 19 | assert -2.7016 == round(float(r.pmo), 4) 20 | assert -2.3117 == round(float(r.signal), 4) 21 | 22 | def test_bad_data(self, quotes_bad): 23 | r = indicators.get_pmo(quotes_bad, 25, 15, 5) 24 | assert 502 == len(r) 25 | 26 | def test_quotes_no(self, quotes): 27 | r = indicators.get_pmo([]) 28 | assert 0 == len(r) 29 | 30 | r = indicators.get_pmo(quotes[:1]) 31 | assert 1 == len(r) 32 | 33 | def test_removed(self, quotes): 34 | results = indicators.get_pmo(quotes, 35, 20, 10).remove_warmup_periods() 35 | 36 | assert 502 - (35 + 20 + 250) == len(results) 37 | 38 | last = results.pop() 39 | assert -2.7016 == round(float(last.pmo), 4) 40 | assert -2.3117 == round(float(last.signal), 4) 41 | 42 | def test_condense(self, quotes): 43 | results = indicators.get_pmo(quotes, 35, 20, 10).condense() 44 | 45 | assert 448 == len(results) 46 | 47 | last = results.pop() 48 | assert -2.7016 == round(float(last.pmo), 4) 49 | assert -2.3117 == round(float(last.signal), 4) 50 | 51 | def test_exceptions(self, quotes): 52 | from System import ArgumentOutOfRangeException 53 | 54 | with pytest.raises(ArgumentOutOfRangeException): 55 | indicators.get_pmo(quotes, 1) 56 | 57 | with pytest.raises(ArgumentOutOfRangeException): 58 | indicators.get_pmo(quotes, 5, 0) 59 | 60 | with pytest.raises(ArgumentOutOfRangeException): 61 | indicators.get_pmo(quotes, 5, 5, 0) 62 | -------------------------------------------------------------------------------- /tests/test_rsi.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestRSI: 7 | def test_standard(self, quotes): 8 | results = indicators.get_rsi(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 488 == len(list(filter(lambda x: x.rsi is not None, results))) 12 | 13 | r = results[13] 14 | assert r.rsi is None 15 | 16 | r = results[14] 17 | assert 62.0541 == round(float(r.rsi), 4) 18 | 19 | r = results[249] 20 | assert 70.9368 == round(float(r.rsi), 4) 21 | 22 | r = results[501] 23 | assert 42.0773 == round(float(r.rsi), 4) 24 | 25 | def test_small_lookback(self, quotes): 26 | results = indicators.get_rsi(quotes, 1) 27 | 28 | assert 502 == len(results) 29 | assert 501 == len(list(filter(lambda x: x.rsi is not None, results))) 30 | 31 | r = results[28] 32 | assert 100 == round(float(r.rsi), 4) 33 | 34 | r = results[52] 35 | assert 0 == round(float(r.rsi), 4) 36 | 37 | # def test_convert_to_quotes(self, quotes): 38 | # results = indicators.get_rsi(quotes, 14).to_quotes() 39 | 40 | # assert 488 == len(results) 41 | 42 | # first = results[0] 43 | # assert 62.0541 == round(float(to_pydecimal(first.Close)), 4) 44 | 45 | # last = results.pop() 46 | # assert 42.0773 == round(float(to_pydecimal(last.Close)), 4) 47 | 48 | def test_bad_data(self, quotes_bad): 49 | r = indicators.get_rsi(quotes_bad, 20) 50 | 51 | assert 502 == len(r) 52 | 53 | def test_removed(self, quotes): 54 | results = indicators.get_rsi(quotes, 14).remove_warmup_periods() 55 | 56 | assert 502 - (10 * 14) == len(results) 57 | 58 | last = results.pop() 59 | assert 42.0773 == round(float(last.rsi), 4) 60 | 61 | def test_condense(self, quotes): 62 | results = indicators.get_rsi(quotes, 14).condense() 63 | 64 | assert 488 == len(results) 65 | 66 | last = results.pop() 67 | assert 42.0773 == round(float(last.rsi), 4) 68 | 69 | def test_exceptions(self, quotes): 70 | from System import ArgumentOutOfRangeException 71 | 72 | with pytest.raises(ArgumentOutOfRangeException): 73 | indicators.get_rsi(quotes, 0) 74 | -------------------------------------------------------------------------------- /tests/test_sma_analysis.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | from stock_indicators._cslib import CsDecimal 5 | 6 | 7 | class TestSMAExtended: 8 | def test_result_types(self, quotes): 9 | results = indicators.get_sma_analysis(quotes, 20) 10 | 11 | # Sample value. 12 | r = results[501] 13 | assert float == type(r._csdata.Sma) 14 | assert float == type(r._csdata.Mad) 15 | assert float == type(r._csdata.Mse) 16 | assert float == type(r._csdata.Mape) 17 | 18 | def test_extended(self, quotes): 19 | results = indicators.get_sma_analysis(quotes, 20) 20 | 21 | # proper quantities. 22 | # should always be the same number of results as there is quotes. 23 | assert 502 == len(results) 24 | assert 483 == len(list(filter(lambda x: x.sma is not None, results))) 25 | 26 | # Sample value. 27 | r = results[501] 28 | assert 251.8600 == round(float(r.sma), 4) 29 | assert 9.4500 == round(float(r.mad), 4) 30 | assert 119.2510 == round(float(r.mse), 4) 31 | assert 0.037637 == round(float(r.mape), 6) 32 | 33 | def test_bad_data(self, quotes_bad): 34 | results = indicators.get_sma_analysis(quotes_bad, 15) 35 | 36 | # Assertions 37 | assert 502 == len(results) 38 | 39 | def test_removed(self, quotes): 40 | results = indicators.get_sma_analysis(quotes, 20).remove_warmup_periods() 41 | 42 | # Assertions 43 | assert 502 - 19 == len(results) 44 | assert 251.8600 == round(float(results[len(results) - 1].sma), 4) 45 | 46 | def test_condense(self, quotes): 47 | results = indicators.get_sma_analysis(quotes, 20).condense() 48 | 49 | assert 483 == len(results) 50 | assert 251.8600 == round(float(results[len(results) - 1].sma), 4) 51 | 52 | def test_exceptions(self, quotes): 53 | from System import ArgumentOutOfRangeException 54 | 55 | with pytest.raises(ArgumentOutOfRangeException): 56 | indicators.get_sma_analysis(quotes, 0) 57 | -------------------------------------------------------------------------------- /tests/test_smma.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestSMMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_smma(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.smma is not None, results))) 12 | 13 | r = results[18] 14 | assert r.smma is None 15 | 16 | r = results[19] 17 | assert r.smma is not None 18 | 19 | r = results[19] 20 | assert 214.52500 == round(float(r.smma), 5) 21 | 22 | r = results[20] 23 | assert 214.55125 == round(float(r.smma), 5) 24 | 25 | r = results[21] 26 | assert 214.58319 == round(float(r.smma), 5) 27 | 28 | r = results[100] 29 | assert 225.78071 == round(float(r.smma), 5) 30 | 31 | r = results[501] 32 | assert 255.67462 == round(float(r.smma), 5) 33 | 34 | def test_bad_data(self, quotes_bad): 35 | r = indicators.get_smma(quotes_bad, 15) 36 | assert 502 == len(r) 37 | 38 | def test_quotes_no(self, quotes): 39 | r = indicators.get_smma([], 5) 40 | assert 0 == len(r) 41 | 42 | r = indicators.get_smma(quotes[:1], 5) 43 | assert 1 == len(r) 44 | 45 | def test_removed(self, quotes): 46 | results = indicators.get_smma(quotes, 20).remove_warmup_periods() 47 | 48 | assert 502 - (20 + 100) == len(results) 49 | 50 | last = results.pop() 51 | assert 255.67462 == round(float(last.smma), 5) 52 | 53 | def test_condense(self, quotes): 54 | results = indicators.get_smma(quotes, 20).condense() 55 | 56 | assert 483 == len(results) 57 | 58 | last = results.pop() 59 | assert 255.67462 == round(float(last.smma), 5) 60 | 61 | def test_exceptions(self, quotes, quotes_other, quotes_mismatch): 62 | from System import ArgumentOutOfRangeException 63 | 64 | with pytest.raises(ArgumentOutOfRangeException): 65 | indicators.get_smma(quotes, 0) 66 | -------------------------------------------------------------------------------- /tests/test_stc.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestSTC: 7 | def test_standard(self, quotes): 8 | cycle_periods = 9 9 | fast_periods = 12 10 | slow_periods = 26 11 | 12 | results = indicators.get_stc(quotes, cycle_periods, fast_periods, slow_periods) 13 | 14 | r = results[34] 15 | assert r.stc is None 16 | 17 | r = results[35] 18 | assert 100 == round(float(r.stc), 4) 19 | 20 | r = results[49] 21 | assert 0.8370 == round(float(r.stc), 4) 22 | 23 | r = results[249] 24 | assert 27.7340 == round(float(r.stc), 4) 25 | 26 | r = results.pop() 27 | assert 19.2544 == round(float(r.stc), 4) 28 | 29 | def test_bad_data(self, quotes_bad): 30 | r = indicators.get_stc(quotes_bad, 10, 23, 50) 31 | assert 502 == len(r) 32 | 33 | def test_quotes_no(self, quotes): 34 | r = indicators.get_stc([]) 35 | assert 0 == len(r) 36 | 37 | r = indicators.get_stc(quotes[:1]) 38 | assert 1 == len(r) 39 | 40 | def test_removed(self, quotes): 41 | cycle_periods = 9 42 | fast_periods = 12 43 | slow_periods = 26 44 | 45 | results = indicators.get_stc(quotes, cycle_periods, fast_periods, slow_periods) 46 | results = results.remove_warmup_periods() 47 | 48 | last = results.pop() 49 | assert 19.2544 == round(float(last.stc), 4) 50 | 51 | def test_condense(self, quotes): 52 | results = indicators.get_stc(quotes, 9, 12, 26).condense() 53 | 54 | assert 467 == len(results) 55 | 56 | last = results.pop() 57 | assert 19.2544 == round(float(last.stc), 4) 58 | 59 | def test_exceptions(self, quotes, quotes_other, quotes_mismatch): 60 | from System import ArgumentOutOfRangeException 61 | 62 | with pytest.raises(ArgumentOutOfRangeException): 63 | indicators.get_stc(quotes, 9, 0, 26) 64 | 65 | with pytest.raises(ArgumentOutOfRangeException): 66 | indicators.get_stc(quotes, 9, 12, 12) 67 | 68 | with pytest.raises(ArgumentOutOfRangeException): 69 | indicators.get_stc(quotes, -1, 12, 26) 70 | -------------------------------------------------------------------------------- /tests/test_t3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestT3: 7 | def test_standard(self, quotes): 8 | results = indicators.get_t3(quotes, 5, 0.7) 9 | 10 | assert 502 == len(results) 11 | assert 502 == len(list(filter(lambda x: x.t3 is not None, results))) 12 | 13 | r = results[5] 14 | assert 213.9654 == round(float(r.t3), 4) 15 | 16 | r = results[24] 17 | assert 215.9481 == round(float(r.t3), 4) 18 | 19 | r = results[44] 20 | assert 224.9412 == round(float(r.t3), 4) 21 | 22 | r = results[149] 23 | assert 235.8851 == round(float(r.t3), 4) 24 | 25 | r = results[249] 26 | assert 257.8735 == round(float(r.t3), 4) 27 | 28 | r = results[501] 29 | assert 238.9308 == round(float(r.t3), 4) 30 | 31 | def test_bad_data(self, quotes_bad): 32 | r = indicators.get_t3(quotes_bad) 33 | assert 502 == len(r) 34 | 35 | def test_quotes_no(self, quotes): 36 | r = indicators.get_t3([]) 37 | assert 0 == len(r) 38 | 39 | r = indicators.get_t3(quotes[:1]) 40 | assert 1 == len(r) 41 | 42 | def test_condense(self, quotes): 43 | results = indicators.get_t3(quotes, 5, 0.7).condense() 44 | 45 | assert 502 == len(results) 46 | 47 | last = results.pop() 48 | assert 238.9308 == round(float(last.t3), 4) 49 | 50 | def test_exceptions(self, quotes): 51 | from System import ArgumentOutOfRangeException 52 | 53 | with pytest.raises(ArgumentOutOfRangeException): 54 | indicators.get_t3(quotes, 0) 55 | 56 | with pytest.raises(ArgumentOutOfRangeException): 57 | indicators.get_t3(quotes, 25, 0) 58 | -------------------------------------------------------------------------------- /tests/test_tema.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestTripleEMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_tema(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.tema is not None, results))) 12 | 13 | r = results[67] 14 | assert 222.9562 == round(float(r.tema), 4) 15 | 16 | r = results[249] 17 | assert 258.6208 == round(float(r.tema), 4) 18 | 19 | r = results[501] 20 | assert 238.7690 == round(float(r.tema), 4) 21 | 22 | def test_bad_data(self, quotes_bad): 23 | r = indicators.get_tema(quotes_bad, 15) 24 | assert 502 == len(r) 25 | 26 | def test_removed(self, quotes): 27 | results = indicators.get_tema(quotes, 20).remove_warmup_periods() 28 | 29 | assert 502 - (3 * 20 + 100) == len(results) 30 | 31 | last = results.pop() 32 | assert 238.7690 == round(float(last.tema), 4) 33 | 34 | def test_condense(self, quotes): 35 | results = indicators.get_tema(quotes, 20).condense() 36 | 37 | assert 483 == len(results) 38 | 39 | last = results.pop() 40 | assert 238.7690 == round(float(last.tema), 4) 41 | 42 | def test_exceptions(self, quotes): 43 | from System import ArgumentOutOfRangeException 44 | 45 | with pytest.raises(ArgumentOutOfRangeException): 46 | indicators.get_tema(quotes, 0) 47 | -------------------------------------------------------------------------------- /tests/test_tr.py: -------------------------------------------------------------------------------- 1 | from stock_indicators import indicators 2 | 3 | 4 | class TestTr: 5 | def test_standard(self, quotes): 6 | results = indicators.get_tr(quotes) 7 | 8 | assert 502 == len(results) 9 | assert 501 == len(list(filter(lambda x: x.tr is not None, results))) 10 | 11 | r = results[0] 12 | assert r.tr is None 13 | 14 | r = results[1] 15 | assert 1.42 == round(float(r.tr), 2) 16 | 17 | r = results[12] 18 | assert 1.32 == round(float(r.tr), 2) 19 | 20 | r = results[13] 21 | assert 1.45 == round(float(r.tr), 2) 22 | 23 | r = results[24] 24 | assert 0.88 == round(float(r.tr), 2) 25 | 26 | r = results[249] 27 | assert 0.58 == round(float(r.tr), 2) 28 | 29 | r = results[501] 30 | assert 2.67 == round(float(r.tr), 2) 31 | 32 | def test_bad_data(self, quotes_bad): 33 | r = indicators.get_tr(quotes_bad) 34 | assert 502 == len(r) 35 | 36 | def test_quotes_no(self, quotes): 37 | r = indicators.get_tr([]) 38 | assert 0 == len(r) 39 | 40 | r = indicators.get_tr(quotes[:1]) 41 | assert 1 == len(r) 42 | 43 | def test_condense(self, quotes): 44 | results = indicators.get_tr(quotes).condense() 45 | 46 | assert 501 == len(results) 47 | 48 | last = results.pop() 49 | assert 2.67 == round(float(last.tr), 2) 50 | -------------------------------------------------------------------------------- /tests/test_trix.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from stock_indicators import indicators 3 | 4 | class TestTRIX: 5 | def test_standard(self, quotes): 6 | results = indicators.get_trix(quotes, 20, 5) 7 | 8 | assert 502 == len(results) 9 | assert 482 == len(list(filter(lambda x: x.ema3 is not None, results))) 10 | assert 482 == len(list(filter(lambda x: x.trix is not None, results))) 11 | assert 478 == len(list(filter(lambda x: x.signal is not None, results))) 12 | 13 | r = results[67] 14 | assert 221.7837 == round(float(r.ema3), 4) 15 | assert 0.050030 == round(float(r.trix), 6) 16 | assert 0.057064 == round(float(r.signal), 6) 17 | 18 | r = results[249] 19 | assert 249.4469 == round(float(r.ema3), 4) 20 | assert 0.121781 == round(float(r.trix), 6) 21 | assert 0.119769 == round(float(r.signal), 6) 22 | 23 | r = results[501] 24 | assert 263.3216 == round(float(r.ema3), 4) 25 | assert -0.230742 == round(float(r.trix), 6) 26 | assert -0.204536 == round(float(r.signal), 6) 27 | 28 | def test_bad_data(self, quotes_bad): 29 | r = indicators.get_trix(quotes_bad, 15, 2) 30 | assert 502 == len(r) 31 | 32 | def test_removed(self, quotes): 33 | results = indicators.get_trix(quotes, 20, 5).remove_warmup_periods() 34 | 35 | assert 502 - ((3 * 20) + 100) == len(results) 36 | 37 | last = results.pop() 38 | assert 263.3216 == round(float(last.ema3), 4) 39 | assert -0.230742 == round(float(last.trix), 6) 40 | assert -0.204536 == round(float(last.signal), 6) 41 | 42 | def test_condense(self, quotes): 43 | results = indicators.get_trix(quotes, 20, 5).condense() 44 | 45 | assert 482 == len(results) 46 | 47 | last = results.pop() 48 | assert 263.3216 == round(float(last.ema3), 4) 49 | assert -0.230742 == round(float(last.trix), 6) 50 | assert -0.204536 == round(float(last.signal), 6) 51 | 52 | def test_exceptions(self, quotes): 53 | from System import ArgumentOutOfRangeException 54 | with pytest.raises(ArgumentOutOfRangeException): 55 | indicators.get_trix(quotes, 0) 56 | -------------------------------------------------------------------------------- /tests/test_ulcer_index.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from stock_indicators import indicators 3 | 4 | class TestUlcerIndex: 5 | def test_standard(self, quotes): 6 | results = indicators.get_ulcer_index(quotes, 14) 7 | 8 | assert 502 == len(results) 9 | assert 489 == len(list(filter(lambda x: x.ui is not None, results))) 10 | 11 | r = results[501] 12 | assert 5.7255 == round(float(r.ui), 4) 13 | 14 | def test_bad_data(self, quotes_bad): 15 | r = indicators.get_ulcer_index(quotes_bad, 15) 16 | assert 502 == len(r) 17 | 18 | def test_no_data(self, quotes): 19 | r = indicators.get_ulcer_index([]) 20 | assert 0 == len(r) 21 | 22 | r = indicators.get_ulcer_index(quotes[:1]) 23 | assert 1 == len(r) 24 | 25 | def test_removed(self, quotes): 26 | results = indicators.get_ulcer_index(quotes, 14).remove_warmup_periods() 27 | 28 | assert 502 - 13 == len(results) 29 | 30 | last = results.pop() 31 | assert 5.7255 == round(float(last.ui), 4) 32 | 33 | def test_condense(self, quotes): 34 | results = indicators.get_ulcer_index(quotes, 14).condense() 35 | 36 | assert 489 == len(results) 37 | 38 | last = results.pop() 39 | assert 5.7255 == round(float(last.ui), 4) 40 | 41 | def test_exceptions(self, quotes): 42 | from System import ArgumentOutOfRangeException 43 | with pytest.raises(ArgumentOutOfRangeException): 44 | indicators.get_ulcer_index(quotes, 0) 45 | -------------------------------------------------------------------------------- /tests/test_ultimate.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestUltimate: 7 | def test_standard(self, quotes): 8 | results = indicators.get_ultimate(quotes, 7, 14, 28) 9 | 10 | assert 502 == len(results) 11 | assert 474 == len(list(filter(lambda x: x.ultimate is not None, results))) 12 | 13 | r = results[74] 14 | assert 51.7770 == round(float(r.ultimate), 4) 15 | 16 | r = results[249] 17 | assert 45.3121 == round(float(r.ultimate), 4) 18 | 19 | r = results[501] 20 | assert 49.5257 == round(float(r.ultimate), 4) 21 | 22 | def test_bad_data(self, quotes_bad): 23 | r = indicators.get_ultimate(quotes_bad, 1, 2, 3) 24 | assert 502 == len(r) 25 | 26 | def test_quotes_no(self, quotes): 27 | r = indicators.get_ultimate([]) 28 | assert 0 == len(r) 29 | 30 | r = indicators.get_ultimate(quotes[:1]) 31 | assert 1 == len(r) 32 | 33 | def test_removed(self, quotes): 34 | results = indicators.get_ultimate(quotes, 7, 14, 28).remove_warmup_periods() 35 | 36 | assert 502 - 28 == len(results) 37 | 38 | last = results.pop() 39 | assert 49.5257 == round(float(last.ultimate), 4) 40 | 41 | def test_condense(self, quotes): 42 | results = indicators.get_ultimate(quotes, 7, 14, 28).condense() 43 | 44 | assert 474 == len(results) 45 | 46 | last = results.pop() 47 | assert 49.5257 == round(float(last.ultimate), 4) 48 | 49 | def test_exceptions(self, quotes): 50 | from System import ArgumentOutOfRangeException 51 | 52 | with pytest.raises(ArgumentOutOfRangeException): 53 | indicators.get_ultimate(quotes, 0) 54 | 55 | with pytest.raises(ArgumentOutOfRangeException): 56 | indicators.get_ultimate(quotes, 7, 6) 57 | 58 | with pytest.raises(ArgumentOutOfRangeException): 59 | indicators.get_ultimate(quotes, 7, 14, 11) 60 | -------------------------------------------------------------------------------- /tests/test_vortex.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestVortex: 7 | def test_standard(self, quotes): 8 | results = indicators.get_vortex(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 488 == len(list(filter(lambda x: x.pvi is not None, results))) 12 | 13 | r = results[13] 14 | assert r.pvi is None 15 | assert r.nvi is None 16 | 17 | r = results[14] 18 | assert 1.0460 == round(float(r.pvi), 4) 19 | assert 0.8119 == round(float(r.nvi), 4) 20 | 21 | r = results[29] 22 | assert 1.1300 == round(float(r.pvi), 4) 23 | assert 0.7393 == round(float(r.nvi), 4) 24 | 25 | r = results[249] 26 | assert 1.1558 == round(float(r.pvi), 4) 27 | assert 0.6634 == round(float(r.nvi), 4) 28 | 29 | r = results[501] 30 | assert 0.8712 == round(float(r.pvi), 4) 31 | assert 1.1163 == round(float(r.nvi), 4) 32 | 33 | def test_bad_data(self, quotes_bad): 34 | r = indicators.get_vortex(quotes_bad, 20) 35 | assert 502 == len(r) 36 | 37 | def test_quotes_no(self, quotes): 38 | r = indicators.get_vortex([], 5) 39 | assert 0 == len(r) 40 | 41 | r = indicators.get_vortex(quotes[:1], 5) 42 | assert 1 == len(r) 43 | 44 | def test_removed(self, quotes): 45 | results = indicators.get_vortex(quotes, 14).remove_warmup_periods() 46 | 47 | assert 502 - 14 == len(results) 48 | 49 | last = results.pop() 50 | assert 0.8712 == round(float(last.pvi), 4) 51 | assert 1.1163 == round(float(last.nvi), 4) 52 | 53 | def test_condense(self, quotes): 54 | results = indicators.get_vortex(quotes, 14).condense() 55 | 56 | assert 488 == len(results) 57 | 58 | last = results.pop() 59 | assert 0.8712 == round(float(last.pvi), 4) 60 | assert 1.1163 == round(float(last.nvi), 4) 61 | 62 | def test_exceptions(self, quotes): 63 | from System import ArgumentOutOfRangeException 64 | 65 | with pytest.raises(ArgumentOutOfRangeException): 66 | indicators.get_vortex(quotes, 1) 67 | -------------------------------------------------------------------------------- /tests/test_vwma.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestVWMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_vwma(quotes, 10) 9 | 10 | assert 502 == len(results) 11 | assert 493 == len(list(filter(lambda x: x.vwma is not None, results))) 12 | 13 | r = results[8] 14 | assert r.vwma is None 15 | 16 | r = results[9] 17 | assert 213.981942 == round(float(r.vwma), 6) 18 | 19 | r = results[24] 20 | assert 215.899211 == round(float(r.vwma), 6) 21 | 22 | r = results[99] 23 | assert 226.302760 == round(float(r.vwma), 6) 24 | 25 | r = results[249] 26 | assert 257.053654 == round(float(r.vwma), 6) 27 | 28 | r = results[501] 29 | assert 242.101548 == round(float(r.vwma), 6) 30 | 31 | def test_bad_data(self, quotes): 32 | r = indicators.get_vwma(quotes, 15) 33 | assert 502 == len(r) 34 | 35 | def test_quotes_no(self, quotes): 36 | r = indicators.get_vwma([], 4) 37 | assert 0 == len(r) 38 | 39 | r = indicators.get_vwma(quotes[:1], 4) 40 | assert 1 == len(r) 41 | 42 | def test_removed(self, quotes): 43 | results = indicators.get_vwma(quotes, 10).remove_warmup_periods() 44 | 45 | assert 502 - 9 == len(results) 46 | 47 | last = results.pop() 48 | assert 242.101548 == round(float(last.vwma), 6) 49 | 50 | def test_condense(self, quotes): 51 | results = indicators.get_vwma(quotes, 10).condense() 52 | 53 | assert 493 == len(results) 54 | 55 | last = results.pop() 56 | assert 242.101548 == round(float(last.vwma), 6) 57 | 58 | def test_exceptions(self, quotes): 59 | from System import ArgumentOutOfRangeException 60 | 61 | with pytest.raises(ArgumentOutOfRangeException): 62 | indicators.get_vwma(quotes, 0) 63 | -------------------------------------------------------------------------------- /tests/test_williams_r.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestWilliamsR: 7 | def test_standard(self, quotes): 8 | results = indicators.get_williams_r(quotes, 14) 9 | 10 | assert 502 == len(results) 11 | assert 489 == len(list(filter(lambda x: x.williams_r is not None, results))) 12 | 13 | r = results[343] 14 | assert -19.8211 == round(float(r.williams_r), 4) 15 | 16 | r = results[501] 17 | assert -52.0121 == round(float(r.williams_r), 4) 18 | 19 | def test_bad_data(self, quotes_bad): 20 | r = indicators.get_williams_r(quotes_bad) 21 | assert 502 == len(r) 22 | 23 | def test_quotes_no(self, quotes): 24 | r = indicators.get_williams_r([]) 25 | assert 0 == len(r) 26 | 27 | r = indicators.get_williams_r(quotes[:1]) 28 | assert 1 == len(r) 29 | 30 | def test_removed(self, quotes): 31 | results = indicators.get_williams_r(quotes, 14).remove_warmup_periods() 32 | 33 | assert 502 - 13 == len(results) 34 | 35 | last = results.pop() 36 | assert -52.0121 == round(float(last.williams_r), 4) 37 | 38 | def test_condense(self, quotes): 39 | results = indicators.get_williams_r(quotes, 14).condense() 40 | 41 | assert 489 == len(results) 42 | 43 | last = results.pop() 44 | assert -52.0121 == round(float(last.williams_r), 4) 45 | 46 | def test_exceptions(self, quotes): 47 | from System import ArgumentOutOfRangeException 48 | 49 | with pytest.raises(ArgumentOutOfRangeException): 50 | indicators.get_williams_r(quotes, 0) 51 | -------------------------------------------------------------------------------- /tests/test_wma.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stock_indicators import indicators 4 | 5 | 6 | class TestWMA: 7 | def test_standard(self, quotes): 8 | results = indicators.get_wma(quotes, 20) 9 | 10 | assert 502 == len(results) 11 | assert 483 == len(list(filter(lambda x: x.wma is not None, results))) 12 | 13 | r = results[149] 14 | assert 235.5253 == round(float(r.wma), 4) 15 | 16 | r = results[501] 17 | assert 246.5110 == round(float(r.wma), 4) 18 | 19 | def test_bad_data(self, quotes_bad): 20 | r = indicators.get_wma(quotes_bad, 15) 21 | assert 502 == len(r) 22 | 23 | def test_quotes_no(self, quotes): 24 | r = indicators.get_wma([], 5) 25 | assert 0 == len(r) 26 | 27 | r = indicators.get_wma(quotes[:1], 5) 28 | assert 1 == len(r) 29 | 30 | def test_removed(self, quotes): 31 | results = indicators.get_wma(quotes, 20).remove_warmup_periods() 32 | 33 | assert 502 - 19 == len(results) 34 | 35 | last = results.pop() 36 | assert 246.5110 == round(float(last.wma), 4) 37 | 38 | def test_condense(self, quotes): 39 | results = indicators.get_wma(quotes, 20).condense() 40 | 41 | assert 483 == len(results) 42 | 43 | last = results.pop() 44 | assert 246.5110 == round(float(last.wma), 4) 45 | 46 | def test_exceptions(self, quotes): 47 | from System import ArgumentOutOfRangeException 48 | 49 | with pytest.raises(ArgumentOutOfRangeException): 50 | indicators.get_wma(quotes, 0) 51 | -------------------------------------------------------------------------------- /tests/utiltest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from datetime import datetime 4 | 5 | from stock_indicators.indicators.common.quote import Quote 6 | 7 | def load_quotes_from_json(json_path): 8 | base_dir = os.path.dirname(__file__) 9 | data_path = os.path.join(base_dir, json_path) 10 | quotes = [] 11 | with open(data_path, "r", encoding="utf-8") as st_json: 12 | for j in json.load(st_json): 13 | quotes.append(Quote(datetime.fromisoformat(j["Date"]), 14 | j["Open"], 15 | j["High"], 16 | j["Low"], 17 | j["Close"], 18 | j["Volume"])) 19 | 20 | return quotes 21 | --------------------------------------------------------------------------------