├── .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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
30 | {% for i in all %}
31 | - {{i.title}}
32 | {% endfor %}
33 |
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 |
42 | {% for i in all %}
43 | - {{i.title}}
44 | {% endfor%}
45 |
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 |
--------------------------------------------------------------------------------