diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 60711dc9..8f166db2 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -131,8 +131,11 @@ reviews: - "!*-lock.json" # NPM/PNPM lock files - "!packages.lock.json" # NuGet lock file - "!packages/" # NuGet packages cache + - "!_cslib/" # DLL library path - "!.jekyll-cache/" # Jekyll cache - "!.pytest_cache/" # Python test cache + - "!__pycache__/" # Python caches + - "!.venv/" # Python virtual env - "!.benchmarks/" # Benchmarks cache - "!.coverage/" # Code coverage reports - "!vendor/" # Ruby vendor and gem files @@ -165,7 +168,7 @@ reviews: enabled: true osvScanner: # Vulnerability package scanner enabled: true - pylint: # Python static analyzer + ruff: # Python linter and formatter enabled: true semgrep: # Security & code quality static analysis enabled: true @@ -225,9 +228,9 @@ reviews: enabled: false prismaLint: # Prisma schema linter enabled: false - regal: # Rego linter and language server + pylint: # Python static analyzer enabled: false - ruff: # Python linter and formatter + regal: # Rego linter and language server enabled: false rubocop: # Ruby linter and code formatter enabled: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 85748491..32609b39 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,36 +9,20 @@ */ { "name": "Stock Indicators for Python", - "image": "mcr.microsoft.com/devcontainers/python:3.12", - "forwardPorts": [], - "remoteUser": "vscode", + "image": "mcr.microsoft.com/devcontainers/python:3.13", "features": { - "ghcr.io/devcontainers/features/git:1": { - "version": "os-provided" - }, "ghcr.io/devcontainers/features/dotnet:2": { - "version": "lts" + "version": "10.0", + "additionalVersions": "9.0,8.0" }, "ghcr.io/devcontainers/features/node:1": { "version": "lts", "pnpmVersion": "none", "nvmVersion": "none" }, - "ghcr.io/devcontainers/features/github-cli:1": { - "installDirectlyFromGitHubRelease": true, - "version": "latest" - }, - "ghcr.io/devcontainers/features/azure-cli:1": { - "version": "latest" - }, + "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/ruby:1": { "version": "3.3" - }, - "ghcr.io/devcontainers-extra/features/isort:2": { - "version": "latest" - }, - "ghcr.io/devcontainers-extra/features/pylint:2": { - "version": "latest" } }, // Use 'settings' to set *default* container specific settings.json @@ -48,26 +32,39 @@ // container overrides only // otherwise use .vscode/settings.json "settings": { - "pylint.importStrategy": "fromEnvironment", - "python.defaultInterpreterPath": "/usr/local/bin/python" - }, - // required extensions - // for recommended, see .vscode/extensions.json - "extensions": [ - "donjayamanne.python-extension-pack", - "DavidAnson.vscode-markdownlint", - "EditorConfig.EditorConfig", - "ms-python.black-formatter", - "ms-python.debugpy", - "ms-python.isort", - "ms-python.python", - "ms-python.pylint", - "ms-python.vscode-pylance" - ] - } - }, - // Runs after the container is created - "postCreateCommand": "chmod +x .devcontainer/setup.sh && .devcontainer/setup.sh", - // Runs every time the container starts - "postStartCommand": "echo 'Container started'" -} + "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python", + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports.ruff": "explicit", + "source.fixAll.ruff": "explicit" + } + }, + // required extensions + // for recommended, see .vscode/extensions.json + "extensions": [ + "charliermarsh.ruff", + "DavidAnson.vscode-markdownlint", + "EditorConfig.EditorConfig", + "ms-python.debugpy", + "ms-python.python", + "ms-python.vscode-pylance" + ] + } + }, + "forwardPorts": [ + 4000 + ], + "portsAttributes": { + "4000": { + "label": "Doc Site (Jekyll)", + "onAutoForward": "notify" + } + }, + "remoteUser": "vscode", + "postCreateCommand": ".devcontainer/post-create.sh", + "postStartCommand": "echo 'Container started'" + } diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 00000000..03560b04 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +# Create virtual environment if it doesn't exist +if [ ! -d ".venv" ]; then + echo "Creating virtual environment..." + python -m venv .venv +fi + +# Activate virtual environment +source .venv/bin/activate + +# Upgrade pip +echo "Upgrading pip..." +python -m pip install --upgrade pip + +# Install core dependencies +echo "Installing core dependencies..." +pip install -r requirements.txt + +# Install test dependencies +echo "Installing test dependencies..." +pip install -r requirements-test.txt + +echo "✓ Dev container setup complete!" diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh deleted file mode 100755 index 7ad8e56e..00000000 --- a/.devcontainer/setup.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# install or upgrade pip -python -m ensurepip --upgrade - -# install core dependencies -pip install -r requirements.txt - -# install test dependencies -pip install -r requirements-test.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..dcd91906 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + linting: + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + dotnet-quality: ga + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: 3.13 + cache: "pip" + + - name: Create virtual environment + run: python -m venv .venv + + - name: Install dependencies + run: | + source .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install -e . + python -m pip install -r requirements-test.txt + + - name: Ruff lint + run: | + source .venv/bin/activate + ruff check . + + - name: Ruff format check + run: | + source .venv/bin/activate + ruff format --check . + + - name: Pyright + run: | + source .venv/bin/activate + pyright + + - name: Pytest + run: | + source .venv/bin/activate + pytest + + - name: pip-audit + run: | + source .venv/bin/activate + pip-audit -r requirements.txt -r requirements-test.txt diff --git a/.github/workflows/deploy-package.yml b/.github/workflows/deploy-package.yml index 2e35e2d8..22df88cb 100644 --- a/.github/workflows/deploy-package.yml +++ b/.github/workflows/deploy-package.yml @@ -19,15 +19,15 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-tags: true fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.13 - name: Build library run: | diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index b58c2f68..96de73a9 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/lint-pull-request.yml b/.github/workflows/lint-pull-request.yml index 56118d78..95c01a2b 100644 --- a/.github/workflows/lint-pull-request.yml +++ b/.github/workflows/lint-pull-request.yml @@ -1,26 +1,48 @@ -name: Pull request +name: Lint pull request on: pull_request_target: + branches: + - "main" + - "v[0-9]*" types: - opened - edited - unlabeled + - ready_for_review + +concurrency: + group: >- + ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true permissions: pull-requests: write jobs: - main: - name: lint PR title + title: runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} steps: - - uses: amannn/action-semantic-pull-request@v5 + - uses: amannn/action-semantic-pull-request@v6.1.1 id: lint_pr_title env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + plan subjectPattern: ^([A-Z]).+$ subjectPatternError: > The subject "**{subject}**" must start with an uppercase character. @@ -28,8 +50,9 @@ jobs: ignoreLabels: | bot dependencies + automated - - uses: marocchino/sticky-pull-request-comment@v2 + - uses: marocchino/sticky-pull-request-comment@v2.9.4 if: always() && (steps.lint_pr_title.outputs.error_message != null) with: header: pr-title-lint-error @@ -47,6 +70,7 @@ jobs: - `feat: Add API endpoint for market data` - `fix: Resolve WebSocket connection issues` + - `plan: Define technical implementation approach` - `chore: Update NuGet dependencies`
@@ -56,29 +80,33 @@ jobs: - `feat: Add API endpoint for market data` - `fix: Resolve WebSocket connection issues` + #### Planning & architecture + - `plan: Define technical implementation approach` + #### Code quality - `style: Format trading strategy classes` - `refactor: Restructure trading engine components` - `perf: Optimize trade order execution flow` - + #### Documentation & testing - `docs: Update API documentation` - `test: Add unit tests for sign-in flow` - + #### Infrastructure - - `build: Update .NET SDK version to 8.0` + - `build: Update .NET SDK version to 10.0` - `ci: Add workflow for performance testing` - `chore: Update NuGet dependencies` - + #### Other - `revert: Remove faulty market data provider` - - See [Conventional Commits](https://www.conventionalcommits.org) for more details. + + See [Conventional Commits](https://www.conventionalcommits.org) + for more details.
# Delete a previous comment when the issue has been resolved - if: ${{ steps.lint_pr_title.outputs.error_message == null }} - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@v2.9.4 with: header: pr-title-lint-error delete: true diff --git a/.github/workflows/test-code-coverage.yml b/.github/workflows/test-code-coverage.yml index adb0063b..bc860047 100644 --- a/.github/workflows/test-code-coverage.yml +++ b/.github/workflows/test-code-coverage.yml @@ -6,7 +6,7 @@ name: Test code coverage on: push: branches: ["main"] - pull_request_target: + pull_request: branches: ["*"] workflow_dispatch: @@ -27,20 +27,20 @@ jobs: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} steps: - - name: Checkout PR - uses: actions/checkout@v5 + - name: Checkout source + uses: actions/checkout@v6 with: # Explicitly checkout PR code ref: ${{ github.event.pull_request.head.sha }} - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.x + dotnet-version: 10.x dotnet-quality: ga - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.13 cache: "pip" diff --git a/.github/workflows/test-indicators-full.yml b/.github/workflows/test-indicators-full.yml index de8685d3..b802f433 100644 --- a/.github/workflows/test-indicators-full.yml +++ b/.github/workflows/test-indicators-full.yml @@ -23,26 +23,26 @@ jobs: # Primary testing on Ubuntu (free tier) - os: ubuntu-24.04-arm python-version: "3.8" - dotnet-version: "6.x" - - os: ubuntu-24.04-arm - python-version: "3.10" dotnet-version: "8.x" - os: ubuntu-24.04-arm - python-version: "3.12" + python-version: "3.10" dotnet-version: "9.x" - + - os: ubuntu-24.04-arm + python-version: "3.13" + dotnet-version: "10.x" + # Essential platform compatibility testing (reduced matrix) - os: windows-2025 python-version: "3.12" - dotnet-version: "9.x" + dotnet-version: "10.x" - os: macos-15 - python-version: "3.12" - dotnet-version: "9.x" - + python-version: "3.13" + dotnet-version: "10.x" + # Legacy support verification - os: ubuntu-22.04 python-version: "3.8" - dotnet-version: "6.x" + dotnet-version: "8.x" runs-on: ${{ matrix.os }} name: "Py${{ matrix.python-version }}/.NET${{ matrix.dotnet-version }} on ${{ matrix.os }}" @@ -54,16 +54,16 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: ${{ matrix.dotnet-version }} dotnet-quality: ga - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: "pip" diff --git a/.github/workflows/test-indicators.yml b/.github/workflows/test-indicators.yml index ca1341ff..06ead104 100644 --- a/.github/workflows/test-indicators.yml +++ b/.github/workflows/test-indicators.yml @@ -20,14 +20,14 @@ jobs: include: - # Primary testing on Ubuntu (free tier) with older configuration + # Primary testing on Ubuntu (free tier) with minimum supported .NET version - os: ubuntu-22.04 - dotnet-version: "6.x" + dotnet-version: "8.x" python-version: "3.8" # Primary testing on Ubuntu (free tier) with newer configuration - os: ubuntu-24.04-arm - dotnet-version: "9.x" + dotnet-version: "10.x" python-version: "3.13" post-summary: true @@ -46,10 +46,10 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check debug settings - if: ${{ matrix.post-summary }} == 'true' + if: ${{ matrix['post-summary'] == true }} shell: bash run: | echo "Checking for debug logging settings in package files..." @@ -64,13 +64,13 @@ jobs: echo "✓ No debug logging settings found." - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: ${{ matrix.dotnet-version }} dotnet-quality: ga - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: "pip" @@ -97,6 +97,6 @@ jobs: - name: Post test summary uses: test-summary/action@v2 - if: ${{ matrix.post-summary }} == 'true' + if: ${{ matrix['post-summary'] == true }} with: paths: test-results.xml diff --git a/.github/workflows/test-localization.yml b/.github/workflows/test-localization.yml index 832fad9a..29e2daa3 100644 --- a/.github/workflows/test-localization.yml +++ b/.github/workflows/test-localization.yml @@ -48,16 +48,17 @@ jobs: PYTHONIOENCODING: utf-8 steps: - - uses: actions/checkout@v5 + - name: Checkout source + uses: actions/checkout@v6 - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.x + dotnet-version: 10.x dotnet-quality: ga - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.13 cache: "pip" - name: Configure Linux locale diff --git a/.github/workflows/test-performance.yml b/.github/workflows/test-performance.yml index 76e651c4..176bad1d 100644 --- a/.github/workflows/test-performance.yml +++ b/.github/workflows/test-performance.yml @@ -21,20 +21,20 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.x + dotnet-version: 10.x dotnet-quality: ga - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.13 cache: "pip" - name: Setup GitVersion diff --git a/.github/workflows/test-website-a11y.yml b/.github/workflows/test-website-a11y.yml index 0f925299..ed7f6542 100644 --- a/.github/workflows/test-website-a11y.yml +++ b/.github/workflows/test-website-a11y.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/test-website-links.yml b/.github/workflows/test-website-links.yml index da87bd15..affe3233 100644 --- a/.github/workflows/test-website-links.yml +++ b/.github/workflows/test-website-links.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Ruby uses: ruby/setup-ruby@v1 diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 00000000..d11a7dc6 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,57 @@ +{ + "globs": [ + "**/*.md", + "**/*.markdown" + ], + "ignores": [ + "**/.pytest_cache/**", + "**/.coverage/**", + "**/.git/**", + "**/.github/**", + "**/.venv/**", + "**/_site/**", + "**/__pycache__/**", + "**/_cslib/**", + "**/node_modules/**", + "**/TestResults/**", + "**/*playwright*/**", + "**/vendor/**" + ], + "config": { + "default": true, + "MD003": { + "style": "atx" + }, + "MD004": { + "style": "dash" + }, + "MD007": { + "indent": 2 + }, + "MD013": false, + "MD025": false, + "MD029": { + "style": "ordered" + }, + "MD033": { + "allowed_elements": [ + "a", + "code", + "details", + "summary", + "sub", + "sup", + "kbd", + "abbr", + "img", + "br" + ] + }, + "MD046": { + "style": "fenced" + }, + "MD048": { + "style": "backtick" + } + } +} diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 3340ad6c..00000000 --- a/.pylintrc +++ /dev/null @@ -1,21 +0,0 @@ -[MASTER] -extension-pkg-allow-list= - clr - -ignore-long-lines=yes - -ignore-imports=yes - -disable= - C0103, # Variable name doesn't conform to snake_case naming style - C0114, # Missing module docstring - C0115, # Missing class docstring - C0116, # Missing function or method docstring - C0301, # Line too long - C0321, # More than one statement on a single line - C0413, # Import should be at the top of the file - C0415, # Import outside toplevel - E0401, # Import error - W0212, # Access to a protected member of a client class - R0903, # Too few public methods - R0913 # Too many arguments diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 61fe2e05..091bc9a5 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,13 +1,10 @@ { "recommendations": [ - "donjayamanne.python-extension-pack", + "charliermarsh.ruff", "DavidAnson.vscode-markdownlint", "EditorConfig.EditorConfig", - "ms-python.black-formatter", "ms-python.debugpy", - "ms-python.isort", "ms-python.python", - "ms-python.pylint", "ms-python.vscode-pylance" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 5df0aa08..42007812 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,12 +10,6 @@ "web_search" ], "github.copilot.chat.tools.memory.enabled": true, - "isort.args": [ - "--profile", - "black" - ], - "isort.check": true, - "isort.importStrategy": "fromEnvironment", // default: "useBundled" "markdownlint.config": { "default": true, // Enable all default rules "MD013": false, // Disable line length checking entirely @@ -29,18 +23,22 @@ }, "MD041": false // Allow content before first heading }, - "pylint.importStrategy": "fromEnvironment", // default: "useBundled" "python.testing.pytestArgs": [ "tests" ], + "python.defaultInterpreterPath": ".venv/bin/python", "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, - "[markdown]": { - "editor.defaultFormatter": "DavidAnson.vscode-markdownlint", - "files.trimTrailingWhitespace": true - }, "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, - "python.analysis.typeCheckingMode": "basic" + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports.ruff": "explicit", + "source.fixAll.ruff": "explicit" + }, + "[markdown]": { + "editor.defaultFormatter": "DavidAnson.vscode-markdownlint", + "files.trimTrailingWhitespace": true + } + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6b21e7fa..29f562d4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -35,12 +35,12 @@ { "label": "Install: Python Packages", "type": "shell", - "command": "pip install -r requirements.txt && pip install -r requirements-test.txt" + "command": "python -m pip install -r requirements.txt && python -m pip install -r requirements-test.txt" }, { "label": "Update: Python Packages", "type": "shell", - "command": "pip install -U -r requirements.txt && pip install -U -r requirements-test.txt" + "command": "python -m pip install -U -r requirements.txt && python -m pip install -U -r requirements-test.txt" }, { "label": "Update: Ruby Packages (docs)", @@ -65,8 +65,37 @@ { "label": "Build", "type": "shell", - "command": "pip install -r requirements.txt && pip install -r requirements-test.txt", - "group": "build" + "command": "python -m pip install -r requirements.txt && python -m pip install -r requirements-test.txt", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Lint: Code", + "type": "shell", + "command": "python -m ruff check . && python -m ruff format --check .", + "group": "test" + }, + { + "label": "Lint: Code (fix)", + "type": "shell", + "command": "python -m ruff check --fix . && python -m ruff format ." + }, + { + "label": "Lint: Markdown files", + "detail": "Run markdownlint over documentation", + "type": "shell", + "command": "echo y | npx markdownlint-cli2", + "group": "test", + "problemMatcher": "$markdownlint" + }, + { + "label": "Lint: Markdown files (fix)", + "detail": "Auto-fix markdown formatting issues", + "type": "shell", + "command": "echo y | npx markdownlint-cli2 --fix", + "problemMatcher": "$markdownlint" }, { "label": "Test: All", @@ -82,28 +111,31 @@ { "label": "Test: Unit (default)", "type": "shell", - "command": "pytest -vr A", - "group": "test" + "command": "python -m pytest -vr A", + "group": { + "kind": "test", + "isDefault": true + } }, { "label": "Test: Coverage", "type": "shell", - "command": "pytest --cov=stock_indicators --cov-report=term-missing" + "command": "python -m pytest --cov=stock_indicators --cov-report=term-missing" }, { "label": "Test: Performance", "type": "shell", - "command": "pytest -m performance" + "command": "python -m pytest -m performance" }, { "label": "Test: Performance (focus)", "type": "shell", - "command": "pytest -m \"performance and perf_focus\"" + "command": "python -m pytest -m \"performance and perf_focus\"" }, { "label": "Test: Localization", "type": "shell", - "command": "pytest -m localization -vr A" + "command": "python -m pytest -m localization -vr A" }, { "label": "Run: Doc Site with LiveReload", @@ -123,50 +155,32 @@ { "label": "Benchmark: JSON (prompt filename)", "type": "shell", - "command": "pytest -m performance --benchmark-json=.benchmarks/${input:benchJsonOut}", - "options": { - "cwd": "${workspaceFolder}" - } + "command": "python -m pytest -m performance --benchmark-json=.benchmarks/${input:benchJsonOut}" }, { "label": "Benchmark: Focus JSON (prompt filename)", "type": "shell", - "command": "pytest -m \"performance and perf_focus\" --benchmark-json=.benchmarks/${input:benchJsonOut}", - "options": { - "cwd": "${workspaceFolder}" - } + "command": "python -m pytest -m \"performance and perf_focus\" --benchmark-json=.benchmarks/${input:benchJsonOut}" }, { "label": "Benchmark: Autosave to .benchmarks", "type": "shell", - "command": "pytest -m performance --benchmark-autosave --benchmark-save-data", - "options": { - "cwd": "${workspaceFolder}" - } + "command": "python -m pytest -m performance --benchmark-autosave --benchmark-save-data" }, { "label": "Benchmark: Focus Autosave to .benchmarks", "type": "shell", - "command": "pytest -m \"performance and perf_focus\" --benchmark-autosave --benchmark-save-data", - "options": { - "cwd": "${workspaceFolder}" - } + "command": "python -m pytest -m \"performance and perf_focus\" --benchmark-autosave --benchmark-save-data" }, { "label": "Benchmark: List Saved Runs", "type": "shell", - "command": "pytest-benchmark list", - "options": { - "cwd": "${workspaceFolder}" - } + "command": "pytest-benchmark list" }, { "label": "Benchmark: Compare Saved Runs", "type": "shell", - "command": "pytest-benchmark compare ${input:benchBase} ${input:benchTarget}", - "options": { - "cwd": "${workspaceFolder}" - } + "command": "pytest-benchmark compare ${input:benchBase} ${input:benchTarget}" } ], "inputs": [ diff --git a/README.md b/README.md index 1b32e6cc..e2d33019 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Visit our project site for more information: ### Windows -1. Install .NET SDK (6.0 or newer): +1. Install .NET SDK (8.0 or newer): - Download from [Microsoft .NET Downloads](https://dotnet.microsoft.com/download) - - Or using winget: `winget install Microsoft.DotNet.SDK.6` + - Or using winget: `winget install Microsoft.DotNet.SDK.8` - Verify: `dotnet --info` 2. Install the package: @@ -35,7 +35,7 @@ Visit our project site for more information: ### macOS -1. Install .NET SDK (6.0 or newer): +1. Install .NET SDK (8.0 or newer): ```bash brew install dotnet-sdk diff --git a/benchmarks/conftest.py b/benchmarks/conftest.py index 28aaf09e..c6518ee3 100644 --- a/benchmarks/conftest.py +++ b/benchmarks/conftest.py @@ -35,7 +35,7 @@ def get_data_from_csv(filename): data_path = quotes_dir / f"{filename}.csv" logger.debug("Loading benchmark data from: %s", data_path) - with open(data_path, "r", newline="", encoding="utf-8") as csvfile: + with open(data_path, newline="", encoding="utf-8") as csvfile: reader = csv.reader(csvfile) data = list(reader) return data[1:] # skips the first row, those are headers @@ -62,9 +62,10 @@ def parse_date(date_str): @pytest.fixture(scope="session") -def raw_data(filename: str = 'Default'): +def raw_data(filename: str = "Default"): return get_data_from_csv(filename) + @pytest.fixture(scope="session") def quotes(days: int = 502): rows = get_data_from_csv("Default") diff --git a/benchmarks/test_benchmark_indicators.py b/benchmarks/test_benchmark_indicators.py index 5a18555e..ef3f2ca5 100644 --- a/benchmarks/test_benchmark_indicators.py +++ b/benchmarks/test_benchmark_indicators.py @@ -6,7 +6,6 @@ @pytest.mark.performance class TestPerformance: - def test_benchmark_adl(self, benchmark, quotes): benchmark(indicators.get_adl, quotes) @@ -262,17 +261,20 @@ def test_benchmark_converting_to_IndicatorResults(self, benchmark, quotes): from stock_indicators._cslib import CsIndicator from stock_indicators.indicators.common.enums import CandlePart from stock_indicators.indicators.common.quote import Quote - from stock_indicators.indicators.sma import SMAResults, SMAResult + from stock_indicators.indicators.sma import SMAResult, SMAResults candle_part: CandlePart = CandlePart.CLOSE lookback_periods = 12 - quotes = Quote.use(quotes * 1000, candle_part) # Error occurs if not assigned to local var. + quotes = Quote.use( + quotes * 1000, candle_part + ) # Error occurs if not assigned to local var. results = CsIndicator.GetSma(quotes, lookback_periods) benchmark(SMAResults, results, SMAResult) def test_benchmark_converting_to_CsDecimal(self, benchmark, raw_data): from stock_indicators._cstypes import Decimal as CsDecimal + raw_data = raw_data * 1000 def convert_to_quotes(rows): diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index b7b42e09..30c558ea 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -40,7 +40,7 @@ GEM ffi (>= 1.15.0) eventmachine (1.2.7) execjs (2.10.0) - faraday (2.13.4) + faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json logger @@ -286,7 +286,7 @@ GEM unicode-display_width (1.8.0) uri (1.0.3) wdm (0.2.0) - webrick (1.9.1) + webrick (1.9.2) PLATFORMS x64-mingw-ucrt diff --git a/docs/_indicators/AtrStop.md b/docs/_indicators/AtrStop.md index bcd6979e..a31a03ba 100644 --- a/docs/_indicators/AtrStop.md +++ b/docs/_indicators/AtrStop.md @@ -89,7 +89,6 @@ Created by Welles Wilder, the ATR Trailing Stop indicator attempts to determine ![chart for {{page.title}}]({{site.dotnet.charts}}/AtrStop.png) - ### Sources - [C# core]({{site.dotnet.src}}/a-d/AtrStop/AtrStop.Series.cs) diff --git a/docs/contributing.md b/docs/contributing.md index 5f9b786c..7e1b7a00 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -50,11 +50,9 @@ For new features, submit an issue with the `enhancement` label. ## Development Environment (Quick Setup) -- Recommended tools: Git, Node.js, npm, Docker, Python, Docker Desktop, Visual Studio Code (see `.vscode/extensions.json` for recommended extensions). -- This project supports [VS Code Dev Containers](https://code.visualstudio.com/docs/remote/containers) for a consistent development environment. Open the project in VS Code and select "Reopen in Container" (requires the Remote - Containers extension). -- You can test GitHub Actions workflows locally using [`act`](https://github.com/nektos/act), which is preinstalled in the devcontainer. Example: `act -l` to list workflows, `act` to run all workflows. - -For more details, see the [official documentation](https://github.com/nektos/act#readme) and the project README. +- Recommended tools: Git, Python 3.8+, Docker (optional), and Visual Studio Code (see `.vscode/extensions.json` for recommended extensions). +- This project supports [VS Code Dev Containers](https://code.visualstudio.com/docs/remote/containers) for a consistent development environment. Open the project in VS Code and select "Reopen in Container" (requires the Dev Containers extension). +- Local installs are plain `pip + venv`; no Poetry/Conda/Hatch required. --- @@ -68,10 +66,10 @@ For more details, see the [official documentation](https://github.com/nektos/act ### Windows Setup -1. Install .NET SDK (6.0 or newer): +1. Install .NET SDK (8.0 or newer): ```powershell - winget install Microsoft.DotNet.SDK.6 + winget install Microsoft.DotNet.SDK.8 # Or download from https://dotnet.microsoft.com/download ``` @@ -80,13 +78,15 @@ For more details, see the [official documentation](https://github.com/nektos/act ```powershell git clone https://github.com/facioquo/stock-indicators-python.git cd stock-indicators-python - pip install -r requirements.txt - pip install -r requirements-test.txt + python -m venv .venv + .venv\Scripts\python -m pip install --upgrade pip + .venv\Scripts\python -m pip install -e . + .venv\Scripts\python -m pip install -r requirements-test.txt ``` ### macOS Setup -1. Install .NET SDK (6.0 or newer): +1. Install .NET SDK (8.0 or newer): ```bash brew install dotnet-sdk @@ -97,13 +97,16 @@ For more details, see the [official documentation](https://github.com/nektos/act ```bash git clone https://github.com/facioquo/stock-indicators-python.git cd stock-indicators-python - pip install -r requirements.txt - pip install -r requirements-test.txt + python -m venv .venv + source .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install -e . + python -m pip install -r requirements-test.txt ``` ## Testing -- We use [pytest](https://docs.pytest.org) for testing. +- We use [Ruff](https://docs.astral.sh/ruff/) for linting/formatting, [Pyright](https://microsoft.github.io/pyright/) for type checking, and [pytest](https://docs.pytest.org) for tests. `pip-audit` runs in CI. - Review the `tests` folder for examples of unit tests. Just copy one of these. - New indicators should be tested against manually calculated, proven, accurate results. It is helpful to include your manual calculations spreadsheet in the appropriate indicator folder when [submitting changes](#submitting-changes). - Historical Stock Quotes are automatically added as pytest fixtures. The various `.csv` files in the `samples` folder are used in the unit tests. See `tests/conftest.py` for their usage. A `History.xlsx` Excel file is also included in the `samples` folder that contains the same information but separated by sheets. Use this for your manual calculations to ensure that it is correct. Do not commit changes to this Excel file. @@ -112,41 +115,38 @@ For more details, see the [official documentation](https://github.com/nektos/act ### Running Tests +Common commands (after activating `.venv`): + ```bash -# install core dependencies -pip install -r requirements.txt +# lint and format +python -m ruff check . +python -m ruff format --check . -# install dependencies -pip install -r requirements-test.txt +# type-check +python -m pyright # run standard unit tests -pytest +python -m pytest ``` -To run different types of tests, use the following commands: - -- **Normal unit tests** (default): - - ```bash - pytest - ``` +To run different types of tests: - **Non-standard `localization` tests**: ```bash - pytest -m "localization" + python -m pytest -m "localization" ``` - **Performance tests**: ```bash - pytest -m "performance" + python -m pytest -m "performance" ``` - **All tests** (not recommended): ```bash - pytest -m "" + python -m pytest -m "" ``` You can also use the `-svr A` arguments with pytest to get more detailed output: @@ -161,14 +161,14 @@ pytest -svr A ### Performance benchmarking -Running the commands below in your console will show performance data. You can find the latest results [here]({{site.baseurl}}/performance/). +Running the commands below in your console will produce [benchmark performance data](https://python.stockindicators.dev/performance/) that we include on our documentation site. ```bash # install dependencies -pip install -r requirements-test.txt +python -m pip install -r requirements-test.txt # run performance tests -pytest -m "performance" +python -m pytest -m "performance" ``` ## Documentation diff --git a/docs/pages/guide.md b/docs/pages/guide.md index 1cc644c1..53c9fe5f 100644 --- a/docs/pages/guide.md +++ b/docs/pages/guide.md @@ -31,11 +31,11 @@ layout: page > Install **Python** and the **.NET SDK**. Use the latest versions for better performance. | Installer | Min | Latest | Download | - |---| :---: | :---: | --- | - | Python | 3.8 | 3.12 | [@python.org](https://www.python.org/downloads/) | - | .NET SDK | 6.0 | 8.0 | [@microsoft.com](https://dotnet.microsoft.com/en-us/download) | + | --- | :---: | :---: | --- | + | Python | 3.8 | 3.13 | [@python.org](https://www.python.org/downloads/) | + | .NET SDK | 8.0 | 10.0 | [@microsoft.com](https://dotnet.microsoft.com/en-us/download) | - Note: we do not support the open source [Mono .NET Framework](https://www.mono-project.com). + Note: we do not support the open source [Mono .NET Framework](https://www.mono-project.com). Python 3.14+ is not yet supported due to `pythonnet` compatibility. 2. Install the **stock-indicators** Python package into your environment. @@ -127,7 +127,7 @@ from stock_indicators.indicators.common.quote import Quote [[source]](https://github.com/facioquo/stock-indicators-python/blob/main/stock_indicators/indicators/common/quote.py) | name | type | notes | -| -- |-- |-- | +| ---- | ---- | ----- | | date | [`datetime.datetime`](https://docs.python.org/3.8/library/datetime.html#datetime.datetime) | Date | | open | [`decimal.Decimal`](https://docs.python.org/3.8/library/decimal.html?highlight=decimal#decimal.Decimal), Optional | Open price | | high | [`decimal.Decimal`](https://docs.python.org/3.8/library/decimal.html?highlight=decimal#decimal.Decimal), Optional | High price | @@ -289,7 +289,7 @@ from stock_indicators.indicators.common.enums import Match ``` | type | description | -|-- |:-- | +| ---- | :---------- | | `Match.BULL_CONFIRMED` | Confirmation of a prior bull Match | | `Match.BULL_SIGNAL` | Matching bullish pattern | | `Match.BULL_BASIS` | Bars supporting a bullish Match | @@ -303,23 +303,23 @@ from stock_indicators.indicators.common.enums import Match The `CandleProperties` class is an extended version of `Quote`, and contains additional calculated properties. -| name | type | notes | -| -- |-- |-- | -| `date` | datetime | Date | -| `open` | Decimal | Open price | -| `high` | Decimal | High price | -| `low` | Decimal | Low price | -| `close` | Decimal | Close price | -| `volume` | Decimal | Volume | -| `size` | Decimal, Optional | `high-low` | -| `body` | Decimal, Optional | `|open-close|` | -| `upper_wick` | Decimal, Optional | Upper wick size | -| `lower_wick` | Decimal, Optional | Lower wick size | -| `body_pct` | float, Optional | `body/size` | -| `upper_wick_pct` | float, Optional | `upper_wick/size` | -| `lower_wick_pct` | float, Optional | `lower_wick/size` | -| `is_bullish` | bool | `close>open` direction | -| `is_bearish` | bool | `closeopen` direction | +| `is_bearish` | bool | `close=3.0.0", + "pythonnet>=3.0.5", "typing_extensions>=4.4.0" ] keywords = [ @@ -61,3 +61,56 @@ exclude = ["tests*", "benchmarks*", "test_data*", "docs*"] # can be empty if no extra settings are needed, presence enables setuptools_scm [tool.setuptools_scm] local_scheme = "no-local-version" + +[tool.ruff] +target-version = "py38" +line-length = 88 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # import sorting + "B", # flake8-bugbear + "UP", # pyupgrade + "C4", # flake8-comprehensions + "TID",# flake8-tidy-imports + "PT", # flake8-pytest-style + "RUF" # Ruff-specific rules +] +ignore = [ + "B905", # allow zip without strict length check on py38 support floor + "E501" # allow longer docstrings/comments +] + +[tool.ruff.lint.isort] +known-first-party = ["stock_indicators"] + +[tool.ruff.lint.per-file-ignores] +"stock_indicators/__init__.py" = ["F401", "F403"] +"stock_indicators/_cslib/__init__.py" = ["F401"] +"stock_indicators/_cstypes/__init__.py" = ["F401"] +"stock_indicators/indicators/__init__.py" = ["F401"] + +[tool.ruff.format] +quote-style = "double" + +[tool.pyright] +include = ["stock_indicators", "tests"] +pythonVersion = "3.8" +typeCheckingMode = "standard" +useLibraryCodeForTypes = true +reportMissingImports = "none" +reportMissingModuleSource = "none" +reportMissingTypeStubs = "none" +stubPath = "typings" +exclude = ["typings"] +reportInvalidTypeForm = "none" +reportAttributeAccessIssue = "none" +reportOperatorIssue = "none" +reportReturnType = "none" +reportGeneralTypeIssues = "none" +reportArgumentType = "none" +reportInconsistentOverload = "none" +reportIndexIssue = "none" +reportCallIssue = "none" diff --git a/requirements-test.txt b/requirements-test.txt index 48573f14..90f51398 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -3,3 +3,6 @@ pytest pytest-cov pytest-benchmark backports.zoneinfo; python_version < '3.9' +ruff +pyright +pip-audit diff --git a/requirements.txt b/requirements.txt index 9045a0c0..91659dac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ # _cslib -pythonnet>=3.0.0 +pythonnet>=3.0.5 typing_extensions>=4.4.0 diff --git a/stock_indicators/__init__.py b/stock_indicators/__init__.py index f2d5deb1..c7561952 100644 --- a/stock_indicators/__init__.py +++ b/stock_indicators/__init__.py @@ -15,4 +15,11 @@ """ from stock_indicators import indicators +from stock_indicators.exceptions import ( + IndicatorCalculationError, + StockIndicatorsError, + StockIndicatorsInitializationError, + TypeConversionError, + ValidationError, +) from stock_indicators.indicators.common import * diff --git a/stock_indicators/_cslib/__init__.py b/stock_indicators/_cslib/__init__.py index defaa45b..90d3b8b0 100644 --- a/stock_indicators/_cslib/__init__.py +++ b/stock_indicators/_cslib/__init__.py @@ -1,96 +1,158 @@ """ Skender.Stock.Indicators ~~~~~~~~~~~~~~~~~~~~~~~~ +# pylint: disable=duplicate-code # Property patterns are expected to repeat -This module loads `Skender.Stock.Indicators.dll`(v2.6.1), which is a compiled library +This module loads `Skender.Stock.Indicators.dll`(v2.7.1), which is a compiled library package from , written in C#. -The target framework of dll is `.NET 6.0`. +The target framework of dll is `.NET 8.0`. """ import logging import platform from pathlib import Path +from typing import Any from pythonnet import load -# Setup logging +from stock_indicators.exceptions import StockIndicatorsInitializationError from stock_indicators.logging_config import configure_logging +# Setup logging configure_logging(debug=False) # Set to True if you need debug this module logger = logging.getLogger(__name__) -try: - # Load CLR - load(runtime="coreclr") - import clr - logger.debug("CLR loaded successfully on %s", platform.system()) - # Get absolute paths - base_path = Path(__file__).parent.resolve() - dll_path = base_path / "lib" / "Skender.Stock.Indicators.dll" - - # Set assembly resolve path - from System import IO, AppDomain +def _initialize_clr() -> Any: + """Initialize the CLR runtime.""" + try: + load(runtime="coreclr") + import clr as clr_module + + logger.debug("CLR loaded successfully on %s", platform.system()) + return clr_module + except Exception as e: + init_error_msg = ( + "Failed to load .NET CLR runtime.\n" + "Please ensure .NET 8.0+ is installed: https://dotnet.microsoft.com/download\n" + f"Platform: {platform.system()}\n" + f"Error: {e!s}" + ) + raise StockIndicatorsInitializationError(init_error_msg) from e + + +def _setup_assembly_probing(assembly_dll_path: Path) -> None: + """Setup assembly probing path for .NET dependency resolution.""" + try: + from System import IO, AppDomain + + current_domain = AppDomain.CurrentDomain + assembly_path = IO.Path.GetDirectoryName(str(assembly_dll_path)) + current_domain.SetData("PROBING_PATH", assembly_path) + logger.debug("Set assembly probing path to: %s", assembly_path) + except Exception as e: # pylint: disable=broad-exception-caught + # Broad exception catch is necessary for C# interop + logger.warning("Failed to set assembly probing path: %s", e) - current_domain = AppDomain.CurrentDomain - assembly_path = IO.Path.GetDirectoryName(str(dll_path)) - current_domain.SetData("PROBING_PATH", assembly_path) - logger.debug("Set assembly probing path to: %s", assembly_path) +def _load_assembly(assembly_dll_path: Path): + """Load the Stock Indicators assembly.""" try: - # Load the assembly first from System.Reflection import Assembly - logger.debug("Loading assembly from: %s", dll_path) - assembly = Assembly.LoadFile(str(dll_path)) - logger.debug("Assembly loaded: %s", assembly.FullName) + if not assembly_dll_path.exists(): + raise FileNotFoundError(f"Assembly not found at: {assembly_dll_path}") + + logger.debug("Loading assembly from: %s", assembly_dll_path) + loaded_assembly = Assembly.LoadFile(str(assembly_dll_path)) + logger.debug("Assembly loaded: %s", loaded_assembly.FullName) - # Add reference after successful load - clr.AddReference(assembly.FullName) # pylint: disable=no-member - logger.debug("Assembly reference added") + return loaded_assembly + except Exception as e: + load_error_msg = ( + f"Failed to load Stock Indicators assembly from: {assembly_dll_path}\n" + "Please ensure the .NET assembly is present and compatible.\n" + f"Error: {e!s}" + ) + raise StockIndicatorsInitializationError(load_error_msg) from e - except Exception as asm_error: - logger.error("Error loading assembly: %s", str(asm_error)) - if hasattr(asm_error, "LoaderExceptions"): - for ex in asm_error.LoaderExceptions: - logger.error("Loader exception: %s", str(ex)) - raise + +def _add_assembly_reference(loaded_assembly, clr_module) -> None: + """Add reference to the loaded assembly.""" + try: + clr_module.AddReference(loaded_assembly.FullName) # pylint: disable=no-member + logger.debug("Assembly reference added successfully") + except Exception as e: + ref_error_msg = ( + f"Failed to add reference to assembly: {loaded_assembly.FullName}\n" + f"Error: {e!s}" + ) + raise StockIndicatorsInitializationError(ref_error_msg) from e + + +try: + # Initialize CLR + clr = _initialize_clr() + + # Get assembly path + base_path = Path(__file__).parent.resolve() + dll_path = base_path / "lib" / "Skender.Stock.Indicators.dll" + + # Setup assembly probing + _setup_assembly_probing(dll_path) + + # Load assembly + assembly = _load_assembly(dll_path) + + # Add assembly reference + _add_assembly_reference(assembly, clr) except Exception as e: - logger.error("Detailed error information: %s", str(e)) - if hasattr(e, "__cause__") and e.__cause__ is not None: - logger.error("Caused by: %s", str(e.__cause__)) + # Re-raise our custom exception or wrap unexpected errors + if not isinstance(e, StockIndicatorsInitializationError): + error_msg = ( + "Stock Indicators initialization failed due to unexpected error.\n" + "Please ensure .NET 8.0+ is installed: https://dotnet.microsoft.com/download\n" + f"Error: {e!s}" + ) + raise StockIndicatorsInitializationError(error_msg) from e + raise + +# Library modules (common) - Import after successful initialization +try: + from Skender.Stock.Indicators import BetaType as CsBetaType + from Skender.Stock.Indicators import CandlePart as CsCandlePart + from Skender.Stock.Indicators import CandleProperties as CsCandleProperties + from Skender.Stock.Indicators import ChandelierType as CsChandelierType + from Skender.Stock.Indicators import EndType as CsEndType + from Skender.Stock.Indicators import Indicator as CsIndicator + from Skender.Stock.Indicators import Match as CsMatch + from Skender.Stock.Indicators import MaType as CsMaType + from Skender.Stock.Indicators import PeriodSize as CsPeriodSize + from Skender.Stock.Indicators import PivotPointType as CsPivotPointType + from Skender.Stock.Indicators import PivotTrend as CsPivotTrend + from Skender.Stock.Indicators import Quote as CsQuote + from Skender.Stock.Indicators import QuoteUtility as CsQuoteUtility + from Skender.Stock.Indicators import ResultBase as CsResultBase + from Skender.Stock.Indicators import ResultUtility as CsResultUtility + + # Built-in System types + from System import DateTime as CsDateTime + from System import Decimal as CsDecimal + from System import Enum as CsEnum + from System.Collections.Generic import IEnumerable as CsIEnumerable + from System.Collections.Generic import List as CsList + from System.Globalization import CultureInfo as CsCultureInfo + from System.Globalization import NumberStyles as CsNumberStyles + + logger.info("Stock Indicators library initialized successfully") + +except ImportError as e: error_msg = ( - "Stock Indicators initialization failed.\n" - "Please ensure .NET 6.0+ is installed: https://dotnet.microsoft.com/download\n" - f"Error: {str(e)}" + "Failed to import Stock Indicators types after successful assembly loading.\n" + "This may indicate a version mismatch or missing dependencies.\n" + f"Error: {e!s}" ) - raise ImportError(error_msg) from e - -# Library modules (common) -from Skender.Stock.Indicators import BetaType as CsBetaType -from Skender.Stock.Indicators import CandlePart as CsCandlePart -from Skender.Stock.Indicators import CandleProperties as CsCandleProperties -from Skender.Stock.Indicators import ChandelierType as CsChandelierType -from Skender.Stock.Indicators import EndType as CsEndType -from Skender.Stock.Indicators import Indicator as CsIndicator -from Skender.Stock.Indicators import Match as CsMatch -from Skender.Stock.Indicators import MaType as CsMaType -from Skender.Stock.Indicators import PeriodSize as CsPeriodSize -from Skender.Stock.Indicators import PivotPointType as CsPivotPointType -from Skender.Stock.Indicators import PivotTrend as CsPivotTrend -from Skender.Stock.Indicators import Quote as CsQuote -from Skender.Stock.Indicators import QuoteUtility as CsQuoteUtility -from Skender.Stock.Indicators import ResultBase as CsResultBase -from Skender.Stock.Indicators import ResultUtility as CsResultUtility - -# Built-in -from System import DateTime as CsDateTime -from System import Decimal as CsDecimal -from System import Enum as CsEnum -from System.Collections.Generic import IEnumerable as CsIEnumerable -from System.Collections.Generic import List as CsList -from System.Globalization import CultureInfo as CsCultureInfo -from System.Globalization import NumberStyles as CsNumberStyles + raise StockIndicatorsInitializationError(error_msg) from e diff --git a/stock_indicators/_cslib/lib/Skender.Stock.Indicators.dll b/stock_indicators/_cslib/lib/Skender.Stock.Indicators.dll index 5351569d..454e6c60 100644 Binary files a/stock_indicators/_cslib/lib/Skender.Stock.Indicators.dll and b/stock_indicators/_cslib/lib/Skender.Stock.Indicators.dll differ diff --git a/stock_indicators/_cslib/lib/Skender.Stock.Indicators.xml b/stock_indicators/_cslib/lib/Skender.Stock.Indicators.xml new file mode 100644 index 00000000..23d4382f --- /dev/null +++ b/stock_indicators/_cslib/lib/Skender.Stock.Indicators.xml @@ -0,0 +1,2742 @@ + + + + Skender.Stock.Indicators + + + + + Accumulation/Distribution Line (ADL) is a rolling accumulation of Chaikin Money Flow Volume. + + See + documentation + for more information. + + + Configurable Quote type. See Guide for more information. + Historical price quotes. + Optional. Number of periods in the moving average of ADL. + Time series of ADL values. + Invalid parameter value provided. + + + + + Directional Movement Index (DMI) and Average Directional Movement Index (ADX) is a measure of price directional movement. + It includes upward and downward indicators, and is often used to measure strength of trend. + + See + documentation + for more information. + + + Configurable Quote type. See Guide for more information. + Historical price quotes. + Number of periods in the lookback window. + Time series of ADX and Plus/Minus Directional values. + Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + + Indicator + results to evaluate. + Time + series of results, pruned. + + + + + Williams Alligator is an indicator that transposes multiple moving averages, + showing chart patterns that creator Bill Williams compared to an alligator's + feeding habits when describing market movement. + + See + documentation + for more information. + + + Configurable Quote type. See Guide for more information. + Historical price quotes. + Lookback periods for the Jaw line. + Offset periods for the Jaw line. + Lookback periods for the Teeth line. + Offset periods for the Teeth line. + Lookback periods for the Lips line. + Offset periods for the Lips line. + Time series of Alligator values. + Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + + Indicator results to evaluate. + Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Arnaud Legoux Moving Average (ALMA) is a Gaussian distribution + weighted moving average of price over a lookback window. + + See + documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Adjusts smoothness versus responsiveness. +Defines the width of the Gaussian normal distribution. +Time series of ALMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Aroon is a simple oscillator view of how long the new high or low price occured over a lookback window. + + See + documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Aroon Up/Down and Oscillator values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Average True Range (ATR) is a measure of volatility that captures gaps and limits between periods. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of ATR values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + ATR Trailing Stop attempts to determine the primary trend of prices by using + Average True Range (ATR) band thresholds. It can indicate a buy/sell signal or a + trailing stop when the trend changes. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for ATR. +Multiplier sets the ATR band width. +Sets basis for stop offsets (Close or High/Low). +Time series of ATR Trailing Stop values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Awesome Oscillator (aka Super AO) is a measure of the gap between a fast and slow period modified moving average. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the Fast moving average. +Number of periods in the Slow moving average. +Time series of Awesome Oscillator values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + A simple quote transform. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +The OHLCV element or simply calculated value type. +Time series of Basic Quote values. +Invalid candle part provided. + + + + + Beta shows how strongly one stock responds to systemic volatility of the entire market. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes for Evaluation. +Historical price quotes for Market. +Number of periods in the lookback window. +Type of Beta to calculate. +Time series of Beta values. +Invalid parameter value provided. +Invalid quotes provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Bollinger Bands® depict volatility as standard deviation boundary lines from a moving average of price. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Width of bands. Number of Standard Deviations from the moving average. +Time series of Bollinger Band and %B values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Balance of Power (aka Balance of Market Power) is a momentum oscillator that depicts the strength of buying and selling pressure. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for smoothing. +Time series of BOP values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Commodity Channel Index (CCI) is an oscillator depicting deviation from typical price range, often used to identify cyclical trends. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of CCI values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Chaikin Oscillator is the difference between fast and slow Exponential Moving Averages (EMA) of the Accumulation/Distribution Line (ADL). + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the ADL fast EMA. +Number of periods for the ADL slow EMA. +Time series of Chaikin Oscillator, Money Flow Volume, and ADL values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Chandelier Exit is typically used for stop-loss and can be computed for both long or short types. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Multiplier. +Short or Long variant selection. +Time series of Chandelier Exit values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Choppiness Index (CHOP) measures the trendiness or choppiness over N lookback periods + on a scale of 0 to 100. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of CHOP values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Chaikin Money Flow (CMF) is the simple moving average of Money Flow Volume (MFV). + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the MFV moving average. +Time series of Chaikin Money Flow and MFV values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + The Chande Momentum Oscillator is a momentum indicator depicting the weighted percent of higher prices in financial markets. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of CMO values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + ConnorsRSI is a composite oscillator that incorporates RSI, winning/losing streaks, and percentile gain metrics on scale of 0 to 100. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the RSI. +Number of periods for streak RSI. +Number of periods for the percentile ranking. +Time series of ConnorsRSI, RSI, Streak RSI, and Percent Rank values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Correlation Coefficient between two quote histories, based on price. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes A for comparison. +Historical price quotes B for comparison. +Number of periods in the lookback window. + + Time series of Correlation Coefficient values. + R², Variance, and Covariance are also included. + +Invalid parameter value provided. +Invalid quotes provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Double Exponential Moving Average (DEMA) of the price. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Double EMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Doji is a single candlestick pattern where open and close price are virtually identical, representing market indecision. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Optional. Maximum absolute percent difference in open and close price. +Time series of Doji values. +Invalid parameter value provided. + + + + + Doji is a single candlestick pattern where open and close price are virtually identical, representing market indecision. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Optional. Maximum absolute percent difference in open and close price. +Time series of Doji values. +Invalid parameter value provided. + + + + + Donchian Channels, also called Price Channels, are derived from highest High and lowest Low values over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Donchian Channel values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Detrended Price Oscillator (DPO) depicts the difference between price and an offset simple moving average. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of DPO values. +Invalid parameter value provided. + + + + + McGinley Dynamic is a more responsive variant of exponential moving average. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Optional. Range adjustment factor. +Time series of Dynamic values. +Invalid parameter value provided. + + + + + The Elder-ray Index depicts buying and selling pressure, also known as Bull and Bear Power. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the EMA. +Time series of Elder-ray Index values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Exponential Moving Average (EMA) of price or any other specified OHLCV element. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of EMA values. +Invalid parameter value provided. + + + + + Extablish a streaming base for Exponential Moving Average (EMA). + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +EMA base that you can add Quotes to with the .Add(quote) method. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Endpoint Moving Average (EPMA), also known as Least Squares Moving Average (LSMA), plots the projected last point of a linear regression lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Endpoint Moving Average values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Fractal Chaos Bands outline high and low price channels to depict broad less-chaotic price movements. FCB is a channelized depiction of Williams Fractals. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of span periods in the evaluation window. +Time series of Fractal Chaos Band and Oscillator values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Ehlers Fisher Transform converts prices into a Gaussian normal distribution. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Fisher Transform values. +Invalid parameter value provided. + + + + + The Force Index depicts volume-based buying and selling pressure. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the EMA of Force Index. +Time series of Force Index values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Williams Fractal is a retrospective price pattern that identifies a central high or low point over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of span periods to the left and right of the evaluation period. +Determines use of Close or High/Low wicks for points. +Time series of Williams Fractal Bull/Bear values. +Invalid parameter value provided. + + + + + Williams Fractal is a retrospective price pattern that identifies a central high or low point over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of span periods to the left of the evaluation period. +Number of span periods to the right of the evaluation period. +Determines use of Close or High/Low wicks for points. +Time series of Williams Fractal Bull/Bear values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + + Gator Oscillator is an expanded view of Williams Alligator. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Time series of Gator values. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Heikin-Ashi is a modified candlestick pattern that uses prior day for smoothing. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Time series of Heikin-Ashi candlestick values. + + + + + Hull Moving Average (HMA) is a modified weighted average of price over N lookback periods that reduces lag. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of HMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Hilbert Transform Instantaneous Trendline (HTL) is a 5-period trendline of high/low price that uses signal processing to reduce noise. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Time series of HTL values and smoothed price. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Hurst Exponent is a measure of randomness, trending, and mean-reverting tendencies of incremental return values. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of lookback periods. +Time series of Hurst Exponent values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the Tenkan-sen midpoint evaluation. +Number of periods in the shorter Kijun-sen midpoint evaluation. This value is also used to offset Senkou and Chinkou spans. +Number of periods in the longer Senkou leading span B midpoint evaluation. +Time series of Ichimoku Cloud values. +Invalid parameter value provided. + + + + + Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the Tenkan-sen midpoint evaluation. +Number of periods in the shorter Kijun-sen midpoint evaluation. +Number of periods in the longer Senkou leading span B midpoint evaluation. +Number of periods to displace the Senkou and Chikou Spans. +Time series of Ichimoku Cloud values. +Invalid parameter value provided. + + + + + Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators that depict support and resistance, momentum, and trend direction. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the Tenkan-sen midpoint evaluation. +Number of periods in the shorter Kijun-sen midpoint evaluation. +Number of periods in the longer Senkou leading span B midpoint evaluation. +Number of periods to displace the Senkou Spans. +Number of periods in displace the Chikou Span. +Time series of Ichimoku Cloud values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + + Kaufman’s Adaptive Moving Average (KAMA) is an volatility adaptive moving average of price over configurable lookback periods. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of Efficiency Ratio (volatility) periods. +Number of periods in the Fast EMA. +Number of periods in the Slow EMA. +Time series of KAMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Keltner Channels are based on an EMA centerline and ATR band widths. See also STARC Bands for an SMA centerline equivalent. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the centerline EMA. +ATR multiplier sets the width of the channel. +Number of periods in the ATR evaluation. +Time series of Keltner Channel values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Klinger Oscillator depicts volume-based divergence between short and long-term money flow. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the short EMA. +Number of periods for the long EMA. +Number of periods Signal line. +Time series of Klinger Oscillator values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Moving Average Convergence/Divergence (MACD) is a simple oscillator view of two converging/diverging exponential moving averages. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the Fast EMA. +Number of periods in the Slow EMA. +Number of periods for the Signal moving average. +Time series of MACD values, including MACD, Signal, and Histogram. +Invalid parameter value provided. + + + + + Moving Average Envelopes is a price band overlay that is offset from the moving average of price over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Percent offset for envelope width. +Moving average type (e.g. EMA, HMA, TEMA, etc.). +Time series of MA Envelopes values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + + MESA Adaptive Moving Average (MAMA) is a 5-period adaptive moving average of high/low price. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Fast limit threshold. +Slow limit threshold. +Time series of MAMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Marubozu is a single candlestick pattern that has no wicks, representing consistent directional movement. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Optional. Minimum candle body size as percentage. +Time series of Marubozu values. +Invalid parameter value provided. + + + + + Marubozu is a single candlestick pattern that has no wicks, representing consistent directional movement. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Optional. Minimum candle body size as percentage. +Time series of Marubozu values. +Invalid parameter value provided. + + + + + Money Flow Index (MFI) is a price-volume oscillator that shows buying and selling momentum. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of MFI values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + On-balance Volume (OBV) is a rolling accumulation of volume based on Close price direction. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Optional. Number of periods for an SMA of the OBV line. +Time series of OBV values. +Invalid parameter value provided. + + + + + Parabolic SAR (stop and reverse) is a price-time based indicator used to determine trend direction and reversals. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Incremental step size. +Maximum step threshold. +Time series of Parabolic SAR values. +Invalid parameter value provided. + + + + + Parabolic SAR (stop and reverse) is a price-time based indicator used to determine trend direction and reversals. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Incremental step size. +Maximum step threshold. +Initial starting acceleration factor. +Time series of Parabolic SAR values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Pivot Points depict support and resistance levels, based on the prior lookback window. You can specify window size (e.g. month, week, day, etc). + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Calendar size of the lookback window. +Pivot Point type. +Time series of Pivot Points values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Pivots is an extended version of Williams Fractal that includes identification of Higher High, Lower Low, Higher Low, and Lower Low trends between pivots in a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of span periods to the left of the evaluation period. +Number of span periods to the right of the evaluation period. +Number of periods in the lookback window. +Determines use of Close or High/Low wicks for points. +Time series of Pivots values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + + Price Momentum Oscillator (PMO) is double-smoothed ROC based momentum indicator. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for ROC EMA smoothing. +Number of periods for PMO EMA smoothing. +Number of periods for Signal line EMA. +Time series of PMO values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Price Relative Strength (PRS), also called Comparative Relative Strength, + shows the ratio of two quote histories. It is often used to compare + against a market index or sector ETF. When using the optional lookbackPeriods, + this also return relative percent change over the specified periods. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes for evaluation. +This is usually market index data, but could be any baseline data that you might use for comparison. +Optional. Number of periods for % difference. +Optional. Number of periods for a PRS SMA signal line. +Time series of PRS values. +Invalid parameter value provided. +Invalid quotes provided. + + + + + Percentage Volume Oscillator (PVO) is a simple oscillator view of two converging/diverging exponential moving averages of Volume. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the Fast moving average. +Number of periods in the Slow moving average. +Number of periods for the PVO SMA signal line. +Time series of PVO values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Renko Chart is a modified Japanese candlestick pattern that uses time-lapsed bricks. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Fixed brick size ($). +End type. See documentation. +Time series of Renko Chart candlestick values. +Invalid parameter value provided. + + + + + The ATR Renko Chart is a modified Japanese candlestick pattern based on Average True Range brick size. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Lookback periods for the ATR evaluation. +End type. See documentation. +Time series of Renko Chart candlestick values. + + + + + Rate of Change (ROC), also known as Momentum Oscillator, is the percent change of price over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Optional. Number of periods for an ROC SMA signal line. +Time series of ROC values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Rate of Change with Bands (ROCWB) is the percent change of price over a lookback window with standard deviation bands. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Number of periods for the ROC EMA line. +Number of periods the standard deviation for upper/lower band lines. +Time series of ROCWB values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Rolling Pivot Points is a modern update to traditional fixed calendar window Pivot Points. + It depicts support and resistance levels, based on a defined rolling window and offset. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the evaluation window. +Number of periods to offset the window from the current period. +Pivot Point type. +Time series of Rolling Pivot Points values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Relative Strength Index (RSI) measures strength of the winning/losing streak over N lookback periods + on a scale of 0 to 100, to depict overbought and oversold conditions. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of RSI values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Slope of the best fit line is determined by an ordinary least-squares simple linear regression on price. + It can be used to help identify trend strength and direction. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Slope values, including Slope, Standard Deviation, R², and a best-fit Line (for the last lookback segment). +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Simple Moving Average (SMA) of the price. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of SMA values. +Invalid parameter value provided. + + + + + Simple Moving Average (SMA) is the average of price over a lookback window. This extended variant includes mean absolute deviation (MAD), mean square error (MSE), and mean absolute percentage error (MAPE). + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of SMA, MAD, MSE, and MAPE values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Stochastic Momentum Index is a double-smoothed variant of the Stochastic Oscillator on a scale from -100 to 100. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the Stochastic lookback. +Number of periods in the first smoothing. +Number of periods in the second smoothing. +Number of periods in the EMA of SMI. +Time series of Stochastic Momentum Index values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Smoothed Moving Average (SMMA) is the average of price over a lookback window using a smoothing method. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of SMMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Stoller Average Range Channel (STARC) Bands, are based on an SMA centerline and ATR band widths. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the centerline SMA. +ATR multiplier sets the width of the channel. +Number of periods in the ATR evaluation. +Time series of STARC Bands values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Schaff Trend Cycle is a stochastic oscillator view of two converging/diverging exponential moving averages. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the Trend Cycle. +Number of periods in the Fast EMA. +Number of periods in the Slow EMA. +Time series of MACD values, including MACD, Signal, and Histogram. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Rolling Standard Deviation of price over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Optional. Number of periods in the Standard Deviation SMA signal line. +Time series of Standard Deviations values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Standard Deviation Channels are based on an linear regression centerline and standard deviations band widths. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Size of the evaluation window. +Width of bands. Number of Standard Deviations from the regression line. +Time series of Standard Deviation Channels values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Stochastic Oscillator is a momentum indicator that looks back N periods to produce a scale of 0 to 100. + %J is also included for the KDJ Index extension. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the Oscillator. +Smoothing period for the %D signal line. +Smoothing period for the %K Oscillator. Use 3 for Slow or 1 for Fast. +Time series of Stochastic Oscillator values. +Invalid parameter value provided. + + + + + Stochastic Oscillator is a momentum indicator that looks back N periods to produce a scale of 0 to 100. + %J is also included for the KDJ Index extension. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the Oscillator. +Smoothing period for the %D signal line. +Smoothing period for the %K Oscillator. Use 3 for Slow or 1 for Fast. +Weight of %K in the %J calculation. Default is 3. +Weight of %K in the %J calculation. Default is 2. +Type of moving average to use. Default is MaType.SMA. See docs for instructions and options. +Time series of Stochastic Oscillator values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Stochastic RSI is a Stochastic interpretation of the Relative Strength Index. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the RSI. +Number of periods for the Stochastic. +Number of periods for the Stochastic RSI SMA signal line. +Number of periods for Stochastic Smoothing. Use 1 for Fast or 3 for Slow. +Time series of Stochastic RSI values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + SuperTrend attempts to determine the primary trend of prices by using + Average True Range (ATR) band thresholds around an HL2 midline. It can indicate a buy/sell signal or a + trailing stop when the trend changes. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for ATR. +Multiplier sets the ATR band width. +Time series of SuperTrend values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Tillson T3 is a smooth moving average that reduces both lag and overshooting. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the EMA smoothing. +Size of the Volume Factor. +Time series of T3 values. +Invalid parameter value provided. + + + + + Triple Exponential Moving Average (TEMA) of the price. Note: TEMA is often confused with the alternative TRIX oscillator. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Triple EMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + True Range (TR) is a measure of volatility that captures gaps and limits between periods. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Time series of True Range (TR) values. +Invalid parameter value provided. + + + + + Triple EMA Oscillator (TRIX) is the rate of change for a 3 EMA smoothing of the price over a lookback window. TRIX is often confused with TEMA. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Optional. Number of periods for a TRIX SMA signal line. +Time series of TRIX values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + True Strength Index (TSI) is a momentum oscillator that depicts trends in price changes. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods for the first EMA. +Number of periods in the second smoothing. +Number of periods in the TSI SMA signal line. +Time series of TSI values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Ulcer Index (UI) is a measure of downside price volatility over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Ulcer Index values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Ultimate Oscillator uses several lookback periods to weigh buying power against True Range price to produce on oversold / overbought oscillator. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the smallest window. +Number of periods in the middle-sized window. +Number of periods in the largest window. +Time series of Ultimate Oscillator values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Volatility Stop is an ATR based indicator used to determine trend direction, stops, and reversals. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +ATR offset amount. +Time series of Volatility Stop values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Vortex Indicator (VI) is a measure of price directional movement. + It includes positive and negative indicators, and is often used to identify trends and reversals. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of VI+ and VI- vortex movement indicator values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Volume Weighted Average Price (VWAP) is a Volume weighted average of price, typically used on intraday data. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Optional anchor date. If not provided, the first date in quotes is used. +Time series of VWAP values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Volume Weighted Moving Average is the volume adjusted average price over a lookback window. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Volume Weighted Moving Average values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Williams %R momentum indicator is a stochastic oscillator with scale of -100 to 0. It is exactly the same as the Fast variant of Stochastic Oscillator, but with a different scaling. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of Williams %R values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Weighted Moving Average (WMA) is the linear weighted average of price over N lookback periods. This also called Linear Weighted Moving Average (LWMA). + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Number of periods in the lookback window. +Time series of WMA values. +Invalid parameter value provided. + + + + Removes the recommended quantity of results from the beginning of the results list + using a reverse-engineering approach. See + documentation for more information. + +Indicator + results to evaluate. +Time + series of results, pruned. + + + + + Zig Zag is a price chart overlay that simplifies the up and down movements and transitions based on a percent change smoothing threshold. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Determines use of Close or High/Low wicks for extreme points. +Percent price change to set threshold for minimum size movements. +Time series of Zig Zag values. +Invalid parameter value provided. + + + + Removes non-essential records containing null values with unique consideration for + this indicator. See + documentation for more information. + +Indicator results to evaluate. +Time series of + indicator results, condensed. + + + + + Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. + + See +documentation + for more information. + + + + Standard output properties: + + +Oscillator +%K Oscillator over prior lookback periods. + + +Signal +%D Simple moving average of %K Oscillator. + + +PercentJ + + %J is the weighted divergence of %K and %D: %J=3×%K-2×%D + + + + These are the aliases of the above properties: + + +K +Same as Oscillator. + + +D +Same as Signal. + + +J +Same as PercentJ. + + + + + + + Removes a specific quantity from the beginning of the time series list. + + See documentation for more information. + + +Any series type. +Collection to evaluate. +Exact quantity to remove from the beginning of the series. +Time series, pruned. +Invalid parameter value provided. + + + + Finds time series values on a specific date. + + See documentation for more information. + + +Any series type. +Time series to evaluate. +Exact date to lookup. +First + record in the series on the date specified. + + + + + Nullable System. + functions. + + +System.Math infamously does not allow + or handle nullable input values. + Instead of adding repetitive inline defensive code, + we're using these equivalents. Most are simple wrappers. + + + + + Returns the absolute value of a nullable double. + +The nullable double value. +The absolute value, or null if the input is null. + + + + Rounds a nullable decimal value to a specified number of fractional digits. + +The nullable decimal value. +The number of fractional digits. +The rounded value, or null if the input is null. + + + + Rounds a nullable double value to a specified number of fractional digits. + +The nullable double value. +The number of fractional digits. +The rounded value, or null if the input is null. + + + + Rounds a double value to a specified number of fractional digits. + It is an extension alias of + +The double value. +The number of fractional digits. +The rounded value. + + + + Rounds a decimal value to a specified number of fractional digits. + It is an extension alias of + +The decimal value. +The number of fractional digits. +The rounded value. + + + + Converts a nullable double value to NaN if it is null. + +The nullable double value. +The value, or NaN if the input is null. + + + + Converts a nullable decimal value to NaN if it is null. + +The nullable decimal value. +The value as a double, or NaN if the input is null. + + + + Converts a nullable double value to null if it is NaN. + +The nullable double value. +The value, or null if the input is NaN. + + + + Converts a double value to null if it is NaN. + +The double value. +The value, or null if the input is NaN. + + + + Converts historical quotes into larger bar sizes. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +PeriodSize enum representing the new bar size. +Time series of historical quote values. +Invalid parameter value provided. + + + + + Converts historical quotes into larger bar sizes. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +TimeSpan representing the new bar size. +Time series of historical quote values. +Invalid parameter value provided. + + + + + Optionally select which candle part to use in the calculation. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +The OHLCV element or simply calculated value type. +Time series of Quote tuple values. +Invalid candle part provided. + + + + + Validate historical quotes. + + See +documentation + for more information. + + +Configurable Quote type. See Guide for more information. +Historical price quotes. +Time series of historical quote values. +Validation check failed. + + + + + Forces indicator results to have the same date-based records as another result baseline. + + This utility is undocumented. + + +Any indicator result series type to be transformed. +Any indicator result series type to be matched. +The indicator result series to be modified. +The indicator result series to compare for matching. +Synchronization behavior See options in SyncType enum. +Indicator result series, synchronized to a comparator match. + + Invalid parameter value provided. + + + + + Removes non-essential records containing null or NaN values. See + documentation for more information. + +Any result + type. +Indicator results to evaluate. +Time series of indicator results, + condensed. + + + +Converts results into a reusable tuple with warmup periods removed and nulls converted + to NaN. See + documentation for more information. + +Indicator results to evaluate. +Collection of non-nullable tuple time series of results, without null warmup periods. + + + +Converts results into a tuple collection with non-nullable NaN to replace null values. + See + documentation for more information. + +Indicator results to evaluate. +Collection of tuple time series of + results with specified handling of nulls, without pruning. + + + + diff --git a/stock_indicators/_cstypes/__init__.py b/stock_indicators/_cstypes/__init__.py index f2e76b20..d24d596b 100644 --- a/stock_indicators/_cstypes/__init__.py +++ b/stock_indicators/_cstypes/__init__.py @@ -2,6 +2,6 @@ from stock_indicators import _cslib -from .datetime import (DateTime, to_pydatetime) -from .decimal import (Decimal, to_pydecimal) -from .list import (List) +from .datetime import DateTime, to_pydatetime +from .decimal import Decimal, to_pydecimal +from .list import List diff --git a/stock_indicators/_cstypes/datetime.py b/stock_indicators/_cstypes/datetime.py index 4fd127e0..e3449933 100644 --- a/stock_indicators/_cstypes/datetime.py +++ b/stock_indicators/_cstypes/datetime.py @@ -1,5 +1,8 @@ -from datetime import datetime as PyDateTime, timezone as PyTimezone +from datetime import datetime as PyDateTime +from datetime import timezone as PyTimezone + from System import DateTimeKind # type: ignore + from stock_indicators._cslib import CsDateTime # type: ignore # Module-level constant: 1 second = 10,000,000 ticks (100ns per tick) diff --git a/stock_indicators/_cstypes/decimal.py b/stock_indicators/_cstypes/decimal.py index d72972ef..5c72d344 100644 --- a/stock_indicators/_cstypes/decimal.py +++ b/stock_indicators/_cstypes/decimal.py @@ -1,6 +1,7 @@ from decimal import Decimal as PyDecimal +from typing import Optional, Union -from stock_indicators._cslib import CsDecimal, CsCultureInfo, CsNumberStyles +from stock_indicators._cslib import CsCultureInfo, CsDecimal, CsNumberStyles class Decimal: @@ -8,7 +9,7 @@ class Decimal: Class for converting a number into C#'s `System.Decimal` class. Parameters: - decimal : `int`, `float` or any `object` that can be represented as a number string. + decimal : `int`, `float`, `PyDecimal`, or any `object` that can be represented as a number string. Example: Constructing `System.Decimal` from `float` of Python. @@ -17,19 +18,51 @@ class Decimal: >>> cs_decimal 2.5 """ - cs_number_styles = CsNumberStyles.AllowDecimalPoint | CsNumberStyles.AllowExponent \ - | CsNumberStyles.AllowLeadingSign | CsNumberStyles.AllowThousands - def __new__(cls, decimal) -> CsDecimal: - return CsDecimal.Parse(str(decimal), cls.cs_number_styles, CsCultureInfo.InvariantCulture) + cs_number_styles = ( + CsNumberStyles.AllowDecimalPoint + | CsNumberStyles.AllowExponent + | CsNumberStyles.AllowLeadingSign + | CsNumberStyles.AllowThousands + ) + def __new__(cls, decimal: Union[int, float, PyDecimal, str, None]) -> CsDecimal: + if decimal is None: + from stock_indicators.exceptions import ValidationError -def to_pydecimal(cs_decimal: CsDecimal) -> PyDecimal: + raise ValidationError("Cannot convert None to C# Decimal") + + # Convert to string first to preserve precision for all numeric types + try: + return CsDecimal.Parse( + str(decimal), cls.cs_number_styles, CsCultureInfo.InvariantCulture + ) + except Exception as e: + from stock_indicators.exceptions import TypeConversionError + + raise TypeConversionError( + f"Cannot convert {decimal} (type: {type(decimal)}) to C# Decimal: {e}" + ) from e + + +def to_pydecimal(cs_decimal: Optional[CsDecimal]) -> Optional[PyDecimal]: """ Converts an object to a native Python decimal object. Parameter: - cs_decimal : `System.Decimal` of C# or any `object` that can be represented as a number. + cs_decimal : `System.Decimal` of C# or None. + + Returns: + Python Decimal object or None if input is None. """ - if cs_decimal is not None: + if cs_decimal is None: + return None + + try: return PyDecimal(cs_decimal.ToString(CsCultureInfo.InvariantCulture)) + except Exception as e: + from stock_indicators.exceptions import TypeConversionError + + raise TypeConversionError( + f"Cannot convert C# Decimal to Python Decimal: {e}" + ) from e diff --git a/stock_indicators/_cstypes/list.py b/stock_indicators/_cstypes/list.py index 9a37b694..4aa8fdbe 100644 --- a/stock_indicators/_cstypes/list.py +++ b/stock_indicators/_cstypes/list.py @@ -1,4 +1,5 @@ from collections import deque +from typing import Iterable from stock_indicators._cslib import CsList @@ -30,8 +31,20 @@ class List: >>> print(i, end='') 123 """ - def __new__(cls, generic, sequence) -> CsList: - cs_list = CsList[generic]() - deque(map(cs_list.Add, sequence), maxlen=0) - return cs_list + def __new__(cls, generic: object, sequence: Iterable) -> CsList: + if not hasattr(sequence, "__iter__"): + raise TypeError("sequence must be iterable") + + try: + cs_list = CsList[generic]() + + # Always use individual Add calls for reliability + # AddRange has issues with Python.NET type conversion in some cases + deque(map(cs_list.Add, sequence), maxlen=0) + + return cs_list + except Exception as e: + raise ValueError( + f"Cannot convert sequence to C# List[{generic}]: {e}" + ) from e diff --git a/stock_indicators/exceptions.py b/stock_indicators/exceptions.py new file mode 100644 index 00000000..913f1cf8 --- /dev/null +++ b/stock_indicators/exceptions.py @@ -0,0 +1,23 @@ +""" +Custom exceptions for Stock Indicators for Python. +""" + + +class StockIndicatorsError(Exception): + """Base exception class for all Stock Indicators errors.""" + + +class StockIndicatorsInitializationError(ImportError, StockIndicatorsError): + """Raised when the .NET library fails to initialize.""" + + +class TypeConversionError(StockIndicatorsError): + """Raised when conversion between Python and C# types fails.""" + + +class IndicatorCalculationError(StockIndicatorsError): + """Raised when indicator calculation fails.""" + + +class ValidationError(StockIndicatorsError): + """Raised when input validation fails.""" diff --git a/stock_indicators/indicators/__init__.py b/stock_indicators/indicators/__init__.py index d11e5fc6..2a415203 100644 --- a/stock_indicators/indicators/__init__.py +++ b/stock_indicators/indicators/__init__.py @@ -2,91 +2,85 @@ from stock_indicators import _cslib -from .adl import (get_adl) -from .adx import (get_adx) -from .alligator import (get_alligator) -from .alma import (get_alma) -from .aroon import (get_aroon) -from .atr_stop import (get_atr_stop) -from .atr import (get_atr) -from .awesome import (get_awesome) -from .basic_quotes import (get_basic_quote) -from .beta import (get_beta) -from .bollinger_bands import (get_bollinger_bands) -from .bop import (get_bop) -from .cci import (get_cci) -from .chaikin_oscillator import (get_chaikin_osc) -from .chandelier import (get_chandelier) -from .chop import (get_chop) -from .cmf import (get_cmf) -from .cmo import (get_cmo) -from .connors_rsi import (get_connors_rsi) -from .correlation import (get_correlation) -from .doji import (get_doji) -from .donchian import (get_donchian) -from .dema import (get_dema) -from .dpo import (get_dpo) -from .dynamic import (get_dynamic) -from .elder_ray import (get_elder_ray) -from .ema import (get_ema) -from .epma import (get_epma) -from .fcb import (get_fcb) -from .fisher_transform import (get_fisher_transform) -from .force_index import (get_force_index) -from .fractal import (get_fractal) -from .gator import (get_gator) -from .heikin_ashi import (get_heikin_ashi) -from .hma import (get_hma) -from .ht_trendline import (get_ht_trendline) -from .hurst import (get_hurst) -from .ichimoku import (get_ichimoku) -from .kama import (get_kama) -from .keltner import (get_keltner) -from .kvo import (get_kvo) -from .macd import (get_macd) -from .ma_envelopes import (get_ma_envelopes) -from .mama import (get_mama) -from .marubozu import (get_marubozu) -from .mfi import (get_mfi) -from .obv import (get_obv) -from .parabolic_sar import (get_parabolic_sar) -from .pivot_points import (get_pivot_points) -from .pivots import (get_pivots) -from .pmo import (get_pmo) -from .prs import (get_prs) -from .pvo import (get_pvo) -from .renko import ( - get_renko, - get_renko_atr) -from .roc import ( - get_roc, - get_roc_with_band) -from .rolling_pivots import (get_rolling_pivots) -from .rsi import (get_rsi) -from .slope import (get_slope) -from .sma import ( - get_sma, - get_sma_analysis) -from .smi import (get_smi) -from .smma import (get_smma) -from .starc_bands import (get_starc_bands) -from .stc import (get_stc) -from .stdev_channels import (get_stdev_channels) -from .stdev import (get_stdev) -from .stoch import (get_stoch) -from .stoch_rsi import (get_stoch_rsi) -from .super_trend import (get_super_trend) -from .t3 import (get_t3) -from .tema import (get_tema) -from .tr import (get_tr) -from .trix import (get_trix) -from .tsi import (get_tsi) -from .ulcer_index import (get_ulcer_index) -from .ultimate import (get_ultimate) -from .volatility_stop import (get_volatility_stop) -from .vortex import (get_vortex) -from .vwap import (get_vwap) -from .vwma import (get_vwma) -from .williams_r import (get_williams_r) -from .wma import (get_wma) -from .zig_zag import (get_zig_zag) +from .adl import get_adl +from .adx import get_adx +from .alligator import get_alligator +from .alma import get_alma +from .aroon import get_aroon +from .atr import get_atr +from .atr_stop import get_atr_stop +from .awesome import get_awesome +from .basic_quotes import get_basic_quote +from .beta import get_beta +from .bollinger_bands import get_bollinger_bands +from .bop import get_bop +from .cci import get_cci +from .chaikin_oscillator import get_chaikin_osc +from .chandelier import get_chandelier +from .chop import get_chop +from .cmf import get_cmf +from .cmo import get_cmo +from .connors_rsi import get_connors_rsi +from .correlation import get_correlation +from .dema import get_dema +from .doji import get_doji +from .donchian import get_donchian +from .dpo import get_dpo +from .dynamic import get_dynamic +from .elder_ray import get_elder_ray +from .ema import get_ema +from .epma import get_epma +from .fcb import get_fcb +from .fisher_transform import get_fisher_transform +from .force_index import get_force_index +from .fractal import get_fractal +from .gator import get_gator +from .heikin_ashi import get_heikin_ashi +from .hma import get_hma +from .ht_trendline import get_ht_trendline +from .hurst import get_hurst +from .ichimoku import get_ichimoku +from .kama import get_kama +from .keltner import get_keltner +from .kvo import get_kvo +from .ma_envelopes import get_ma_envelopes +from .macd import get_macd +from .mama import get_mama +from .marubozu import get_marubozu +from .mfi import get_mfi +from .obv import get_obv +from .parabolic_sar import get_parabolic_sar +from .pivot_points import get_pivot_points +from .pivots import get_pivots +from .pmo import get_pmo +from .prs import get_prs +from .pvo import get_pvo +from .renko import get_renko, get_renko_atr +from .roc import get_roc, get_roc_with_band +from .rolling_pivots import get_rolling_pivots +from .rsi import get_rsi +from .slope import get_slope +from .sma import get_sma, get_sma_analysis +from .smi import get_smi +from .smma import get_smma +from .starc_bands import get_starc_bands +from .stc import get_stc +from .stdev import get_stdev +from .stdev_channels import get_stdev_channels +from .stoch import get_stoch +from .stoch_rsi import get_stoch_rsi +from .super_trend import get_super_trend +from .t3 import get_t3 +from .tema import get_tema +from .tr import get_tr +from .trix import get_trix +from .tsi import get_tsi +from .ulcer_index import get_ulcer_index +from .ultimate import get_ultimate +from .volatility_stop import get_volatility_stop +from .vortex import get_vortex +from .vwap import get_vwap +from .vwma import get_vwma +from .williams_r import get_williams_r +from .wma import get_wma +from .zig_zag import get_zig_zag diff --git a/stock_indicators/indicators/adl.py b/stock_indicators/indicators/adl.py index 468b6d3a..0b36548a 100644 --- a/stock_indicators/indicators/adl.py +++ b/stock_indicators/indicators/adl.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_adl(quotes: Iterable[Quote], sma_periods: Optional[int] = None): @@ -70,6 +70,8 @@ def adl_sma(self, value): _T = TypeVar("_T", bound=ADLResult) + + class ADLResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of ADL(Accumulation/Distribution Line) results. diff --git a/stock_indicators/indicators/adx.py b/stock_indicators/indicators/adx.py index c7d6639b..a6755cdd 100644 --- a/stock_indicators/indicators/adx.py +++ b/stock_indicators/indicators/adx.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_adx(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -53,6 +53,14 @@ def mdi(self) -> Optional[float]: def mdi(self, value): self._csdata.Mdi = value + @property + def dx(self) -> Optional[float]: + return self._csdata.Dx + + @dx.setter + def dx(self, value): + self._csdata.Dx = value + @property def adx(self) -> Optional[float]: return self._csdata.Adx @@ -71,6 +79,8 @@ def adxr(self, value): _T = TypeVar("_T", bound=ADXResult) + + class ADXResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of ADX(Average Directional Movement Index) results. diff --git a/stock_indicators/indicators/alligator.py b/stock_indicators/indicators/alligator.py index e0aaea70..74512699 100644 --- a/stock_indicators/indicators/alligator.py +++ b/stock_indicators/indicators/alligator.py @@ -3,14 +3,19 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_alligator(quotes: Iterable[Quote], - jaw_periods: int = 13, jaw_offset: int = 8, - teeth_periods: int = 8, teeth_offset: int = 5, - lips_periods: int = 5, lips_offset: int = 3): +def get_alligator( + quotes: Iterable[Quote], # pylint: disable=too-many-positional-arguments + jaw_periods: int = 13, + jaw_offset: int = 8, + teeth_periods: int = 8, + teeth_offset: int = 5, + lips_periods: int = 5, + lips_offset: int = 3, +): """Get Williams Alligator calculated. Williams Alligator is an indicator that transposes multiple moving averages, @@ -47,10 +52,15 @@ def get_alligator(quotes: Iterable[Quote], - [Williams Alligator Reference](https://python.stockindicators.dev/indicators/Alligator/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - alligator_results = CsIndicator.GetAlligator[Quote](CsList(Quote, quotes), - jaw_periods, jaw_offset, - teeth_periods, teeth_offset, - lips_periods, lips_offset) + alligator_results = CsIndicator.GetAlligator[Quote]( + CsList(Quote, quotes), + jaw_periods, + jaw_offset, + teeth_periods, + teeth_offset, + lips_periods, + lips_offset, + ) return AlligatorResults(alligator_results, AlligatorResult) @@ -85,6 +95,8 @@ def lips(self, value): _T = TypeVar("_T", bound=AlligatorResult) + + class AlligatorResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Williams Alligator results. diff --git a/stock_indicators/indicators/alma.py b/stock_indicators/indicators/alma.py index 262c7112..b0ec5366 100644 --- a/stock_indicators/indicators/alma.py +++ b/stock_indicators/indicators/alma.py @@ -7,7 +7,12 @@ from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_alma(quotes: Iterable[Quote], lookback_periods: int = 9, offset: float = .85, sigma : float = 6): +def get_alma( + quotes: Iterable[Quote], + lookback_periods: int = 9, + offset: float = 0.85, + sigma: float = 6, +): """Get ALMA calculated. Arnaud Legoux Moving Average (ALMA) is a Gaussian distribution @@ -34,7 +39,9 @@ def get_alma(quotes: Iterable[Quote], lookback_periods: int = 9, offset: float = - [ALMA Reference](https://python.stockindicators.dev/indicators/Alma/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - alma_results = CsIndicator.GetAlma[Quote](CsList(Quote, quotes), lookback_periods, offset, sigma) + alma_results = CsIndicator.GetAlma[Quote]( + CsList(Quote, quotes), lookback_periods, offset, sigma + ) return ALMAResults(alma_results, ALMAResult) @@ -53,6 +60,8 @@ def alma(self, value): _T = TypeVar("_T", bound=ALMAResult) + + class ALMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of ALMA(Arnaud Legoux Moving Average) results. diff --git a/stock_indicators/indicators/aroon.py b/stock_indicators/indicators/aroon.py index 4cf04184..a64e0cd1 100644 --- a/stock_indicators/indicators/aroon.py +++ b/stock_indicators/indicators/aroon.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_aroon(quotes: Iterable[Quote], lookback_periods: int = 25): @@ -62,6 +62,8 @@ def oscillator(self, value): _T = TypeVar("_T", bound=AroonResult) + + class AroonResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Aroon results. diff --git a/stock_indicators/indicators/atr.py b/stock_indicators/indicators/atr.py index 6e97978c..e029e57b 100644 --- a/stock_indicators/indicators/atr.py +++ b/stock_indicators/indicators/atr.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_atr(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -62,6 +62,8 @@ def atrp(self, value): _T = TypeVar("_T", bound=ATRResult) + + class ATRResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of ATR(Average True Range) results. diff --git a/stock_indicators/indicators/atr_stop.py b/stock_indicators/indicators/atr_stop.py index 140b1774..8e3a8a7f 100644 --- a/stock_indicators/indicators/atr_stop.py +++ b/stock_indicators/indicators/atr_stop.py @@ -2,17 +2,21 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import EndType from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_atr_stop(quotes: Iterable[Quote], lookback_periods: int = 21, - multiplier: float = 3, end_type: EndType = EndType.CLOSE): +def get_atr_stop( + quotes: Iterable[Quote], + lookback_periods: int = 21, + multiplier: float = 3, + end_type: EndType = EndType.CLOSE, +): """Get ATR Trailing Stop calculated. ATR Trailing Stop attempts to determine the primary trend of prices by using @@ -40,7 +44,9 @@ def get_atr_stop(quotes: Iterable[Quote], lookback_periods: int = 21, - [ATR Trailing Stop Reference](https://python.stockindicators.dev/indicators/AtrStop/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetAtrStop[Quote](CsList(Quote, quotes), lookback_periods, multiplier, end_type.cs_value) + results = CsIndicator.GetAtrStop[Quote]( + CsList(Quote, quotes), lookback_periods, multiplier, end_type.cs_value + ) return AtrStopResults(results, AtrStopResult) @@ -75,6 +81,8 @@ def sell_stop(self, value): _T = TypeVar("_T", bound=AtrStopResult) + + class AtrStopResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of ATR Trailing Stop results. diff --git a/stock_indicators/indicators/awesome.py b/stock_indicators/indicators/awesome.py index 8873d29c..6877790f 100644 --- a/stock_indicators/indicators/awesome.py +++ b/stock_indicators/indicators/awesome.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_awesome(quotes: Iterable[Quote], fast_periods: int = 5, slow_periods: int = 34): @@ -31,7 +31,9 @@ def get_awesome(quotes: Iterable[Quote], fast_periods: int = 5, slow_periods: in - [Awesome Oscillator Reference](https://python.stockindicators.dev/indicators/Awesome/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - awesome_results = CsIndicator.GetAwesome[Quote](CsList(Quote, quotes), fast_periods, slow_periods) + awesome_results = CsIndicator.GetAwesome[Quote]( + CsList(Quote, quotes), fast_periods, slow_periods + ) return AwesomeResults(awesome_results, AwesomeResult) @@ -58,6 +60,8 @@ def normalized(self, value): _T = TypeVar("_T", bound=AwesomeResult) + + class AwesomeResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Awesome Oscillator (aka Super AO) results. diff --git a/stock_indicators/indicators/basic_quotes.py b/stock_indicators/indicators/basic_quotes.py index 5480c1d7..365a8b39 100644 --- a/stock_indicators/indicators/basic_quotes.py +++ b/stock_indicators/indicators/basic_quotes.py @@ -3,11 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.enums import CandlePart -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_basic_quote(quotes: Iterable[Quote], candle_part: CandlePart = CandlePart.CLOSE): +def get_basic_quote( + quotes: Iterable[Quote], candle_part: CandlePart = CandlePart.CLOSE +): """Get Basic Quote calculated. A simple quote transform (e.g. HL2, OHL3, etc.) and isolation of individual @@ -28,7 +30,9 @@ def get_basic_quote(quotes: Iterable[Quote], candle_part: CandlePart = CandlePar - [Basic Quote Reference](https://python.stockindicators.dev/indicators/BasicQuote/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetBaseQuote[Quote](CsList(Quote, quotes), candle_part.cs_value) + results = CsIndicator.GetBaseQuote[Quote]( + CsList(Quote, quotes), candle_part.cs_value + ) return BasicQuoteResults(results, BasicQuoteResult) @@ -47,6 +51,8 @@ def jaw(self, value): _T = TypeVar("_T", bound=BasicQuoteResult) + + class BasicQuoteResults(IndicatorResults[_T]): """ A wrapper class for the list of Basic Quote results. diff --git a/stock_indicators/indicators/beta.py b/stock_indicators/indicators/beta.py index 3e317b19..ec2640eb 100644 --- a/stock_indicators/indicators/beta.py +++ b/stock_indicators/indicators/beta.py @@ -4,12 +4,16 @@ from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.enums import BetaType from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_beta(eval_quotes: Iterable[Quote], market_quotes: Iterable[Quote], - lookback_periods: int, beta_type: BetaType = BetaType.STANDARD): +def get_beta( + eval_quotes: Iterable[Quote], + market_quotes: Iterable[Quote], + lookback_periods: int, + beta_type: BetaType = BetaType.STANDARD, +): """Get Beta calculated. Beta shows how strongly one stock responds to systemic volatility of the entire market. @@ -36,8 +40,12 @@ def get_beta(eval_quotes: Iterable[Quote], market_quotes: Iterable[Quote], - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - beta_results = CsIndicator.GetBeta[Quote](CsList(Quote, eval_quotes), CsList(Quote, market_quotes), - lookback_periods, beta_type.cs_value) + beta_results = CsIndicator.GetBeta[Quote]( + CsList(Quote, eval_quotes), + CsList(Quote, market_quotes), + lookback_periods, + beta_type.cs_value, + ) return BetaResults(beta_results, BetaResult) diff --git a/stock_indicators/indicators/bollinger_bands.py b/stock_indicators/indicators/bollinger_bands.py index 9a955ce5..b2b99158 100644 --- a/stock_indicators/indicators/bollinger_bands.py +++ b/stock_indicators/indicators/bollinger_bands.py @@ -3,11 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_bollinger_bands(quotes: Iterable[Quote], lookback_periods: int = 20, standard_deviations: float = 2): +def get_bollinger_bands( + quotes: Iterable[Quote], lookback_periods: int = 20, standard_deviations: float = 2 +): """Get Bollinger Bands® calculated. Bollinger Bands® depict volatility as standard deviation @@ -31,7 +33,9 @@ def get_bollinger_bands(quotes: Iterable[Quote], lookback_periods: int = 20, sta - [Bollinger Bands® Reference](https://python.stockindicators.dev/indicators/BollingerBands/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - bollinger_bands_results = CsIndicator.GetBollingerBands[Quote](CsList(Quote, quotes), lookback_periods, standard_deviations) + bollinger_bands_results = CsIndicator.GetBollingerBands[Quote]( + CsList(Quote, quotes), lookback_periods, standard_deviations + ) return BollingerBandsResults(bollinger_bands_results, BollingerBandsResult) @@ -90,6 +94,8 @@ def width(self, value): _T = TypeVar("_T", bound=BollingerBandsResult) + + class BollingerBandsResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Bollinger Bands results. diff --git a/stock_indicators/indicators/bop.py b/stock_indicators/indicators/bop.py index 69d3f744..bda166e3 100644 --- a/stock_indicators/indicators/bop.py +++ b/stock_indicators/indicators/bop.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_bop(quotes: Iterable[Quote], smooth_periods: int = 14): @@ -48,6 +48,8 @@ def bop(self, value): _T = TypeVar("_T", bound=BOPResult) + + class BOPResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Balance of Power (aka Balance of Market Power) results. diff --git a/stock_indicators/indicators/cci.py b/stock_indicators/indicators/cci.py index f6e151ba..2f8b752a 100644 --- a/stock_indicators/indicators/cci.py +++ b/stock_indicators/indicators/cci.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_cci(quotes: Iterable[Quote], lookback_periods: int = 20): @@ -47,6 +47,8 @@ def cci(self, value): _T = TypeVar("_T", bound=CCIResult) + + class CCIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Commodity Channel Index (CCI) results. diff --git a/stock_indicators/indicators/chaikin_oscillator.py b/stock_indicators/indicators/chaikin_oscillator.py index 394467ad..3b879265 100644 --- a/stock_indicators/indicators/chaikin_oscillator.py +++ b/stock_indicators/indicators/chaikin_oscillator.py @@ -3,11 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_chaikin_osc(quotes: Iterable[Quote], fast_periods: int = 3, slow_periods: int = 10): +def get_chaikin_osc( + quotes: Iterable[Quote], fast_periods: int = 3, slow_periods: int = 10 +): """Get Chaikin Oscillator calculated. Chaikin Oscillator is the difference between fast and slow @@ -31,7 +33,9 @@ def get_chaikin_osc(quotes: Iterable[Quote], fast_periods: int = 3, slow_periods - [Chaikin Oscillator Reference](https://python.stockindicators.dev/indicators/ChaikinOsc/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetChaikinOsc[Quote](CsList(Quote, quotes), fast_periods, slow_periods) + results = CsIndicator.GetChaikinOsc[Quote]( + CsList(Quote, quotes), fast_periods, slow_periods + ) return ChaikinOscResults(results, ChaikinOscResult) @@ -74,6 +78,8 @@ def oscillator(self, value): _T = TypeVar("_T", bound=ChaikinOscResult) + + class ChaikinOscResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Chaikin Oscillator results. diff --git a/stock_indicators/indicators/chandelier.py b/stock_indicators/indicators/chandelier.py index b136f231..55eef624 100644 --- a/stock_indicators/indicators/chandelier.py +++ b/stock_indicators/indicators/chandelier.py @@ -4,12 +4,16 @@ from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.enums import ChandelierType from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_chandelier(quotes: Iterable[Quote], lookback_periods: int = 22, - multiplier: float = 3, chandelier_type: ChandelierType = ChandelierType.LONG): +def get_chandelier( + quotes: Iterable[Quote], + lookback_periods: int = 22, + multiplier: float = 3, + chandelier_type: ChandelierType = ChandelierType.LONG, +): """Get Chandelier Exit calculated. Chandelier Exit is typically used for stop-loss and can be @@ -36,8 +40,9 @@ def get_chandelier(quotes: Iterable[Quote], lookback_periods: int = 22, - [Chandelier Exit Reference](https://python.stockindicators.dev/indicators/Chandelier/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetChandelier[Quote](CsList(Quote, quotes), lookback_periods, - multiplier, chandelier_type.cs_value) + results = CsIndicator.GetChandelier[Quote]( + CsList(Quote, quotes), lookback_periods, multiplier, chandelier_type.cs_value + ) return ChandelierResults(results, ChandelierResult) @@ -56,6 +61,8 @@ def chandelier_exit(self, value): _T = TypeVar("_T", bound=ChandelierResult) + + class ChandelierResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Chandelier Exit results. diff --git a/stock_indicators/indicators/chop.py b/stock_indicators/indicators/chop.py index 40bf6048..da12cf3c 100644 --- a/stock_indicators/indicators/chop.py +++ b/stock_indicators/indicators/chop.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_chop(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -47,6 +47,8 @@ def chop(self, value): _T = TypeVar("_T", bound=ChopResult) + + class ChopResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Choppiness Index (CHOP) results. diff --git a/stock_indicators/indicators/cmf.py b/stock_indicators/indicators/cmf.py index a9aa24b9..878a99fe 100644 --- a/stock_indicators/indicators/cmf.py +++ b/stock_indicators/indicators/cmf.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_cmf(quotes: Iterable[Quote], lookback_periods: int = 20): @@ -63,6 +63,8 @@ def cmf(self, value): _T = TypeVar("_T", bound=CMFResult) + + class CMFResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Chaikin Money Flow (CMF) results. diff --git a/stock_indicators/indicators/cmo.py b/stock_indicators/indicators/cmo.py index 0e6ed714..45fa6c16 100644 --- a/stock_indicators/indicators/cmo.py +++ b/stock_indicators/indicators/cmo.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_cmo(quotes: Iterable[Quote], lookback_periods: int): @@ -47,6 +47,8 @@ def cmo(self, value): _T = TypeVar("_T", bound=CMOResult) + + class CMOResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Chande Momentum Oscillator (CMO) results. diff --git a/stock_indicators/indicators/common/__init__.py b/stock_indicators/indicators/common/__init__.py index 06994367..d869d097 100644 --- a/stock_indicators/indicators/common/__init__.py +++ b/stock_indicators/indicators/common/__init__.py @@ -1,35 +1,30 @@ -from .quote import Quote -from .results import ( - ResultBase, - IndicatorResults -) -from .candles import ( - CandleProperties, -) +from .candles import CandleProperties from .enums import ( BetaType, - ChandelierType, CandlePart, + ChandelierType, EndType, + Match, MAType, PeriodSize, PivotPointType, PivotTrend, - Match ) +from .quote import Quote +from .results import IndicatorResults, ResultBase __all__ = [ - "Quote", - "ResultBase", - "IndicatorResults", - "CandleProperties", "BetaType", - "ChandelierType", "CandlePart", + "CandleProperties", + "ChandelierType", "EndType", + "IndicatorResults", "MAType", + "Match", "PeriodSize", "PivotPointType", "PivotTrend", - "Match" + "Quote", + "ResultBase", ] diff --git a/stock_indicators/indicators/common/_contrib/type_resolver.py b/stock_indicators/indicators/common/_contrib/type_resolver.py index 59876c61..6bdcfdf1 100644 --- a/stock_indicators/indicators/common/_contrib/type_resolver.py +++ b/stock_indicators/indicators/common/_contrib/type_resolver.py @@ -1,5 +1,16 @@ from typing import Type, TypeVar, cast _T = TypeVar("_T") -def generate_cs_inherited_class(child: Type[_T], cs_parent: Type, class_name="_Wrapper"): - return cast(Type[_T], type(class_name, (cs_parent, ), {attr: getattr(child, attr) for attr in dir(child)})) + + +def generate_cs_inherited_class( + child: Type[_T], cs_parent: Type, class_name="_Wrapper" +): + return cast( + Type[_T], + type( + class_name, + (cs_parent,), + {attr: getattr(child, attr) for attr in dir(child)}, + ), + ) diff --git a/stock_indicators/indicators/common/candles.py b/stock_indicators/indicators/common/candles.py index a6345fab..d1d94559 100644 --- a/stock_indicators/indicators/common/candles.py +++ b/stock_indicators/indicators/common/candles.py @@ -1,11 +1,14 @@ from decimal import Decimal from typing import Optional, TypeVar + from typing_extensions import override from stock_indicators._cslib import CsCandleProperties from stock_indicators._cstypes import Decimal as CsDecimal from stock_indicators._cstypes import to_pydecimal -from stock_indicators.indicators.common._contrib.type_resolver import generate_cs_inherited_class +from stock_indicators.indicators.common._contrib.type_resolver import ( + generate_cs_inherited_class, +) from stock_indicators.indicators.common.enums import Match from stock_indicators.indicators.common.helpers import CondenseMixin from stock_indicators.indicators.common.quote import _Quote @@ -15,26 +18,31 @@ class _CandleProperties(_Quote): @property def size(self) -> Optional[Decimal]: + # pylint: disable=no-member # C# interop properties return to_pydecimal(self.High - self.Low) @property def body(self) -> Optional[Decimal]: - return to_pydecimal(self.Open - self.Close \ - if (self.Open > self.Close) \ - else self.Close - self.Open) + # pylint: disable=no-member # C# interop properties + return to_pydecimal( + self.Open - self.Close + if (self.Open > self.Close) + else self.Close - self.Open + ) @property def upper_wick(self) -> Optional[Decimal]: - return to_pydecimal(self.High - ( - self.Open \ - if self.Open > self.Close \ - else self.Close)) + # pylint: disable=no-member # C# interop properties + return to_pydecimal( + self.High - (self.Open if self.Open > self.Close else self.Close) + ) @property def lower_wick(self) -> Optional[Decimal]: - return to_pydecimal((self.Close \ - if self.Open > self.Close \ - else self.Open) - self.Low) + # pylint: disable=no-member # C# interop properties + return to_pydecimal( + (self.Close if self.Open > self.Close else self.Open) - self.Low + ) @property def body_pct(self) -> Optional[float]: @@ -50,14 +58,18 @@ def lower_wick_pct(self) -> Optional[float]: @property def is_bullish(self) -> bool: + # pylint: disable=no-member # C# interop properties return self.Close > self.Open @property def is_bearish(self) -> bool: + # pylint: disable=no-member # C# interop properties return self.Close < self.Open -class CandleProperties(generate_cs_inherited_class(_CandleProperties, CsCandleProperties)): +class CandleProperties( + generate_cs_inherited_class(_CandleProperties, CsCandleProperties) +): """An extended version of Quote that contains additional calculated properties.""" @@ -87,7 +99,10 @@ def match(self, value): @property def candle(self) -> CandleProperties: if not self.__candle_prop_cache: - self.__candle_prop_cache = CandleProperties.from_csquote(self._csdata.Candle) + # pylint: disable=no-member # C# interop method + self.__candle_prop_cache = CandleProperties.from_csquote( + self._csdata.Candle + ) return self.__candle_prop_cache @@ -98,6 +113,8 @@ def candle(self, value): _T = TypeVar("_T", bound=CandleResult) + + class CandleResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Candlestick pattern results. @@ -107,4 +124,6 @@ class CandleResults(CondenseMixin, IndicatorResults[_T]): @override def condense(self): - return self.__class__(filter(lambda x: x.match != Match.NONE, self), self._wrapper_class) + return self.__class__( + filter(lambda x: x.match != Match.NONE, self), self._wrapper_class + ) diff --git a/stock_indicators/indicators/common/enums.py b/stock_indicators/indicators/common/enums.py index 60a40c76..a9296e8f 100644 --- a/stock_indicators/indicators/common/enums.py +++ b/stock_indicators/indicators/common/enums.py @@ -1,6 +1,15 @@ from stock_indicators._cslib import ( - CsCandlePart, CsMatch, CsEnum, CsBetaType, CsChandelierType, CsMaType, - CsPivotPointType, CsPeriodSize, CsEndType, CsPivotTrend) + CsBetaType, + CsCandlePart, + CsChandelierType, + CsEndType, + CsEnum, + CsMatch, + CsMaType, + CsPeriodSize, + CsPivotPointType, + CsPivotTrend, +) from stock_indicators.indicators.common._contrib.enum import CsCompatibleIntEnum diff --git a/stock_indicators/indicators/common/helpers.py b/stock_indicators/indicators/common/helpers.py index 12343b17..d019dc14 100644 --- a/stock_indicators/indicators/common/helpers.py +++ b/stock_indicators/indicators/common/helpers.py @@ -2,37 +2,69 @@ from typing_extensions import Self -from stock_indicators._cslib import CsIndicator, CsIEnumerable, CsResultUtility +from stock_indicators._cslib import CsIEnumerable, CsIndicator, CsResultUtility from stock_indicators._cstypes import List as CsList +from stock_indicators.exceptions import IndicatorCalculationError from stock_indicators.indicators.common.results import IndicatorResults class RemoveWarmupMixin: """IndicatorResults Mixin for remove_warmup_periods().""" + @IndicatorResults._verify_data - def remove_warmup_periods(self: IndicatorResults, remove_periods: Optional[int] = None) -> Self: + def remove_warmup_periods( + self: IndicatorResults, remove_periods: Optional[int] = None + ) -> Self: """ Remove the recommended(or specified) quantity of results from the beginning of the results list. + + Args: + remove_periods: Number of periods to remove. If None, removes recommended warmup periods. + + Returns: + New IndicatorResults instance with warmup periods removed. """ if remove_periods is not None: + if not isinstance(remove_periods, int): + raise TypeError("remove_periods must be an integer") + if remove_periods < 0: + raise ValueError("remove_periods must be non-negative") return super().remove_warmup_periods(remove_periods) - removed_results = CsIndicator.RemoveWarmupPeriods(CsList(self._get_csdata_type(), self._csdata)) - return self.__class__(removed_results, self._wrapper_class) + try: + removed_results = CsIndicator.RemoveWarmupPeriods( + CsList(self._get_csdata_type(), self._csdata) + ) + return self.__class__(removed_results, self._wrapper_class) + except Exception as e: + raise IndicatorCalculationError("remove_warmup_periods failed") from e class CondenseMixin: """IndicatorResults Mixin for condense().""" + @IndicatorResults._verify_data def condense(self: IndicatorResults) -> Self: """ Removes non-essential records containing null values with unique consideration for this indicator. + + Returns: + New IndicatorResults instance with null values removed. """ cs_results_type = self._get_csdata_type() - try: # to check whether there's matched overloaded method. - condense_method = CsIndicator.Condense.Overloads[CsIEnumerable[cs_results_type]] - except TypeError: - condense_method = CsResultUtility.Condense[cs_results_type] - condensed_results = condense_method(CsList(cs_results_type, self._csdata)) - return self.__class__(condensed_results, self._wrapper_class) + try: + # Try to find the specific overloaded method first + try: + condense_method = CsIndicator.Condense.Overloads[ + CsIEnumerable[cs_results_type] + ] + except TypeError: + # Fall back to generic utility method + condense_method = CsResultUtility.Condense[cs_results_type] + + condensed_results = condense_method(CsList(cs_results_type, self._csdata)) + return self.__class__(condensed_results, self._wrapper_class) + + except Exception as e: + raise ValueError("Failed to condense results") from e diff --git a/stock_indicators/indicators/common/quote.py b/stock_indicators/indicators/common/quote.py index dd986851..a3d29788 100644 --- a/stock_indicators/indicators/common/quote.py +++ b/stock_indicators/indicators/common/quote.py @@ -1,55 +1,94 @@ from datetime import datetime, timezone from decimal import Decimal -from typing import Any, Iterable, Optional +from typing import Any, Iterable, Optional, Union from stock_indicators._cslib import CsQuote, CsQuoteUtility -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import DateTime as CsDateTime from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydatetime, to_pydecimal +from stock_indicators.indicators.common._contrib.type_resolver import ( + generate_cs_inherited_class, +) from stock_indicators.indicators.common.enums import CandlePart -from stock_indicators.indicators.common._contrib.type_resolver import generate_cs_inherited_class -def _get_date(quote): +def _get_date(quote) -> datetime: + """Get the date property with proper null handling.""" return to_pydatetime(quote.Date) -def _set_date(quote, value): + +def _set_date(quote, value: datetime) -> None: + """Set the date property with validation and timezone normalization.""" + if not isinstance(value, datetime): + raise TypeError("Date must be a datetime.datetime instance") + + # Normalize timezone-aware datetime to UTC (from main branch) if value.tzinfo is not None and value.utcoffset() is not None: value = value.astimezone(timezone.utc) quote.Date = CsDateTime(value) -def _get_open(quote): + +def _get_open(quote) -> Optional[Decimal]: + """Get the open property with proper null handling.""" return to_pydecimal(quote.Open) -def _set_open(quote, value): - quote.Open = CsDecimal(value) -def _get_high(quote): +def _set_open(quote, value: Optional[Union[int, float, Decimal, str]]) -> None: + """Set the open property with validation.""" + if value is not None: + quote.Open = CsDecimal(value) + # Note: C# nullable decimals can't be explicitly set to None from Python.NET + # The C# property handles null values internally when not set + + +def _get_high(quote) -> Optional[Decimal]: + """Get the high property with proper null handling.""" return to_pydecimal(quote.High) -def _set_high(quote, value): - quote.High = CsDecimal(value) -def _get_low(quote): +def _set_high(quote, value: Optional[Union[int, float, Decimal, str]]) -> None: + """Set the high property with validation.""" + if value is not None: + quote.High = CsDecimal(value) + + +def _get_low(quote) -> Optional[Decimal]: + """Get the low property with proper null handling.""" return to_pydecimal(quote.Low) -def _set_low(quote, value): - quote.Low = CsDecimal(value) -def _get_close(quote): +def _set_low(quote, value: Optional[Union[int, float, Decimal, str]]) -> None: + """Set the low property with validation.""" + if value is not None: + quote.Low = CsDecimal(value) + + +def _get_close(quote) -> Optional[Decimal]: + """Get the close property with proper null handling.""" return to_pydecimal(quote.Close) -def _set_close(quote, value): - quote.Close = CsDecimal(value) -def _get_volume(quote): +def _set_close(quote, value: Optional[Union[int, float, Decimal, str]]) -> None: + """Set the close property with validation.""" + if value is not None: + quote.Close = CsDecimal(value) + + +def _get_volume(quote) -> Optional[Decimal]: + """Get the volume property with proper null handling.""" return to_pydecimal(quote.Volume) -def _set_volume(quote, value): - quote.Volume = CsDecimal(value) + +def _set_volume(quote, value: Optional[Union[int, float, Decimal, str]]) -> None: + """Set the volume property with validation.""" + if value is not None: + quote.Volume = CsDecimal(value) + class _Quote: + """Internal Quote implementation with property definitions.""" + date = property(_get_date, _set_date) open = property(_get_open, _set_open) high = property(_get_high, _set_high) @@ -57,39 +96,87 @@ class _Quote: close = property(_get_close, _set_close) volume = property(_get_volume, _set_volume) - def __init__(self, date: datetime, open: Optional[Any] = None, - high: Optional[Any] = None, low: Optional[Any] = None, - close: Optional[Any] = None, volume: Optional[Any] = None): + def __init__( + self, + date: datetime, # pylint: disable=too-many-positional-arguments + open: Optional[Union[int, float, Decimal, str]] = None, # pylint: disable=redefined-builtin + high: Optional[Union[int, float, Decimal, str]] = None, + low: Optional[Union[int, float, Decimal, str]] = None, + close: Optional[Union[int, float, Decimal, str]] = None, + volume: Optional[Union[int, float, Decimal, str]] = None, + ): + """ + Initialize a Quote with OHLCV data. + + Args: + date: The date for this quote (required) + open: Opening price (optional) + high: High price (optional) + low: Low price (optional) + close: Closing price (optional) + volume: Volume (optional) + """ + if not isinstance(date, datetime): + raise TypeError("date must be a datetime.datetime instance") + self.date = date - self.open: Decimal = open if open else 0 - self.high: Decimal = high if high else 0 - self.low: Decimal = low if low else 0 - self.close: Decimal = close if close else 0 - self.volume: Decimal = volume if volume else 0 + # Only set values that are not None to avoid C# nullable issues + if open is not None: + self.open = open + if high is not None: + self.high = high + if low is not None: + self.low = low + if close is not None: + self.close = close + if volume is not None: + self.volume = volume @classmethod - def from_csquote(cls, cs_quote: CsQuote): + def from_csquote(cls, cs_quote: CsQuote) -> "Quote": """Constructs `Quote` instance from C# `Quote` instance.""" + if not isinstance(cs_quote, CsQuote): + raise TypeError("cs_quote must be a C# Quote instance") + return cls( date=to_pydatetime(cs_quote.Date), open=to_pydecimal(cs_quote.Open), high=to_pydecimal(cs_quote.High), low=to_pydecimal(cs_quote.Low), close=to_pydecimal(cs_quote.Close), - volume=to_pydecimal(cs_quote.Volume) + volume=to_pydecimal(cs_quote.Volume), ) @classmethod - def use(cls, quotes: Iterable["Quote"], candle_part: CandlePart): + def use(cls, quotes: Iterable["Quote"], candle_part: CandlePart) -> Any: """ Optionally select which candle part to use in the calculation. It returns C# Object. + + Args: + quotes: Collection of Quote objects + candle_part: Which part of the candle to use + + Returns: + C# collection prepared for indicator calculation """ - return CsQuoteUtility.Use[Quote](CsList(Quote, quotes), candle_part.cs_value) + if not hasattr(quotes, "__iter__"): + raise TypeError("quotes must be iterable") + if not isinstance(candle_part, CandlePart): + raise TypeError("candle_part must be a CandlePart enum value") + + try: + return CsQuoteUtility.Use[Quote]( + CsList(Quote, quotes), candle_part.cs_value + ) + except Exception as e: + raise ValueError(f"Failed to prepare quotes for calculation: {e}") from e class Quote(generate_cs_inherited_class(_Quote, CsQuote)): """ A single dated quote containing OHLCV elements. OHLCV values can be given as any object that can be represented as a number string. + + This class extends the C# Quote type to provide Python-friendly access to quote data. """ diff --git a/stock_indicators/indicators/common/results.py b/stock_indicators/indicators/common/results.py index 2424283d..d0510990 100644 --- a/stock_indicators/indicators/common/results.py +++ b/stock_indicators/indicators/common/results.py @@ -1,6 +1,5 @@ from datetime import datetime as PyDateTime from typing import Callable, Iterable, List, Optional, Type, TypeVar -from warnings import warn from stock_indicators._cslib import CsResultBase from stock_indicators._cstypes import DateTime as CsDateTime @@ -9,59 +8,63 @@ class ResultBase: """A base wrapper class for a single unit of the results.""" + def __init__(self, base_result: CsResultBase): self._csdata = base_result @property - def date(self): + def date(self) -> PyDateTime: + """Get the date of this result.""" return to_pydatetime(self._csdata.Date) @date.setter - def date(self, value): + def date(self, value: PyDateTime) -> None: + """Set the date of this result.""" + if not isinstance(value, PyDateTime): + raise TypeError("Date must be a datetime.datetime instance") self._csdata.Date = CsDateTime(value) _T = TypeVar("_T", bound=ResultBase) + + class IndicatorResults(List[_T]): """ A base wrapper class for the list of results. It provides helper methods written in CSharp implementation. """ + def __init__(self, data: Iterable, wrapper_class: Type[_T]): - super().__init__(map(wrapper_class, data)) - self._csdata = data + if not data: + super().__init__() + self._csdata = [] + else: + super().__init__(map(wrapper_class, data)) + self._csdata = data self._wrapper_class = wrapper_class - def reload(self): - """ - Reload a C# array of the results to perform more operations. - It is usually called after `done()`. - This method is deprecated. It will be removed in the next version. - """ - warn('This method is deprecated.', DeprecationWarning, stacklevel=2) - if self._csdata is None: - self._csdata = [ _._csdata for _ in self ] - return self - - def done(self): - """ - Remove a C# array of the results after finishing all operations. - It is not necessary but saves memory. - This method is deprecated. It will be removed in the next version. - """ - warn('This method is deprecated.', DeprecationWarning, stacklevel=2) - self._csdata = None - return self - def _get_csdata_type(self): """Get C# result object type.""" + if len(self) == 0: + raise ValueError("Cannot determine C# data type from empty results") return type(self[0]._csdata) - def _verify_data(func: Callable): + @staticmethod # pylint: disable=no-self-argument + def _verify_data(func: Callable) -> Callable: """Check whether `_csdata` can be passed to helper method.""" + def verify_data(self, *args): + if self._csdata is None: + # Use a generic name when func.__name__ is not available + func_name = getattr(func, "__name__", "method") + raise ValueError( + f"Cannot {func_name}() after done() has been called. Call reload() first." + ) + if not isinstance(self._csdata, Iterable) or len(self) < 1: - raise ValueError(f"Cannot {func.__name__}() an empty result.") + # Use a generic name when func.__name__ is not available + func_name = getattr(func, "__name__", "method") + raise ValueError(f"Cannot {func_name}() an empty result.") if not issubclass(self._get_csdata_type(), CsResultBase): raise TypeError( @@ -74,23 +77,52 @@ def verify_data(self, *args): @_verify_data def __add__(self, other: "IndicatorResults"): - return self.__class__(list(self._csdata).__add__(list(other._csdata)), self._wrapper_class) + """Concatenate two IndicatorResults.""" + if not isinstance(other, IndicatorResults): + raise TypeError("Can only add IndicatorResults to IndicatorResults") + return self.__class__( + list(self._csdata).__add__(list(other._csdata)), self._wrapper_class + ) @_verify_data def __mul__(self, value: int): + """Repeat IndicatorResults.""" + if not isinstance(value, int): + raise TypeError("Can only multiply IndicatorResults by integer") return self.__class__(list(self._csdata).__mul__(value), self._wrapper_class) @_verify_data - def remove_warmup_periods(self, remove_periods: int): + def remove_warmup_periods(self, remove_periods: int) -> "IndicatorResults": """Remove a specific quantity of results from the beginning of the results list.""" if not isinstance(remove_periods, int): raise TypeError("remove_periods must be an integer.") + if remove_periods < 0: + raise ValueError("remove_periods must be non-negative.") + + if remove_periods >= len(self): + return self.__class__([], self._wrapper_class) + return self.__class__(list(self._csdata)[remove_periods:], self._wrapper_class) def find(self, lookup_date: PyDateTime) -> Optional[_T]: - """Find indicator values on a specific date. It returns `None` if no result found.""" + """ + Find indicator values on a specific date. + Returns `None` if no result found. + + Args: + lookup_date: The date to search for + + Returns: + The result for the given date or None if not found + """ if not isinstance(lookup_date, PyDateTime): raise TypeError("lookup_date must be an instance of datetime.datetime.") - return next((r for r in self if r.date == lookup_date), None) + # Linear search (result sets are usually small enough that this is sufficient) + # First try matching only the calendar date (ignoring time) for convenience. + # If that attribute access fails, fall back to exact datetime comparison. + try: + return next((r for r in self if r.date.date() == lookup_date.date()), None) + except (AttributeError, TypeError): + return next((r for r in self if r.date == lookup_date), None) diff --git a/stock_indicators/indicators/connors_rsi.py b/stock_indicators/indicators/connors_rsi.py index 1e0af0ec..73295d46 100644 --- a/stock_indicators/indicators/connors_rsi.py +++ b/stock_indicators/indicators/connors_rsi.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_connors_rsi(quotes: Iterable[Quote], rsi_periods: int = 3, - streak_periods: int = 2, rank_periods: int = 100): +def get_connors_rsi( + quotes: Iterable[Quote], + rsi_periods: int = 3, + streak_periods: int = 2, + rank_periods: int = 100, +): """Get Connors RSI calculated. Connors RSI is a composite oscillator that incorporates @@ -35,8 +39,9 @@ def get_connors_rsi(quotes: Iterable[Quote], rsi_periods: int = 3, - [Connors RSI Reference](https://python.stockindicators.dev/indicators/ConnorsRsi/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetConnorsRsi[Quote](CsList(Quote, quotes), rsi_periods, - streak_periods, rank_periods) + results = CsIndicator.GetConnorsRsi[Quote]( + CsList(Quote, quotes), rsi_periods, streak_periods, rank_periods + ) return ConnorsRSIResults(results, ConnorsRSIResult) @@ -81,6 +86,8 @@ def connors_rsi(self, value): _T = TypeVar("_T", bound=ConnorsRSIResult) + + class ConnorsRSIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Connors RSI results. diff --git a/stock_indicators/indicators/correlation.py b/stock_indicators/indicators/correlation.py index 630d771e..6bdbbb1b 100644 --- a/stock_indicators/indicators/correlation.py +++ b/stock_indicators/indicators/correlation.py @@ -3,12 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_correlation(quotes_a: Iterable[Quote], quotes_b: Iterable[Quote], - lookback_periods: int): +def get_correlation( + quotes_a: Iterable[Quote], quotes_b: Iterable[Quote], lookback_periods: int +): """Get Correlation Coefficient calculated. Correlation Coefficient between two quote histories, based on Close price. @@ -31,8 +32,9 @@ def get_correlation(quotes_a: Iterable[Quote], quotes_b: Iterable[Quote], - [Correlation Coefficient Reference](https://python.stockindicators.dev/indicators/Correlation/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetCorrelation[Quote](CsList(Quote, quotes_a), CsList(Quote, quotes_b), - lookback_periods) + results = CsIndicator.GetCorrelation[Quote]( + CsList(Quote, quotes_a), CsList(Quote, quotes_b), lookback_periods + ) return CorrelationResults(results, CorrelationResult) @@ -83,6 +85,8 @@ def r_squared(self, value): _T = TypeVar("_T", bound=CorrelationResult) + + class CorrelationResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Correlation Coefficient results. diff --git a/stock_indicators/indicators/dema.py b/stock_indicators/indicators/dema.py index bc7b54af..64253d04 100644 --- a/stock_indicators/indicators/dema.py +++ b/stock_indicators/indicators/dema.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_dema(quotes: Iterable[Quote], lookback_periods: int): @@ -46,6 +46,8 @@ def dema(self, value): _T = TypeVar("_T", bound=DEMAResult) + + class DEMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Double Exponential Moving Average (DEMA) results. diff --git a/stock_indicators/indicators/doji.py b/stock_indicators/indicators/doji.py index 23874df7..aa567457 100644 --- a/stock_indicators/indicators/doji.py +++ b/stock_indicators/indicators/doji.py @@ -28,5 +28,7 @@ def get_doji(quotes: Iterable[Quote], max_price_change_percent: float = 0.1): - [Doji Reference](https://python.stockindicators.dev/indicators/Doji/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetDoji[Quote](CsList(Quote, quotes), max_price_change_percent) + results = CsIndicator.GetDoji[Quote]( + CsList(Quote, quotes), max_price_change_percent + ) return CandleResults(results, CandleResult) diff --git a/stock_indicators/indicators/donchian.py b/stock_indicators/indicators/donchian.py index f577e109..f033ac47 100644 --- a/stock_indicators/indicators/donchian.py +++ b/stock_indicators/indicators/donchian.py @@ -2,12 +2,12 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_donchian(quotes: Iterable[Quote], lookback_periods: int = 20): @@ -73,6 +73,8 @@ def width(self, value): _T = TypeVar("_T", bound=DonchianResult) + + class DonchianResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Donchian Channels results. diff --git a/stock_indicators/indicators/dpo.py b/stock_indicators/indicators/dpo.py index 6d2d9123..18a96d35 100644 --- a/stock_indicators/indicators/dpo.py +++ b/stock_indicators/indicators/dpo.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_dpo(quotes: Iterable[Quote], lookback_periods: int): @@ -55,6 +55,8 @@ def dpo(self, value): _T = TypeVar("_T", bound=DPOResult) + + class DPOResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Detrended Price Oscillator (DPO) results. diff --git a/stock_indicators/indicators/dynamic.py b/stock_indicators/indicators/dynamic.py index e6a9565c..ae3279f8 100644 --- a/stock_indicators/indicators/dynamic.py +++ b/stock_indicators/indicators/dynamic.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_dynamic(quotes: Iterable[Quote], lookback_periods: int, k_factor: float = 0.6): @@ -30,7 +30,9 @@ def get_dynamic(quotes: Iterable[Quote], lookback_periods: int, k_factor: float - [McGinley Dynamic Reference](https://python.stockindicators.dev/indicators/Dynamic/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetDynamic[Quote](CsList(Quote, quotes), lookback_periods, k_factor) + results = CsIndicator.GetDynamic[Quote]( + CsList(Quote, quotes), lookback_periods, k_factor + ) return DynamicResults(results, DynamicResult) @@ -49,6 +51,8 @@ def dynamic(self, value): _T = TypeVar("_T", bound=DynamicResult) + + class DynamicResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of McGinley Dynamic results. diff --git a/stock_indicators/indicators/elder_ray.py b/stock_indicators/indicators/elder_ray.py index 1ac3a97c..2eaa9967 100644 --- a/stock_indicators/indicators/elder_ray.py +++ b/stock_indicators/indicators/elder_ray.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_elder_ray(quotes: Iterable[Quote], lookback_periods: int = 13): @@ -62,6 +62,8 @@ def bear_power(self, value): _T = TypeVar("_T", bound=ElderRayResult) + + class ElderRayResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Elder-ray Index results. diff --git a/stock_indicators/indicators/ema.py b/stock_indicators/indicators/ema.py index 8de55047..1a097f8e 100644 --- a/stock_indicators/indicators/ema.py +++ b/stock_indicators/indicators/ema.py @@ -3,12 +3,15 @@ from stock_indicators._cslib import CsIndicator from stock_indicators.indicators.common.enums import CandlePart from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_ema(quotes: Iterable[Quote], lookback_periods: int, - candle_part: CandlePart = CandlePart.CLOSE): +def get_ema( + quotes: Iterable[Quote], + lookback_periods: int, + candle_part: CandlePart = CandlePart.CLOSE, +): """Get EMA calculated. Exponential Moving Average (EMA) of the Close price. @@ -31,7 +34,7 @@ def get_ema(quotes: Iterable[Quote], lookback_periods: int, - [EMA Reference](https://python.stockindicators.dev/indicators/Ema/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - quotes = Quote.use(quotes, candle_part) # Error occurs if not assigned to local var. + quotes = Quote.use(quotes, candle_part) # pylint: disable=no-member # Error occurs if not assigned to local var. ema_list = CsIndicator.GetEma(quotes, lookback_periods) return EMAResults(ema_list, EMAResult) @@ -51,6 +54,8 @@ def ema(self, value): _T = TypeVar("_T", bound=EMAResult) + + class EMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of EMA(Exponential Moving Average) results. diff --git a/stock_indicators/indicators/epma.py b/stock_indicators/indicators/epma.py index 5616cb83..7bc0e2cd 100644 --- a/stock_indicators/indicators/epma.py +++ b/stock_indicators/indicators/epma.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_epma(quotes: Iterable[Quote], lookback_periods: int): @@ -48,6 +48,8 @@ def epma(self, value): _T = TypeVar("_T", bound=EPMAResult) + + class EPMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Endpoint Moving Average (EPMA) results. diff --git a/stock_indicators/indicators/fcb.py b/stock_indicators/indicators/fcb.py index a7c6615b..ed29240a 100644 --- a/stock_indicators/indicators/fcb.py +++ b/stock_indicators/indicators/fcb.py @@ -2,12 +2,12 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_fcb(quotes: Iterable[Quote], window_span: int = 2): @@ -59,6 +59,8 @@ def lower_band(self, value): _T = TypeVar("_T", bound=FCBResult) + + class FCBResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Fractal Chaos Bands (FCB) results. diff --git a/stock_indicators/indicators/fisher_transform.py b/stock_indicators/indicators/fisher_transform.py index b208c29d..637b7ef5 100644 --- a/stock_indicators/indicators/fisher_transform.py +++ b/stock_indicators/indicators/fisher_transform.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_fisher_transform(quotes: Iterable[Quote], lookback_periods: int = 10): @@ -29,7 +29,9 @@ def get_fisher_transform(quotes: Iterable[Quote], lookback_periods: int = 10): - [Fisher Transform Reference](https://python.stockindicators.dev/indicators/FisherTransform/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetFisherTransform[Quote](CsList(Quote, quotes), lookback_periods) + results = CsIndicator.GetFisherTransform[Quote]( + CsList(Quote, quotes), lookback_periods + ) return FisherTransformResults(results, FisherTransformResult) @@ -56,6 +58,8 @@ def trigger(self, value): _T = TypeVar("_T", bound=FisherTransformResult) + + class FisherTransformResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Ehlers Fisher Transform results. diff --git a/stock_indicators/indicators/force_index.py b/stock_indicators/indicators/force_index.py index ec7fe444..e125dab9 100644 --- a/stock_indicators/indicators/force_index.py +++ b/stock_indicators/indicators/force_index.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_force_index(quotes: Iterable[Quote], lookback_periods: int): @@ -46,6 +46,8 @@ def force_index(self, value): _T = TypeVar("_T", bound=ForceIndexResult) + + class ForceIndexResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Force Index results. diff --git a/stock_indicators/indicators/fractal.py b/stock_indicators/indicators/fractal.py index 18548d63..7c676a58 100644 --- a/stock_indicators/indicators/fractal.py +++ b/stock_indicators/indicators/fractal.py @@ -2,20 +2,30 @@ from typing import Iterable, Optional, TypeVar, overload from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import EndType from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase @overload -def get_fractal(quotes: Iterable[Quote], window_span: int = 2, end_type = EndType.HIGH_LOW) -> "FractalResults[FractalResult]": ... +def get_fractal( + quotes: Iterable[Quote], window_span: int = 2, end_type=EndType.HIGH_LOW +) -> "FractalResults[FractalResult]": ... + + @overload -def get_fractal(quotes: Iterable[Quote], left_span: int, right_span: int, end_type = EndType.HIGH_LOW) -> "FractalResults[FractalResult]": ... -def get_fractal(quotes, left_span = None, right_span = EndType.HIGH_LOW, end_type = EndType.HIGH_LOW): +def get_fractal( + quotes: Iterable[Quote], left_span: int, right_span: int, end_type=EndType.HIGH_LOW +) -> "FractalResults[FractalResult]": ... + + +def get_fractal( + quotes, left_span=None, right_span=EndType.HIGH_LOW, end_type=EndType.HIGH_LOW +): """Get Williams Fractal calculated. Williams Fractal is a retrospective price pattern that @@ -46,10 +56,15 @@ def get_fractal(quotes, left_span = None, right_span = EndType.HIGH_LOW, end_typ - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ if isinstance(right_span, EndType): - if left_span is None: left_span = 2 - fractal_results = CsIndicator.GetFractal[Quote](CsList(Quote, quotes), left_span, right_span.cs_value) + if left_span is None: + left_span = 2 + fractal_results = CsIndicator.GetFractal[Quote]( + CsList(Quote, quotes), left_span, right_span.cs_value + ) else: - fractal_results = CsIndicator.GetFractal[Quote](CsList(Quote, quotes), left_span, right_span, end_type.cs_value) + fractal_results = CsIndicator.GetFractal[Quote]( + CsList(Quote, quotes), left_span, right_span, end_type.cs_value + ) return FractalResults(fractal_results, FractalResult) @@ -77,6 +92,8 @@ def fractal_bull(self, value): _T = TypeVar("_T", bound=FractalResult) + + class FractalResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Williams Fractal results. diff --git a/stock_indicators/indicators/gator.py b/stock_indicators/indicators/gator.py index 885dcc98..a573bd96 100644 --- a/stock_indicators/indicators/gator.py +++ b/stock_indicators/indicators/gator.py @@ -4,8 +4,9 @@ from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.alligator import AlligatorResult from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase + @overload def get_gator(quotes: Iterable[Quote]) -> "GatorResults[GatorResult]": ... @@ -32,12 +33,12 @@ def get_gator(quotes): results = CsIndicator.GetGator[Quote](CsList(Quote, quotes)) else: # Get C# objects. - if isinstance(quotes, IndicatorResults) and quotes._csdata is not None: - cs_results = quotes._csdata + if isinstance(quotes, IndicatorResults): + # Use the C# data directly if available + results = CsIndicator.GetGator(quotes._csdata) else: - cs_results = [ q._csdata for q in quotes ] - - results = CsIndicator.GetGator(CsList(type(cs_results[0]), cs_results)) + cs_results = [q._csdata for q in quotes] + results = CsIndicator.GetGator(CsList(type(cs_results[0]), cs_results)) return GatorResults(results, GatorResult) @@ -80,6 +81,8 @@ def is_lower_expanding(self, value): _T = TypeVar("_T", bound=GatorResult) + + class GatorResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Gator Oscillator results. diff --git a/stock_indicators/indicators/heikin_ashi.py b/stock_indicators/indicators/heikin_ashi.py index 02674d0c..fd56345b 100644 --- a/stock_indicators/indicators/heikin_ashi.py +++ b/stock_indicators/indicators/heikin_ashi.py @@ -2,11 +2,11 @@ from typing import Iterable, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_heikin_ashi(quotes: Iterable[Quote]): @@ -77,6 +77,8 @@ def volume(self, value): _T = TypeVar("_T", bound=HeikinAshiResult) + + class HeikinAshiResults(IndicatorResults[_T]): """ A wrapper class for the list of Heikin-Ashi results. diff --git a/stock_indicators/indicators/hma.py b/stock_indicators/indicators/hma.py index 7738ea26..9926d3f0 100644 --- a/stock_indicators/indicators/hma.py +++ b/stock_indicators/indicators/hma.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_hma(quotes: Iterable[Quote], lookback_periods: int): @@ -47,6 +47,8 @@ def hma(self, value): _T = TypeVar("_T", bound=HMAResult) + + class HMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Hull Moving Average (HMA) results. diff --git a/stock_indicators/indicators/ht_trendline.py b/stock_indicators/indicators/ht_trendline.py index 79843bb5..75a7b5fd 100644 --- a/stock_indicators/indicators/ht_trendline.py +++ b/stock_indicators/indicators/ht_trendline.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_ht_trendline(quotes: Iterable[Quote]): @@ -60,6 +60,8 @@ def smooth_price(self, value): _T = TypeVar("_T", bound=HTTrendlineResult) + + class HTTrendlineResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Hilbert Transform Instantaneous Trendline (HTL) results. diff --git a/stock_indicators/indicators/hurst.py b/stock_indicators/indicators/hurst.py index cea24f49..3b0f3c72 100644 --- a/stock_indicators/indicators/hurst.py +++ b/stock_indicators/indicators/hurst.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_hurst(quotes: Iterable[Quote], lookback_periods: int = 100): @@ -47,6 +47,8 @@ def hurst_exponent(self, value): _T = TypeVar("_T", bound=HurstResult) + + class HurstResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Hurst Exponent results. diff --git a/stock_indicators/indicators/ichimoku.py b/stock_indicators/indicators/ichimoku.py index 5a11444d..dd5e7f26 100644 --- a/stock_indicators/indicators/ichimoku.py +++ b/stock_indicators/indicators/ichimoku.py @@ -2,28 +2,56 @@ from typing import Iterable, Optional, TypeVar, overload from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase @overload -def get_ichimoku(quotes: Iterable[Quote], tenkan_periods: int = 9, - kijun_periods: int = 26, senkou_b_periods: int = 52) -> "IchimokuResults[IchimokuResult]": ... +def get_ichimoku( + quotes: Iterable[Quote], + tenkan_periods: int = 9, + kijun_periods: int = 26, + senkou_b_periods: int = 52, +) -> "IchimokuResults[IchimokuResult]": ... + + @overload -def get_ichimoku(quotes: Iterable[Quote], tenkan_periods: int, - kijun_periods: int, senkou_b_periods: int, - offset_periods: int) -> "IchimokuResults[IchimokuResult]": ... +def get_ichimoku( + quotes: Iterable[Quote], + tenkan_periods: int, + kijun_periods: int, + senkou_b_periods: int, + *, + offset_periods: int, +) -> "IchimokuResults[IchimokuResult]": ... + + @overload -def get_ichimoku(quotes: Iterable[Quote], tenkan_periods: int, - kijun_periods: int, senkou_b_periods: int, - senkou_offset: int, chikou_offset: int) -> "IchimokuResults[IchimokuResult]": ... -def get_ichimoku(quotes: Iterable[Quote], tenkan_periods: int = None, - kijun_periods: int = None, senkou_b_periods: int = None, - senkou_offset: int = None, chikou_offset: int = None): +def get_ichimoku( + quotes: Iterable[Quote], + tenkan_periods: int, + kijun_periods: int, + senkou_b_periods: int, + *, + senkou_offset: int, + chikou_offset: int, +) -> "IchimokuResults[IchimokuResult]": ... + + +def get_ichimoku( + quotes: Iterable[Quote], + tenkan_periods: int = 9, + kijun_periods: int = 26, + senkou_b_periods: int = 52, + senkou_offset: Optional[int] = None, + chikou_offset: Optional[int] = None, + *, + offset_periods: Optional[int] = None, +) -> "IchimokuResults[IchimokuResult]": # pylint: disable=too-many-positional-arguments """Get Ichimoku Cloud calculated. Ichimoku Cloud, also known as Ichimoku Kinkō Hyō, is a collection of indicators @@ -60,17 +88,27 @@ def get_ichimoku(quotes: Iterable[Quote], tenkan_periods: int = None, - [Ichimoku Cloud Reference](https://python.stockindicators.dev/indicators/Ichimoku/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ + # Normalize offset_periods into senkou_offset and chikou_offset + if offset_periods is not None: + if senkou_offset is None: + senkou_offset = offset_periods + if chikou_offset is None: + chikou_offset = offset_periods + + # Apply default logic when offsets are still None if chikou_offset is None: if senkou_offset is None: - if tenkan_periods is None: tenkan_periods = 9 - if kijun_periods is None: kijun_periods = 26 - if senkou_b_periods is None: senkou_b_periods = 52 senkou_offset = kijun_periods chikou_offset = senkou_offset - results = CsIndicator.GetIchimoku[Quote](CsList(Quote, quotes), tenkan_periods, - kijun_periods, senkou_b_periods, - senkou_offset, chikou_offset) + results = CsIndicator.GetIchimoku[Quote]( + CsList(Quote, quotes), + tenkan_periods, + kijun_periods, + senkou_b_periods, + senkou_offset, + chikou_offset, + ) return IchimokuResults(results, IchimokuResult) @@ -121,6 +159,8 @@ def chikou_span(self, value): _T = TypeVar("_T", bound=IchimokuResult) + + class IchimokuResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Ichimoku Cloud results. diff --git a/stock_indicators/indicators/kama.py b/stock_indicators/indicators/kama.py index 8fefb9fc..720e3fea 100644 --- a/stock_indicators/indicators/kama.py +++ b/stock_indicators/indicators/kama.py @@ -3,15 +3,19 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_kama(quotes: Iterable[Quote], er_periods: int = 10, - fast_periods: int = 2, slow_periods: int = 30): +def get_kama( + quotes: Iterable[Quote], + er_periods: int = 10, + fast_periods: int = 2, + slow_periods: int = 30, +): """Get KAMA calculated. - Kaufman’s Adaptive Moving Average (KAMA) is an volatility + Kaufman's Adaptive Moving Average (KAMA) is an volatility adaptive moving average of Close price over configurable lookback periods. Parameters: @@ -35,14 +39,15 @@ def get_kama(quotes: Iterable[Quote], er_periods: int = 10, - [KAMA Reference](https://python.stockindicators.dev/indicators/Kama/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetKama[Quote](CsList(Quote, quotes), er_periods, - fast_periods, slow_periods) + results = CsIndicator.GetKama[Quote]( + CsList(Quote, quotes), er_periods, fast_periods, slow_periods + ) return KAMAResults(results, KAMAResult) class KAMAResult(ResultBase): """ - A wrapper class for a single unit of Kaufman’s Adaptive Moving Average (KAMA) results. + A wrapper class for a single unit of Kaufman's Adaptive Moving Average (KAMA) results. """ @property @@ -63,9 +68,11 @@ def kama(self, value): _T = TypeVar("_T", bound=KAMAResult) + + class KAMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ - A wrapper class for the list of Kaufman’s Adaptive Moving Average (KAMA) results. + A wrapper class for the list of Kaufman's Adaptive Moving Average (KAMA) results. It is exactly same with built-in `list` except for that it provides some useful helper methods written in CSharp implementation. """ diff --git a/stock_indicators/indicators/keltner.py b/stock_indicators/indicators/keltner.py index 57b79399..94920fbd 100644 --- a/stock_indicators/indicators/keltner.py +++ b/stock_indicators/indicators/keltner.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_keltner(quotes: Iterable[Quote], ema_periods: int = 20, - multiplier: float = 2, atr_periods: int = 10): +def get_keltner( + quotes: Iterable[Quote], + ema_periods: int = 20, + multiplier: float = 2, + atr_periods: int = 10, +): """Get Keltner Channels calculated. Keltner Channels are based on an EMA centerline andATR band widths. @@ -35,8 +39,9 @@ def get_keltner(quotes: Iterable[Quote], ema_periods: int = 20, - [Keltner Channels Reference](https://python.stockindicators.dev/indicators/Keltner/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetKeltner[Quote](CsList(Quote, quotes), ema_periods, - multiplier, atr_periods) + results = CsIndicator.GetKeltner[Quote]( + CsList(Quote, quotes), ema_periods, multiplier, atr_periods + ) return KeltnerResults(results, KeltnerResult) @@ -79,6 +84,8 @@ def width(self, value): _T = TypeVar("_T", bound=KeltnerResult) + + class KeltnerResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Keltner Channels results. diff --git a/stock_indicators/indicators/kvo.py b/stock_indicators/indicators/kvo.py index cea328dc..9bcb2d8f 100644 --- a/stock_indicators/indicators/kvo.py +++ b/stock_indicators/indicators/kvo.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_kvo(quotes: Iterable[Quote], fast_periods: int = 34, - slow_periods: int = 55, signal_periods: int = 13): +def get_kvo( + quotes: Iterable[Quote], + fast_periods: int = 34, + slow_periods: int = 55, + signal_periods: int = 13, +): """Get KVO calculated. Klinger Volume Oscillator (KVO) depicts volume-based divergence @@ -35,8 +39,9 @@ def get_kvo(quotes: Iterable[Quote], fast_periods: int = 34, - [KVO Reference](https://python.stockindicators.dev/indicators/Kvo/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetKvo[Quote](CsList(Quote, quotes), fast_periods, - slow_periods, signal_periods) + results = CsIndicator.GetKvo[Quote]( + CsList(Quote, quotes), fast_periods, slow_periods, signal_periods + ) return KVOResults(results, KVOResult) @@ -63,6 +68,8 @@ def signal(self, value): _T = TypeVar("_T", bound=KVOResult) + + class KVOResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Klinger Volume Oscillator (KVO) results. diff --git a/stock_indicators/indicators/ma_envelopes.py b/stock_indicators/indicators/ma_envelopes.py index 55ccccd7..5be93337 100644 --- a/stock_indicators/indicators/ma_envelopes.py +++ b/stock_indicators/indicators/ma_envelopes.py @@ -4,12 +4,16 @@ from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.enums import MAType from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_ma_envelopes(quotes: Iterable[Quote], lookback_periods: int, - percent_offset: float = 2.5, ma_type: MAType = MAType.SMA): +def get_ma_envelopes( + quotes: Iterable[Quote], + lookback_periods: int, + percent_offset: float = 2.5, + ma_type: MAType = MAType.SMA, +): """Get Moving Average Envelopes calculated. Moving Average Envelopes is a price band overlay that is offset @@ -36,8 +40,9 @@ def get_ma_envelopes(quotes: Iterable[Quote], lookback_periods: int, - [Moving Average Envelopes Reference](https://python.stockindicators.dev/indicators/MaEnvelopes/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetMaEnvelopes[Quote](CsList(Quote, quotes), lookback_periods, - percent_offset, ma_type.cs_value) + results = CsIndicator.GetMaEnvelopes[Quote]( + CsList(Quote, quotes), lookback_periods, percent_offset, ma_type.cs_value + ) return MAEnvelopeResults(results, MAEnvelopeResult) @@ -72,6 +77,8 @@ def lower_envelope(self, value): _T = TypeVar("_T", bound=MAEnvelopeResult) + + class MAEnvelopeResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Moving Average Envelopes results. diff --git a/stock_indicators/indicators/macd.py b/stock_indicators/indicators/macd.py index 647537b6..df3574dd 100644 --- a/stock_indicators/indicators/macd.py +++ b/stock_indicators/indicators/macd.py @@ -3,13 +3,17 @@ from stock_indicators._cslib import CsIndicator from stock_indicators.indicators.common.enums import CandlePart from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_macd(quotes: Iterable[Quote], fast_periods: int = 12, - slow_periods: int = 26, signal_periods: int = 9, - candle_part: CandlePart = CandlePart.CLOSE): +def get_macd( + quotes: Iterable[Quote], + fast_periods: int = 12, + slow_periods: int = 26, + signal_periods: int = 9, + candle_part: CandlePart = CandlePart.CLOSE, +): """Get MACD calculated. Moving Average Convergence/Divergence (MACD) is a simple oscillator view @@ -39,9 +43,11 @@ def get_macd(quotes: Iterable[Quote], fast_periods: int = 12, - [MACD Reference](https://python.stockindicators.dev/indicators/Macd/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - quotes = Quote.use(quotes, candle_part) # Error occurs if not assigned to local var. - macd_results = CsIndicator.GetMacd(quotes, fast_periods, - slow_periods, signal_periods) + # pylint: disable=no-member # Error occurs if not assigned to local var. + quotes = Quote.use(quotes, candle_part) + macd_results = CsIndicator.GetMacd( + quotes, fast_periods, slow_periods, signal_periods + ) return MACDResults(macd_results, MACDResult) @@ -92,7 +98,9 @@ def slow_ema(self, value): _T = TypeVar("_T", bound=MACDResult) -class MACDResults(CondenseMixin, RemoveWarmupMixin ,IndicatorResults[_T]): + + +class MACDResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of MACD(Moving Average Convergence/Divergence) results. It is exactly same with built-in `list` except for that it provides diff --git a/stock_indicators/indicators/mama.py b/stock_indicators/indicators/mama.py index f48e7cde..369041d3 100644 --- a/stock_indicators/indicators/mama.py +++ b/stock_indicators/indicators/mama.py @@ -3,12 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_mama(quotes: Iterable[Quote], fast_limit: float = 0.5, - slow_limit: float = 0.05): +def get_mama( + quotes: Iterable[Quote], fast_limit: float = 0.5, slow_limit: float = 0.05 +): """Get MAMA calculated. MESA Adaptive Moving Average (MAMA) is a 5-period @@ -32,8 +33,7 @@ def get_mama(quotes: Iterable[Quote], fast_limit: float = 0.5, - [MAMA Reference](https://python.stockindicators.dev/indicators/Mama/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetMama[Quote](CsList(Quote, quotes), fast_limit, - slow_limit) + results = CsIndicator.GetMama[Quote](CsList(Quote, quotes), fast_limit, slow_limit) return MAMAResults(results, MAMAResult) @@ -60,6 +60,8 @@ def fama(self, value): _T = TypeVar("_T", bound=MAMAResult) + + class MAMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of MESA Adaptive Moving Average (MAMA) results. diff --git a/stock_indicators/indicators/mfi.py b/stock_indicators/indicators/mfi.py index f610c4d2..90edc546 100644 --- a/stock_indicators/indicators/mfi.py +++ b/stock_indicators/indicators/mfi.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_mfi(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -47,6 +47,8 @@ def mfi(self, value): _T = TypeVar("_T", bound=MFIResult) + + class MFIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Money Flow Index (MFI) results. diff --git a/stock_indicators/indicators/obv.py b/stock_indicators/indicators/obv.py index 0d3754cc..88ed0041 100644 --- a/stock_indicators/indicators/obv.py +++ b/stock_indicators/indicators/obv.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_obv(quotes: Iterable[Quote], sma_periods: Optional[int] = None): @@ -55,6 +55,8 @@ def obv_sma(self, value): _T = TypeVar("_T", bound=OBVResult) + + class OBVResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of On-balance Volume (OBV) results. diff --git a/stock_indicators/indicators/parabolic_sar.py b/stock_indicators/indicators/parabolic_sar.py index b3bd3b79..2728be25 100644 --- a/stock_indicators/indicators/parabolic_sar.py +++ b/stock_indicators/indicators/parabolic_sar.py @@ -3,18 +3,26 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase @overload -def get_parabolic_sar(quotes: Iterable[Quote], acceleration_step: float = 0.02, - max_acceleration_factor: float = 0.2) -> "ParabolicSARResults[ParabolicSARResult]": ... +def get_parabolic_sar( + quotes: Iterable[Quote], + acceleration_step: float = 0.02, + max_acceleration_factor: float = 0.2, +) -> "ParabolicSARResults[ParabolicSARResult]": ... @overload -def get_parabolic_sar(quotes: Iterable[Quote], acceleration_step: float, - max_acceleration_factor: float, initial_factor: float) -> "ParabolicSARResults[ParabolicSARResult]": ... -def get_parabolic_sar(quotes, acceleration_step = None, - max_acceleration_factor = None, initial_factor = None): +def get_parabolic_sar( + quotes: Iterable[Quote], + acceleration_step: float, + max_acceleration_factor: float, + initial_factor: float, +) -> "ParabolicSARResults[ParabolicSARResult]": ... +def get_parabolic_sar( + quotes, acceleration_step=None, max_acceleration_factor=None, initial_factor=None +): """Get Parabolic SAR calculated. Parabolic SAR (stop and reverse) is a price-time based indicator @@ -42,13 +50,20 @@ def get_parabolic_sar(quotes, acceleration_step = None, - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ if initial_factor is None: - if acceleration_step is None: acceleration_step = 0.02 - if max_acceleration_factor is None: max_acceleration_factor = 0.2 - results = CsIndicator.GetParabolicSar[Quote](CsList(Quote, quotes), acceleration_step, - max_acceleration_factor) + if acceleration_step is None: + acceleration_step = 0.02 + if max_acceleration_factor is None: + max_acceleration_factor = 0.2 + results = CsIndicator.GetParabolicSar[Quote]( + CsList(Quote, quotes), acceleration_step, max_acceleration_factor + ) else: - results = CsIndicator.GetParabolicSar[Quote](CsList(Quote, quotes), acceleration_step, - max_acceleration_factor, initial_factor) + results = CsIndicator.GetParabolicSar[Quote]( + CsList(Quote, quotes), + acceleration_step, + max_acceleration_factor, + initial_factor, + ) return ParabolicSARResults(results, ParabolicSARResult) @@ -76,6 +91,8 @@ def is_reversal(self, value): _T = TypeVar("_T", bound=ParabolicSARResult) + + class ParabolicSARResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Parabolic SAR(stop and reverse) results. diff --git a/stock_indicators/indicators/pivot_points.py b/stock_indicators/indicators/pivot_points.py index e7379b24..5537a182 100644 --- a/stock_indicators/indicators/pivot_points.py +++ b/stock_indicators/indicators/pivot_points.py @@ -2,17 +2,20 @@ from typing import Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import PeriodSize, PivotPointType from stock_indicators.indicators.common.helpers import RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_pivot_points(quotes, window_size: PeriodSize, - point_type: PivotPointType = PivotPointType.STANDARD): +def get_pivot_points( + quotes, + window_size: PeriodSize, + point_type: PivotPointType = PivotPointType.STANDARD, +): """Get Pivot Points calculated. Pivot Points depict support and resistance levels, based on @@ -37,8 +40,9 @@ def get_pivot_points(quotes, window_size: PeriodSize, - [Pivot Points Reference](https://python.stockindicators.dev/indicators/PivotPoints/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetPivotPoints[Quote](CsList(Quote, quotes), window_size.cs_value, - point_type.cs_value) + results = CsIndicator.GetPivotPoints[Quote]( + CsList(Quote, quotes), window_size.cs_value, point_type.cs_value + ) return PivotPointsResults(results, PivotPointsResult) @@ -121,6 +125,8 @@ def s4(self, value): _T = TypeVar("_T", bound=PivotPointsResult) + + class PivotPointsResults(RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Pivot Points results. diff --git a/stock_indicators/indicators/pivots.py b/stock_indicators/indicators/pivots.py index 26540ea9..afa55af4 100644 --- a/stock_indicators/indicators/pivots.py +++ b/stock_indicators/indicators/pivots.py @@ -2,18 +2,22 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import EndType, PivotTrend from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_pivots(quotes: Iterable[Quote], left_span: int = 2, - right_span: int = 2, max_trend_periods: int = 20, - end_type: EndType = EndType.HIGH_LOW): +def get_pivots( + quotes: Iterable[Quote], + left_span: int = 2, + right_span: int = 2, + max_trend_periods: int = 20, + end_type: EndType = EndType.HIGH_LOW, +): """Get Pivots calculated. Pivots is an extended version of Williams Fractal that includes @@ -44,9 +48,13 @@ def get_pivots(quotes: Iterable[Quote], left_span: int = 2, - [Pivots Reference](https://python.stockindicators.dev/indicators/Pivots/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetPivots[Quote](CsList(Quote, quotes), left_span, - right_span, max_trend_periods, - end_type.cs_value) + results = CsIndicator.GetPivots[Quote]( + CsList(Quote, quotes), + left_span, + right_span, + max_trend_periods, + end_type.cs_value, + ) return PivotsResults(results, PivotsResult) @@ -109,6 +117,8 @@ def low_trend(self, value): _T = TypeVar("_T", bound=PivotsResult) + + class PivotsResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Pivots results. diff --git a/stock_indicators/indicators/pmo.py b/stock_indicators/indicators/pmo.py index 1e42dbd9..5af2697a 100644 --- a/stock_indicators/indicators/pmo.py +++ b/stock_indicators/indicators/pmo.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_pmo(quotes: Iterable[Quote], time_periods: int = 35, - smooth_periods: int = 20, signal_periods: int = 10): +def get_pmo( + quotes: Iterable[Quote], + time_periods: int = 35, + smooth_periods: int = 20, + signal_periods: int = 10, +): """Get PMO calculated. Price Momentum Oscillator (PMO) is double-smoothed ROC @@ -35,8 +39,9 @@ def get_pmo(quotes: Iterable[Quote], time_periods: int = 35, - [PMO Reference](https://python.stockindicators.dev/indicators/Pmo/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetPmo[Quote](CsList(Quote, quotes), time_periods, - smooth_periods, signal_periods) + results = CsIndicator.GetPmo[Quote]( + CsList(Quote, quotes), time_periods, smooth_periods, signal_periods + ) return PMOResults(results, PMOResult) @@ -63,6 +68,8 @@ def signal(self, value): _T = TypeVar("_T", bound=PMOResult) + + class PMOResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Price Momentum Oscillator (PMO) results. diff --git a/stock_indicators/indicators/prs.py b/stock_indicators/indicators/prs.py index 9eba1070..f54ce2b1 100644 --- a/stock_indicators/indicators/prs.py +++ b/stock_indicators/indicators/prs.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_prs(eval_quotes: Iterable[Quote], base_quotes: Iterable[Quote], - lookback_periods: Optional[int] = None, sma_periods: Optional[int] = None): +def get_prs( + eval_quotes: Iterable[Quote], + base_quotes: Iterable[Quote], + lookback_periods: Optional[int] = None, + sma_periods: Optional[int] = None, +): """Get PRS calculated. Price Relative Strength (PRS), also called Comparative Relative Strength, @@ -40,8 +44,12 @@ def get_prs(eval_quotes: Iterable[Quote], base_quotes: Iterable[Quote], - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetPrs[Quote](CsList(Quote, eval_quotes), CsList(Quote, base_quotes), - lookback_periods, sma_periods) + results = CsIndicator.GetPrs[Quote]( + CsList(Quote, eval_quotes), + CsList(Quote, base_quotes), + lookback_periods, + sma_periods, + ) return PRSResults(results, PRSResult) diff --git a/stock_indicators/indicators/pvo.py b/stock_indicators/indicators/pvo.py index f1eba34f..46b33001 100644 --- a/stock_indicators/indicators/pvo.py +++ b/stock_indicators/indicators/pvo.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_pvo(quotes: Iterable[Quote], fast_periods: int = 12, - slow_periods: int = 26, signal_periods: int = 9): +def get_pvo( + quotes: Iterable[Quote], + fast_periods: int = 12, + slow_periods: int = 26, + signal_periods: int = 9, +): """Get PVO calculated. Percentage Volume Oscillator (PVO) is a simple oscillator view @@ -35,8 +39,9 @@ def get_pvo(quotes: Iterable[Quote], fast_periods: int = 12, - [PVO Reference](https://python.stockindicators.dev/indicators/Pvo/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetPvo[Quote](CsList(Quote, quotes), fast_periods, - slow_periods, signal_periods) + results = CsIndicator.GetPvo[Quote]( + CsList(Quote, quotes), fast_periods, slow_periods, signal_periods + ) return PVOResults(results, PVOResult) @@ -71,6 +76,8 @@ def histogram(self, value): _T = TypeVar("_T", bound=PVOResult) + + class PVOResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Percentage Volume Oscillator (PVO) results. diff --git a/stock_indicators/indicators/renko.py b/stock_indicators/indicators/renko.py index b06e12c1..17dbc872 100644 --- a/stock_indicators/indicators/renko.py +++ b/stock_indicators/indicators/renko.py @@ -2,16 +2,17 @@ from typing import Iterable, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import EndType -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_renko(quotes: Iterable[Quote], brick_size: float, - end_type: EndType = EndType.CLOSE): +def get_renko( + quotes: Iterable[Quote], brick_size: float, end_type: EndType = EndType.CLOSE +): """Get Renko Chart calculated. Renko Chart is a modified Japanese candlestick pattern @@ -35,13 +36,15 @@ def get_renko(quotes: Iterable[Quote], brick_size: float, - [Renko Chart Reference](https://python.stockindicators.dev/indicators/Renko/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetRenko[Quote](CsList(Quote, quotes), CsDecimal(brick_size), - end_type.cs_value) + results = CsIndicator.GetRenko[Quote]( + CsList(Quote, quotes), CsDecimal(brick_size), end_type.cs_value + ) return RenkoResults(results, RenkoResult) -def get_renko_atr(quotes: Iterable[Quote], atr_periods: int, - end_type: EndType = EndType.CLOSE): +def get_renko_atr( + quotes: Iterable[Quote], atr_periods: int, end_type: EndType = EndType.CLOSE +): """Get ATR Renko Chart calculated. The ATR Renko Chart is a modified Japanese candlestick pattern @@ -65,8 +68,9 @@ def get_renko_atr(quotes: Iterable[Quote], atr_periods: int, - [ATR Renko Chart Reference](https://python.stockindicators.dev/indicators/Renko/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetRenkoAtr[Quote](CsList(Quote, quotes), atr_periods, - end_type.cs_value) + results = CsIndicator.GetRenkoAtr[Quote]( + CsList(Quote, quotes), atr_periods, end_type.cs_value + ) return RenkoResults(results, RenkoResult) @@ -125,6 +129,8 @@ def is_up(self, value): _T = TypeVar("_T", bound=RenkoResult) + + class RenkoResults(IndicatorResults[_T]): """ A wrapper class for the list of Renko Chart results. diff --git a/stock_indicators/indicators/roc.py b/stock_indicators/indicators/roc.py index 98d473f4..5738ae61 100644 --- a/stock_indicators/indicators/roc.py +++ b/stock_indicators/indicators/roc.py @@ -3,11 +3,15 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_roc(quotes: Iterable[Quote], lookback_periods: int, sma_periods: int = None): +def get_roc( + quotes: Iterable[Quote], + lookback_periods: int, + sma_periods: Optional[int] = None, +): """Get ROC calculated. Rate of Change (ROC), also known as Momentum Oscillator, is the percent change @@ -31,10 +35,18 @@ def get_roc(quotes: Iterable[Quote], lookback_periods: int, sma_periods: int = N - [ROC Reference](https://python.stockindicators.dev/indicators/Roc/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetRoc[Quote](CsList(Quote, quotes), lookback_periods, sma_periods) + results = CsIndicator.GetRoc[Quote]( + CsList(Quote, quotes), lookback_periods, sma_periods + ) return ROCResults(results, ROCResult) -def get_roc_with_band(quotes: Iterable[Quote], lookback_periods: int, ema_periods: int, std_dev_periods: int): + +def get_roc_with_band( + quotes: Iterable[Quote], + lookback_periods: int, + ema_periods: int, + std_dev_periods: int, +): """Get ROCWB calculated. Rate of Change with Bands (ROCWB) is the percent change of Close price @@ -61,7 +73,9 @@ def get_roc_with_band(quotes: Iterable[Quote], lookback_periods: int, ema_period - [ROCWB Reference](https://python.stockindicators.dev/indicators/Roc/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetRocWb[Quote](CsList(Quote, quotes), lookback_periods, ema_periods, std_dev_periods) + results = CsIndicator.GetRocWb[Quote]( + CsList(Quote, quotes), lookback_periods, ema_periods, std_dev_periods + ) return ROCWBResults(results, ROCWBResult) @@ -96,6 +110,8 @@ def roc_sma(self, value): _T = TypeVar("_T", bound=ROCResult) + + class ROCResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of ROC(Rate of Change) results. @@ -143,6 +159,8 @@ def lower_band(self, value): _T = TypeVar("_T", bound=ROCWBResult) + + class ROCWBResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of ROC(Rate of Change) with band results. diff --git a/stock_indicators/indicators/rolling_pivots.py b/stock_indicators/indicators/rolling_pivots.py index 44267dc0..b25cea42 100644 --- a/stock_indicators/indicators/rolling_pivots.py +++ b/stock_indicators/indicators/rolling_pivots.py @@ -2,17 +2,21 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import PivotPointType from stock_indicators.indicators.common.helpers import RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_rolling_pivots(quotes: Iterable[Quote], window_periods: int, - offset_periods: int, point_type: PivotPointType = PivotPointType.STANDARD): +def get_rolling_pivots( + quotes: Iterable[Quote], + window_periods: int, + offset_periods: int, + point_type: PivotPointType = PivotPointType.STANDARD, +): """Get Rolling Pivot Points calculated. Rolling Pivot Points is a modern update to traditional fixed calendar window Pivot Points. @@ -39,8 +43,9 @@ def get_rolling_pivots(quotes: Iterable[Quote], window_periods: int, - [Rolling Pivot Points Reference](https://python.stockindicators.dev/indicators/RollingPivots/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetRollingPivots[Quote](CsList(Quote, quotes), window_periods, - offset_periods, point_type.cs_value) + results = CsIndicator.GetRollingPivots[Quote]( + CsList(Quote, quotes), window_periods, offset_periods, point_type.cs_value + ) return RollingPivotsResults(results, RollingPivotsResult) @@ -123,6 +128,8 @@ def s4(self, value): _T = TypeVar("_T", bound=RollingPivotsResult) + + class RollingPivotsResults(RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Rolling Pivot Points results. diff --git a/stock_indicators/indicators/rsi.py b/stock_indicators/indicators/rsi.py index a55352d6..f6a37b95 100644 --- a/stock_indicators/indicators/rsi.py +++ b/stock_indicators/indicators/rsi.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_rsi(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -47,6 +47,8 @@ def rsi(self, value): _T = TypeVar("_T", bound=RSIResult) + + class RSIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of RSI(Relative Strength Index) results. diff --git a/stock_indicators/indicators/slope.py b/stock_indicators/indicators/slope.py index 40457b93..5b9708dd 100644 --- a/stock_indicators/indicators/slope.py +++ b/stock_indicators/indicators/slope.py @@ -2,12 +2,12 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_slope(quotes: Iterable[Quote], lookback_periods: int): @@ -80,7 +80,10 @@ def line(self) -> Optional[Decimal]: def line(self, value): self._csdata.Line = CsDecimal(value) + _T = TypeVar("_T", bound=SlopeResult) + + class SlopeResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Slope results. diff --git a/stock_indicators/indicators/sma.py b/stock_indicators/indicators/sma.py index 8fcd4688..c90cb5a3 100644 --- a/stock_indicators/indicators/sma.py +++ b/stock_indicators/indicators/sma.py @@ -4,12 +4,15 @@ from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.enums import CandlePart from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_sma(quotes: Iterable[Quote], lookback_periods: int, - candle_part: CandlePart = CandlePart.CLOSE): +def get_sma( + quotes: Iterable[Quote], + lookback_periods: int, + candle_part: CandlePart = CandlePart.CLOSE, +): """Get SMA calculated. Simple Moving Average (SMA) is the average of price over a lookback window. @@ -32,8 +35,8 @@ def get_sma(quotes: Iterable[Quote], lookback_periods: int, - [SMA Reference](https://python.stockindicators.dev/indicators/Sma/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - quotes = Quote.use( - quotes, candle_part) # Error occurs if not assigned to local var. + # pylint: disable=no-member # Error occurs if not assigned to local var. + quotes = Quote.use(quotes, candle_part) results = CsIndicator.GetSma(quotes, lookback_periods) return SMAResults(results, SMAResult) @@ -61,7 +64,8 @@ def get_sma_analysis(quotes: Iterable[Quote], lookback_periods: int): - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ sma_extended_list = CsIndicator.GetSmaAnalysis[Quote]( - CsList(Quote, quotes), lookback_periods) + CsList(Quote, quotes), lookback_periods + ) return SMAAnalysisResults(sma_extended_list, SMAAnalysisResult) @@ -80,6 +84,8 @@ def sma(self, value): _T = TypeVar("_T", bound=SMAResult) + + class SMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of SMA(Simple Moving Average) results. @@ -119,6 +125,8 @@ def mape(self, value): _T = TypeVar("_T", bound=SMAAnalysisResult) + + class SMAAnalysisResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of SMA Analysis results. diff --git a/stock_indicators/indicators/smi.py b/stock_indicators/indicators/smi.py index eaea2ffc..cf192ed6 100644 --- a/stock_indicators/indicators/smi.py +++ b/stock_indicators/indicators/smi.py @@ -3,13 +3,17 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_smi(quotes: Iterable[Quote], lookback_periods: int = 13, - first_smooth_periods: int = 25, second_smooth_periods: int = 2, - signal_periods: int = 3): +def get_smi( + quotes: Iterable[Quote], + lookback_periods: int = 13, + first_smooth_periods: int = 25, + second_smooth_periods: int = 2, + signal_periods: int = 3, +): """Get SMI calculated. Stochastic Momentum Index (SMI) is a double-smoothed variant of @@ -39,9 +43,13 @@ def get_smi(quotes: Iterable[Quote], lookback_periods: int = 13, - [SMI Reference](https://python.stockindicators.dev/indicators/Smi/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetSmi[Quote](CsList(Quote, quotes), lookback_periods, - first_smooth_periods, second_smooth_periods, - signal_periods) + results = CsIndicator.GetSmi[Quote]( + CsList(Quote, quotes), + lookback_periods, + first_smooth_periods, + second_smooth_periods, + signal_periods, + ) return SMIResults(results, SMIResult) @@ -68,6 +76,8 @@ def signal(self, value): _T = TypeVar("_T", bound=SMIResult) + + class SMIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Stochastic Momentum Index (SMI) results. diff --git a/stock_indicators/indicators/smma.py b/stock_indicators/indicators/smma.py index 10a63c72..0d502e2e 100644 --- a/stock_indicators/indicators/smma.py +++ b/stock_indicators/indicators/smma.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_smma(quotes: Iterable[Quote], lookback_periods: int): @@ -47,6 +47,8 @@ def smma(self, value): _T = TypeVar("_T", bound=SMMAResult) + + class SMMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Smoothed Moving Average (SMMA) results. diff --git a/stock_indicators/indicators/starc_bands.py b/stock_indicators/indicators/starc_bands.py index 779d3f85..34f91390 100644 --- a/stock_indicators/indicators/starc_bands.py +++ b/stock_indicators/indicators/starc_bands.py @@ -1,15 +1,18 @@ from typing import Iterable, Optional, TypeVar -from warnings import warn from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_starc_bands(quotes: Iterable[Quote], sma_periods: int = None, - multiplier: float = 2, atr_periods: int = 10): +def get_starc_bands( + quotes: Iterable[Quote], + sma_periods: int = 20, + multiplier: float = 2, + atr_periods: int = 10, +): """Get STARC Bands calculated. Stoller Average Range Channel (STARC) Bands, are based @@ -36,12 +39,9 @@ def get_starc_bands(quotes: Iterable[Quote], sma_periods: int = None, - [STARC Bands Reference](https://python.stockindicators.dev/indicators/StarcBands/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - if sma_periods is None: - warn('The default value of sma_periods will be removed in the next version. Pass sma_periods explicitly.', DeprecationWarning, stacklevel=2) - sma_periods = 20 - - results = CsIndicator.GetStarcBands[Quote](CsList(Quote, quotes), sma_periods, - multiplier, atr_periods) + results = CsIndicator.GetStarcBands[Quote]( + CsList(Quote, quotes), sma_periods, multiplier, atr_periods + ) return STARCBandsResults(results, STARCBandsResult) @@ -76,6 +76,8 @@ def lower_band(self, value): _T = TypeVar("_T", bound=STARCBandsResult) + + class STARCBandsResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Stoller Average Range Channel (STARC) Bands results. diff --git a/stock_indicators/indicators/stc.py b/stock_indicators/indicators/stc.py index 92217822..edec7e4a 100644 --- a/stock_indicators/indicators/stc.py +++ b/stock_indicators/indicators/stc.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_stc(quotes: Iterable[Quote], cycle_periods: int = 10, - fast_periods: int = 23, slow_periods: int = 50): +def get_stc( + quotes: Iterable[Quote], + cycle_periods: int = 10, + fast_periods: int = 23, + slow_periods: int = 50, +): """Get STC calculated. Schaff Trend Cycle (STC) is a stochastic oscillator view @@ -35,8 +39,9 @@ def get_stc(quotes: Iterable[Quote], cycle_periods: int = 10, - [STC Reference](https://python.stockindicators.dev/indicators/Stc/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetStc[Quote](CsList(Quote, quotes), cycle_periods, - fast_periods, slow_periods) + results = CsIndicator.GetStc[Quote]( + CsList(Quote, quotes), cycle_periods, fast_periods, slow_periods + ) return STCResults(results, STCResult) @@ -55,6 +60,8 @@ def stc(self, value): _T = TypeVar("_T", bound=STCResult) + + class STCResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Schaff Trend Cycle (STC) results. diff --git a/stock_indicators/indicators/stdev.py b/stock_indicators/indicators/stdev.py index d7a3c767..038a0873 100644 --- a/stock_indicators/indicators/stdev.py +++ b/stock_indicators/indicators/stdev.py @@ -3,12 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_stdev(quotes: Iterable[Quote], lookback_periods: int, - sma_periods: Optional[int] = None): +def get_stdev( + quotes: Iterable[Quote], lookback_periods: int, sma_periods: Optional[int] = None +): """Get Rolling Standard Deviation calculated. Rolling Standard Deviation of Close price over a lookback window. @@ -31,7 +32,9 @@ def get_stdev(quotes: Iterable[Quote], lookback_periods: int, - [Stdev Reference](https://python.stockindicators.dev/indicators/StdDev/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetStdDev[Quote](CsList(Quote, quotes), lookback_periods, sma_periods) + results = CsIndicator.GetStdDev[Quote]( + CsList(Quote, quotes), lookback_periods, sma_periods + ) return StdevResults(results, StdevResult) @@ -74,6 +77,8 @@ def stdev_sma(self, value): _T = TypeVar("_T", bound=StdevResult) + + class StdevResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Rolling Standard Deviation results. diff --git a/stock_indicators/indicators/stdev_channels.py b/stock_indicators/indicators/stdev_channels.py index ee62a96d..5e759f99 100644 --- a/stock_indicators/indicators/stdev_channels.py +++ b/stock_indicators/indicators/stdev_channels.py @@ -3,13 +3,15 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_stdev_channels(quotes: Iterable[Quote], - lookback_periods: Optional[int] = 20, - standard_deviations: float = 2): +def get_stdev_channels( + quotes: Iterable[Quote], + lookback_periods: Optional[int] = 20, + standard_deviations: float = 2, +): """Get Standard Deviation Channels calculated. Standard Deviation Channels are based on an linearregression centerline @@ -33,7 +35,9 @@ def get_stdev_channels(quotes: Iterable[Quote], - [Stdev Channels Reference](https://python.stockindicators.dev/indicators/StdDevChannels/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetStdDevChannels[Quote](CsList(Quote, quotes), lookback_periods, standard_deviations) + results = CsIndicator.GetStdDevChannels[Quote]( + CsList(Quote, quotes), lookback_periods, standard_deviations + ) return StdevChannelsResults(results, StdevChannelsResult) @@ -76,6 +80,8 @@ def break_point(self, value): _T = TypeVar("_T", bound=StdevChannelsResult) + + class StdevChannelsResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Standard Deviation Channels results. diff --git a/stock_indicators/indicators/stoch.py b/stock_indicators/indicators/stoch.py index 071587bb..2551699e 100644 --- a/stock_indicators/indicators/stoch.py +++ b/stock_indicators/indicators/stoch.py @@ -4,12 +4,19 @@ from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.enums import MAType from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_stoch(quotes: Iterable[Quote], lookback_periods: int = 14, signal_periods: int = 3, smooth_periods: int = 3, - k_factor: float = 3, d_factor: float = 2, ma_type: MAType = MAType.SMA): +def get_stoch( + quotes: Iterable[Quote], + lookback_periods: int = 14, # pylint: disable=too-many-positional-arguments + signal_periods: int = 3, + smooth_periods: int = 3, + k_factor: float = 3, + d_factor: float = 2, + ma_type: MAType = MAType.SMA, +): """Get Stochastic Oscillator calculated, with KDJ indexes. Stochastic Oscillatoris a momentum indicator that looks back N periods to produce a scale of 0 to 100. @@ -47,8 +54,15 @@ def get_stoch(quotes: Iterable[Quote], lookback_periods: int = 14, signal_period - [Stochastic Oscillator Reference](https://python.stockindicators.dev/indicators/Stoch/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - stoch_results = CsIndicator.GetStoch[Quote](CsList(Quote, quotes), lookback_periods, signal_periods, smooth_periods, - k_factor, d_factor, ma_type.cs_value) + stoch_results = CsIndicator.GetStoch[Quote]( + CsList(Quote, quotes), + lookback_periods, + signal_periods, + smooth_periods, + k_factor, + d_factor, + ma_type.cs_value, + ) return StochResults(stoch_results, StochResult) @@ -87,6 +101,8 @@ def percent_j(self, value): _T = TypeVar("_T", bound=StochResult) + + class StochResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Stochastic Oscillator(with KDJ Index) results. diff --git a/stock_indicators/indicators/stoch_rsi.py b/stock_indicators/indicators/stoch_rsi.py index 98221ca3..cf801555 100644 --- a/stock_indicators/indicators/stoch_rsi.py +++ b/stock_indicators/indicators/stoch_rsi.py @@ -3,11 +3,17 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_stoch_rsi(quotes: Iterable[Quote], rsi_periods: int, stoch_periods: int, signal_periods: int, smooth_periods: int = 1): +def get_stoch_rsi( + quotes: Iterable[Quote], + rsi_periods: int, + stoch_periods: int, + signal_periods: int, + smooth_periods: int = 1, +): """Get Stochastic RSI calculated. Stochastic RSI is a Stochastic interpretation of the Relative Strength Index. @@ -36,7 +42,13 @@ def get_stoch_rsi(quotes: Iterable[Quote], rsi_periods: int, stoch_periods: int, - [Stochastic RSI Reference](https://python.stockindicators.dev/indicators/StochRsi/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - stoch_rsi_results = CsIndicator.GetStochRsi[Quote](CsList(Quote, quotes), rsi_periods, stoch_periods, signal_periods, smooth_periods) + stoch_rsi_results = CsIndicator.GetStochRsi[Quote]( + CsList(Quote, quotes), + rsi_periods, + stoch_periods, + signal_periods, + smooth_periods, + ) return StochRSIResults(stoch_rsi_results, StochRSIResult) @@ -63,6 +75,8 @@ def signal(self, value): _T = TypeVar("_T", bound=StochRSIResult) + + class StochRSIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Stochastic RSI results. diff --git a/stock_indicators/indicators/super_trend.py b/stock_indicators/indicators/super_trend.py index 012d237b..6f16aa77 100644 --- a/stock_indicators/indicators/super_trend.py +++ b/stock_indicators/indicators/super_trend.py @@ -2,15 +2,17 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_super_trend(quotes: Iterable[Quote], lookback_periods: int = 10, multiplier: float = 3): +def get_super_trend( + quotes: Iterable[Quote], lookback_periods: int = 10, multiplier: float = 3 +): """Get SuperTrend calculated. SuperTrend attempts to determine the primary trend of Close prices by using @@ -35,7 +37,9 @@ def get_super_trend(quotes: Iterable[Quote], lookback_periods: int = 10, multipl - [SuperTrend Reference](https://python.stockindicators.dev/indicators/SuperTrend/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - super_trend_results = CsIndicator.GetSuperTrend[Quote](CsList(Quote, quotes), lookback_periods, multiplier) + super_trend_results = CsIndicator.GetSuperTrend[Quote]( + CsList(Quote, quotes), lookback_periods, multiplier + ) return SuperTrendResults(super_trend_results, SuperTrendResult) @@ -70,6 +74,8 @@ def lower_band(self, value): _T = TypeVar("_T", bound=SuperTrendResult) + + class SuperTrendResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Super Trend results. diff --git a/stock_indicators/indicators/t3.py b/stock_indicators/indicators/t3.py index 4cea9652..fbd0e276 100644 --- a/stock_indicators/indicators/t3.py +++ b/stock_indicators/indicators/t3.py @@ -3,12 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_t3(quotes: Iterable[Quote], lookback_periods: int = 5, - volume_factor: float = 0.7): +def get_t3( + quotes: Iterable[Quote], lookback_periods: int = 5, volume_factor: float = 0.7 +): """Get T3 calculated. Tillson T3 is a smooth moving average that reduces @@ -32,8 +33,9 @@ def get_t3(quotes: Iterable[Quote], lookback_periods: int = 5, - [T3 Reference](https://python.stockindicators.dev/indicators/T3/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetT3[Quote](CsList(Quote, quotes), lookback_periods, - volume_factor) + results = CsIndicator.GetT3[Quote]( + CsList(Quote, quotes), lookback_periods, volume_factor + ) return T3Results(results, T3Result) @@ -52,6 +54,8 @@ def t3(self, value): _T = TypeVar("_T", bound=T3Result) + + class T3Results(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Tillson T3 results. diff --git a/stock_indicators/indicators/tema.py b/stock_indicators/indicators/tema.py index 1cbf93c8..f856e2e3 100644 --- a/stock_indicators/indicators/tema.py +++ b/stock_indicators/indicators/tema.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_tema(quotes: Iterable[Quote], lookback_periods: int): @@ -47,6 +47,8 @@ def tema(self, value): _T = TypeVar("_T", bound=TEMAResult) + + class TEMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Triple Exponential Moving Average (TEMA) results. diff --git a/stock_indicators/indicators/tr.py b/stock_indicators/indicators/tr.py index e5b260f9..90b99456 100644 --- a/stock_indicators/indicators/tr.py +++ b/stock_indicators/indicators/tr.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_tr(quotes: Iterable[Quote]): @@ -43,6 +43,8 @@ def tr(self, value): _T = TypeVar("_T", bound=TrResult) + + class TrResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of True Range (TR) results. diff --git a/stock_indicators/indicators/trix.py b/stock_indicators/indicators/trix.py index 4ce81b96..1d103070 100644 --- a/stock_indicators/indicators/trix.py +++ b/stock_indicators/indicators/trix.py @@ -3,11 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_trix(quotes: Iterable[Quote], lookback_periods: int, signal_periods: Optional[int] = None): +def get_trix( + quotes: Iterable[Quote], lookback_periods: int, signal_periods: Optional[int] = None +): """Get TRIX calculated. Triple EMA Oscillator (TRIX) is the rate of change for a 3 EMA smoothing of the Close price over a lookback window. @@ -31,7 +33,9 @@ def get_trix(quotes: Iterable[Quote], lookback_periods: int, signal_periods: Opt - [TRIX Reference](https://python.stockindicators.dev/indicators/Trix/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetTrix[Quote](CsList(Quote, quotes), lookback_periods, signal_periods) + results = CsIndicator.GetTrix[Quote]( + CsList(Quote, quotes), lookback_periods, signal_periods + ) return TRIXResults(results, TRIXResult) @@ -66,6 +70,8 @@ def signal(self, value): _T = TypeVar("_T", bound=TRIXResult) + + class TRIXResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Triple EMA Oscillator (TRIX) results. diff --git a/stock_indicators/indicators/tsi.py b/stock_indicators/indicators/tsi.py index 128e6172..c87690ca 100644 --- a/stock_indicators/indicators/tsi.py +++ b/stock_indicators/indicators/tsi.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_tsi(quotes: Iterable[Quote], lookback_periods: int = 25, - smooth_periods: int = 13, signal_periods: int = 7): +def get_tsi( + quotes: Iterable[Quote], + lookback_periods: int = 25, + smooth_periods: int = 13, + signal_periods: int = 7, +): """Get TSI calculated. True Strength Index (TSI) is a momentum oscillator @@ -35,8 +39,9 @@ def get_tsi(quotes: Iterable[Quote], lookback_periods: int = 25, - [TSI Reference](https://python.stockindicators.dev/indicators/Tsi/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetTsi[Quote](CsList(Quote, quotes), lookback_periods, - smooth_periods, signal_periods) + results = CsIndicator.GetTsi[Quote]( + CsList(Quote, quotes), lookback_periods, smooth_periods, signal_periods + ) return TSIResults(results, TSIResult) @@ -63,6 +68,8 @@ def signal(self, value): _T = TypeVar("_T", bound=TSIResult) + + class TSIResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of True Strength Index (TSI) results. diff --git a/stock_indicators/indicators/ulcer_index.py b/stock_indicators/indicators/ulcer_index.py index 92e859ec..780fd428 100644 --- a/stock_indicators/indicators/ulcer_index.py +++ b/stock_indicators/indicators/ulcer_index.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_ulcer_index(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -47,6 +47,8 @@ def ui(self, value): _T = TypeVar("_T", bound=UlcerIndexResult) + + class UlcerIndexResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Ulcer Index (UI) results. diff --git a/stock_indicators/indicators/ultimate.py b/stock_indicators/indicators/ultimate.py index 88033fa8..855e71fc 100644 --- a/stock_indicators/indicators/ultimate.py +++ b/stock_indicators/indicators/ultimate.py @@ -3,12 +3,16 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_ultimate(quotes: Iterable[Quote], short_periods: int = 7, - middle_periods: int = 14, long_periods: int = 28): +def get_ultimate( + quotes: Iterable[Quote], + short_periods: int = 7, + middle_periods: int = 14, + long_periods: int = 28, +): """Get Ultimate Oscillator calculated. Ultimate Oscillator uses several lookback periods to weigh buying power @@ -35,8 +39,9 @@ def get_ultimate(quotes: Iterable[Quote], short_periods: int = 7, - [Ultimate Oscillator Reference](https://python.stockindicators.dev/indicators/Ultimate/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetUltimate[Quote](CsList(Quote, quotes), short_periods, - middle_periods, long_periods) + results = CsIndicator.GetUltimate[Quote]( + CsList(Quote, quotes), short_periods, middle_periods, long_periods + ) return UltimateResults(results, UltimateResult) @@ -55,6 +60,8 @@ def ultimate(self, value): _T = TypeVar("_T", bound=UltimateResult) + + class UltimateResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Ultimate Oscillator results. diff --git a/stock_indicators/indicators/volatility_stop.py b/stock_indicators/indicators/volatility_stop.py index ac78d9f0..9196a633 100644 --- a/stock_indicators/indicators/volatility_stop.py +++ b/stock_indicators/indicators/volatility_stop.py @@ -3,12 +3,13 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_volatility_stop(quotes: Iterable[Quote], lookback_periods: int = 7, - multiplier: float = 3): +def get_volatility_stop( + quotes: Iterable[Quote], lookback_periods: int = 7, multiplier: float = 3 +): """Get Volatility Stop calculated. Volatility Stop is an ATR based indicator used to @@ -32,8 +33,9 @@ def get_volatility_stop(quotes: Iterable[Quote], lookback_periods: int = 7, - [Volatility Stop Reference](https://python.stockindicators.dev/indicators/VolatilityStop/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetVolatilityStop[Quote](CsList(Quote, quotes), lookback_periods, - multiplier) + results = CsIndicator.GetVolatilityStop[Quote]( + CsList(Quote, quotes), lookback_periods, multiplier + ) return VolatilityStopResults(results, VolatilityStopResult) @@ -76,6 +78,8 @@ def is_stop(self, value): _T = TypeVar("_T", bound=VolatilityStopResult) + + class VolatilityStopResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Volatility Stop results. diff --git a/stock_indicators/indicators/vortex.py b/stock_indicators/indicators/vortex.py index 3ace0fe0..f2c31f72 100644 --- a/stock_indicators/indicators/vortex.py +++ b/stock_indicators/indicators/vortex.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_vortex(quotes: Iterable[Quote], lookback_periods: int): @@ -56,6 +56,8 @@ def nvi(self, value): _T = TypeVar("_T", bound=VortexResult) + + class VortexResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Vortex Indicator (VI) results. diff --git a/stock_indicators/indicators/vwap.py b/stock_indicators/indicators/vwap.py index cec0e32e..c74ef86d 100644 --- a/stock_indicators/indicators/vwap.py +++ b/stock_indicators/indicators/vwap.py @@ -1,21 +1,41 @@ from datetime import datetime -from typing import Iterable, Optional, TypeVar, overload +from typing import Iterable, Optional, TypeVar, Union, overload from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import DateTime as CsDateTime +from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase @overload -def get_vwap(quotes: Iterable[Quote], start: Optional[datetime] = None) -> "VWAPResults[VWAPResult]": ... +def get_vwap( + quotes: Iterable[Quote], start: Optional[datetime] = None +) -> "VWAPResults[VWAPResult]": ... + + @overload -def get_vwap(quotes: Iterable[Quote], year: int, - month: int = 1, day: int = 1, - hour: int = 0, minute: int = 0) -> "VWAPResults[VWAPResult]": ... -def get_vwap(quotes, start = None, month = 1, day = 1, hour = 0, minute = 0): +def get_vwap( + quotes: Iterable[Quote], + year: int, + *, + month: int = 1, + day: int = 1, + hour: int = 0, + minute: int = 0, +) -> "VWAPResults[VWAPResult]": ... + + +def get_vwap( + quotes: Iterable[Quote], + start: Union[datetime, int, None] = None, + *legacy_date_parts, + month: int = 1, + day: int = 1, + hour: int = 0, + minute: int = 0, +) -> "VWAPResults[VWAPResult]": # pylint: disable=too-many-branches,too-many-statements,keyword-arg-before-vararg """Get VWAP calculated. Volume Weighted Average Price (VWAP) is a Volume weighted average @@ -39,10 +59,81 @@ def get_vwap(quotes, start = None, month = 1, day = 1, hour = 0, minute = 0): - [VWAP Fractal Reference](https://python.stockindicators.dev/indicators/Vwap/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - if isinstance(start, int): - start = datetime(start, month, day, hour, minute) - - results = CsIndicator.GetVwap[Quote](CsList(Quote, quotes), CsDateTime(start) if start else None) + # Backward compatibility: support positional year,month,day,hour as in tests + if legacy_date_parts: + # Validate legacy_date_parts is a sequence with at most 4 items + if not isinstance(legacy_date_parts, (tuple, list)): + raise TypeError("legacy_date_parts must be a sequence") + if len(legacy_date_parts) > 4: + raise ValueError( + "Too many positional date arguments (max 4: month, day, hour, minute)" + ) + + # Interpret: start is actually the year; legacy_date_parts supply remaining + if not isinstance(start, int): + raise TypeError( + "Year must be provided as an int when using legacy positional date form" + ) + year = start + + # Fill provided parts into month, day, hour, minute if given positionally + parts = list(legacy_date_parts) + if len(parts) > 0: + if not isinstance(parts[0], int): + raise TypeError("Month must be an int") + month = parts[0] + if len(parts) > 1: + if not isinstance(parts[1], int): + raise TypeError("Day must be an int") + day = parts[1] + if len(parts) > 2: + if not isinstance(parts[2], int): + raise TypeError("Hour must be an int") + hour = parts[2] + if len(parts) > 3: + if not isinstance(parts[3], int): + raise TypeError("Minute must be an int") + minute = parts[3] + + # Validate ranges + if not 1 <= month <= 12: + raise ValueError(f"Month must be 1-12, got {month}") + if not 1 <= day <= 31: + raise ValueError(f"Day must be 1-31, got {day}") + if not 0 <= hour <= 23: + raise ValueError(f"Hour must be 0-23, got {hour}") + if not 0 <= minute <= 59: + raise ValueError(f"Minute must be 0-59, got {minute}") + + start_dt = datetime(year, month, day, hour, minute) + else: + # When not using legacy parts, validate that start is either int (year) or datetime + if start is not None: + if isinstance(start, int): + # Using keyword arguments for date parts + year = start + # Validate ranges for keyword arguments + if not 1 <= month <= 12: + raise ValueError(f"Month must be 1-12, got {month}") + if not 1 <= day <= 31: + raise ValueError(f"Day must be 1-31, got {day}") + if not 0 <= hour <= 23: + raise ValueError(f"Hour must be 0-23, got {hour}") + if not 0 <= minute <= 59: + raise ValueError(f"Minute must be 0-59, got {minute}") + start_dt = datetime(year, month, day, hour, minute) + elif isinstance(start, datetime): + start_dt = start + else: + raise TypeError( + f"start must be int (year), datetime, or None, got {type(start).__name__}" + ) + else: + start_dt = None + + results = CsIndicator.GetVwap[Quote]( + CsList(Quote, quotes), CsDateTime(start_dt) if start_dt else None + ) return VWAPResults(results, VWAPResult) @@ -61,6 +152,8 @@ def vwap(self, value): _T = TypeVar("_T", bound=VWAPResult) + + class VWAPResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Volume Weighted Average Price (VWAP) results. diff --git a/stock_indicators/indicators/vwma.py b/stock_indicators/indicators/vwma.py index 9df7abfc..fd1c642f 100644 --- a/stock_indicators/indicators/vwma.py +++ b/stock_indicators/indicators/vwma.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_vwma(quotes: Iterable[Quote], lookback_periods: int): @@ -47,6 +47,8 @@ def vwma(self, value): _T = TypeVar("_T", bound=VWMAResult) + + class VWMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Volume Weighted Moving Average (VWMA) results. diff --git a/stock_indicators/indicators/williams_r.py b/stock_indicators/indicators/williams_r.py index 2276898c..f739649b 100644 --- a/stock_indicators/indicators/williams_r.py +++ b/stock_indicators/indicators/williams_r.py @@ -3,8 +3,8 @@ from stock_indicators._cslib import CsIndicator from stock_indicators._cstypes import List as CsList from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase def get_williams_r(quotes: Iterable[Quote], lookback_periods: int = 14): @@ -48,6 +48,8 @@ def williams_r(self, value): _T = TypeVar("_T", bound=WilliamsResult) + + class WilliamsResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Williams %R results. diff --git a/stock_indicators/indicators/wma.py b/stock_indicators/indicators/wma.py index dc34b1f6..07eb0984 100644 --- a/stock_indicators/indicators/wma.py +++ b/stock_indicators/indicators/wma.py @@ -3,12 +3,15 @@ from stock_indicators._cslib import CsIndicator from stock_indicators.indicators.common.enums import CandlePart from stock_indicators.indicators.common.helpers import CondenseMixin, RemoveWarmupMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_wma(quotes: Iterable[Quote], lookback_periods: int, - candle_part: CandlePart = CandlePart.CLOSE): +def get_wma( + quotes: Iterable[Quote], + lookback_periods: int, + candle_part: CandlePart = CandlePart.CLOSE, +): """Get WMA calculated. Weighted Moving Average (WMA) is the linear weighted average @@ -33,7 +36,7 @@ def get_wma(quotes: Iterable[Quote], lookback_periods: int, - [WMA Reference](https://python.stockindicators.dev/indicators/Wma/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - quotes = Quote.use(quotes, candle_part) # Error occurs if not assigned to local var. + quotes = Quote.use(quotes, candle_part) # pylint: disable=no-member # Error occurs if not assigned to local var. results = CsIndicator.GetWma(quotes, lookback_periods) return WMAResults(results, WMAResult) @@ -53,6 +56,8 @@ def wma(self, value): _T = TypeVar("_T", bound=WMAResult) + + class WMAResults(CondenseMixin, RemoveWarmupMixin, IndicatorResults[_T]): """ A wrapper class for the list of Weighted Moving Average (WMA) results. diff --git a/stock_indicators/indicators/zig_zag.py b/stock_indicators/indicators/zig_zag.py index 7ccb2165..bd6fcf44 100644 --- a/stock_indicators/indicators/zig_zag.py +++ b/stock_indicators/indicators/zig_zag.py @@ -2,17 +2,20 @@ from typing import Iterable, Optional, TypeVar from stock_indicators._cslib import CsIndicator -from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import Decimal as CsDecimal +from stock_indicators._cstypes import List as CsList from stock_indicators._cstypes import to_pydecimal from stock_indicators.indicators.common.enums import EndType from stock_indicators.indicators.common.helpers import CondenseMixin -from stock_indicators.indicators.common.results import IndicatorResults, ResultBase from stock_indicators.indicators.common.quote import Quote +from stock_indicators.indicators.common.results import IndicatorResults, ResultBase -def get_zig_zag(quotes: Iterable[Quote], end_type: EndType = EndType.CLOSE, - percent_change: float = 5): +def get_zig_zag( + quotes: Iterable[Quote], + end_type: EndType = EndType.CLOSE, + percent_change: float = 5, +): """Get Zig Zag calculated. Zig Zag is a price chart overlay that simplifies the up and down @@ -36,8 +39,9 @@ def get_zig_zag(quotes: Iterable[Quote], end_type: EndType = EndType.CLOSE, - [Zig Zag Reference](https://python.stockindicators.dev/indicators/ZigZag/#content) - [Helper Methods](https://python.stockindicators.dev/utilities/#content) """ - results = CsIndicator.GetZigZag[Quote](CsList(Quote, quotes), end_type.cs_value, - CsDecimal(percent_change)) + results = CsIndicator.GetZigZag[Quote]( + CsList(Quote, quotes), end_type.cs_value, CsDecimal(percent_change) + ) return ZigZagResults(results, ZigZagResult) @@ -80,6 +84,8 @@ def retrace_low(self, value): _T = TypeVar("_T", bound=ZigZagResult) + + class ZigZagResults(CondenseMixin, IndicatorResults[_T]): """ A wrapper class for the list of Zig Zag results. diff --git a/tests/common/test-dateof-roundtrip-variants.py b/tests/common/test-dateof-roundtrip-variants.py index 38c68e72..657a273d 100644 --- a/tests/common/test-dateof-roundtrip-variants.py +++ b/tests/common/test-dateof-roundtrip-variants.py @@ -47,7 +47,7 @@ def _mk_date_only(): @pytest.mark.parametrize( - "variant, maker", + ("variant", "maker"), [ ("utc", _mk_utc), ("offset", _mk_offset), @@ -55,8 +55,9 @@ def _mk_date_only(): ("date_only", _mk_date_only), ], ) -def test_sma_roundtrip_dates_variants(_variant, maker): +def test_sma_roundtrip_dates_variants(variant, maker): quotes = maker() + assert variant results = indicators.get_sma(quotes, 2) assert len(results) == len(quotes) diff --git a/tests/common/test_candle.py b/tests/common/test_candle.py index 7653a529..7fb02b2c 100644 --- a/tests/common/test_candle.py +++ b/tests/common/test_candle.py @@ -1,41 +1,42 @@ from stock_indicators import indicators + class TestCandleResults: def test_standard(self, quotes): results = indicators.get_doji(quotes, 0.1) - + r = results[0] assert 212.80 == round(float(r.candle.close), 2) - assert 1.83 == round(float(r.candle.size), 2) - assert 0.19 == round(float(r.candle.body), 2) - assert 0.10 == round(float(r.candle.body_pct), 2) - assert 1.09 == round(float(r.candle.lower_wick), 2) - assert 0.60 == round(float(r.candle.lower_wick_pct), 2) - assert 0.55 == round(float(r.candle.upper_wick), 2) - assert 0.30 == round(float(r.candle.upper_wick_pct), 2) - assert r.candle.is_bearish == False - assert r.candle.is_bullish == True - + assert 1.83 == round(float(r.candle.size), 2) + assert 0.19 == round(float(r.candle.body), 2) + assert 0.10 == round(float(r.candle.body_pct), 2) + assert 1.09 == round(float(r.candle.lower_wick), 2) + assert 0.60 == round(float(r.candle.lower_wick_pct), 2) + assert 0.55 == round(float(r.candle.upper_wick), 2) + assert 0.30 == round(float(r.candle.upper_wick_pct), 2) + assert not r.candle.is_bearish + assert r.candle.is_bullish + r = results[351] assert 263.16 == round(float(r.candle.close), 2) - assert 1.24 == round(float(r.candle.size), 2) - assert 0.00 == round(float(r.candle.body), 2) - assert 0.00 == round(float(r.candle.body_pct), 2) - assert 0.55 == round(float(r.candle.lower_wick), 2) - assert 0.44 == round(float(r.candle.lower_wick_pct), 2) - assert 0.69 == round(float(r.candle.upper_wick), 2) - assert 0.56 == round(float(r.candle.upper_wick_pct), 2) - assert r.candle.is_bearish == False - assert r.candle.is_bullish == False - - r = results[501] + assert 1.24 == round(float(r.candle.size), 2) + assert 0.00 == round(float(r.candle.body), 2) + assert 0.00 == round(float(r.candle.body_pct), 2) + assert 0.55 == round(float(r.candle.lower_wick), 2) + assert 0.44 == round(float(r.candle.lower_wick_pct), 2) + assert 0.69 == round(float(r.candle.upper_wick), 2) + assert 0.56 == round(float(r.candle.upper_wick_pct), 2) + assert not r.candle.is_bearish + assert not r.candle.is_bullish + + r = results[501] assert 245.28 == round(float(r.candle.close), 2) - assert 2.67 == round(float(r.candle.size), 2) - assert 0.36 == round(float(r.candle.body), 2) - assert 0.13 == round(float(r.candle.body_pct), 2) - assert 2.05 == round(float(r.candle.lower_wick), 2) - assert 0.77 == round(float(r.candle.lower_wick_pct), 2) - assert 0.26 == round(float(r.candle.upper_wick), 2) - assert 0.10 == round(float(r.candle.upper_wick_pct), 2) - assert r.candle.is_bearish == False - assert r.candle.is_bullish == True + assert 2.67 == round(float(r.candle.size), 2) + assert 0.36 == round(float(r.candle.body), 2) + assert 0.13 == round(float(r.candle.body_pct), 2) + assert 2.05 == round(float(r.candle.lower_wick), 2) + assert 0.77 == round(float(r.candle.lower_wick_pct), 2) + assert 0.26 == round(float(r.candle.upper_wick), 2) + assert 0.10 == round(float(r.candle.upper_wick_pct), 2) + assert not r.candle.is_bearish + assert r.candle.is_bullish diff --git a/tests/common/test_common.py b/tests/common/test_common.py index d52df668..afe46266 100644 --- a/tests/common/test_common.py +++ b/tests/common/test_common.py @@ -1,25 +1,28 @@ from datetime import datetime + from stock_indicators import indicators + class TestCommon: def test_find(self, quotes): results = indicators.get_bollinger_bands(quotes) - + r = results.find(datetime(2018, 12, 28)) + assert r is not None assert 252.9625 == round(float(r.sma), 4) assert 230.3495 == round(float(r.lower_band), 4) - + r = results.find(datetime(2018, 12, 31)) + assert r is not None assert 251.8600 == round(float(r.sma), 4) assert 230.0196 == round(float(r.lower_band), 4) - - + def test_remove_warmup_periods(self, quotes): results = indicators.get_adl(quotes) assert 502 == len(results) - + results = results.remove_warmup_periods(200) assert 302 == len(results) - + results = results.remove_warmup_periods(1000) assert 0 == len(results) diff --git a/tests/common/test_cstype_conversion.py b/tests/common/test_cstype_conversion.py index bfeb832c..fb23d473 100644 --- a/tests/common/test_cstype_conversion.py +++ b/tests/common/test_cstype_conversion.py @@ -1,12 +1,13 @@ from datetime import datetime -from numbers import Number from decimal import Decimal as PyDecimal +from numbers import Number from stock_indicators._cslib import CsCultureInfo from stock_indicators._cstypes import DateTime as CsDateTime from stock_indicators._cstypes import Decimal as CsDecimal from stock_indicators._cstypes import to_pydatetime, to_pydecimal + class TestCsTypeConversion: def test_datetime_conversion(self): py_datetime = datetime.now() @@ -22,7 +23,9 @@ def test_datetime_conversion(self): # assert py_datetime.microsecond == converted_datetime.microsecond def test_timezone_aware_datetime_conversion(self): - py_datetime = datetime.strptime('2022-06-02 10:29:00-04:00', '%Y-%m-%d %H:%M:%S%z') + py_datetime = datetime.strptime( + "2022-06-02 10:29:00-04:00", "%Y-%m-%d %H:%M:%S%z" + ) converted_datetime = to_pydatetime(CsDateTime(py_datetime)) assert py_datetime.year == converted_datetime.year @@ -38,40 +41,41 @@ def test_timezone_aware_datetime_conversion(self): def test_auto_conversion_from_double_to_float(self): from System import Double as CsDouble - cs_double = CsDouble.Parse('1996.1012', CsCultureInfo.InvariantCulture) + cs_double = CsDouble.Parse("1996.1012", CsCultureInfo.InvariantCulture) assert isinstance(cs_double, Number) assert isinstance(cs_double, float) assert 1996.1012 == cs_double def test_quote_constructor_retains_timezone(self): - from stock_indicators.indicators.common.quote import Quote from decimal import Decimal - - dt = datetime.fromisoformat('2000-03-26T23:00:00+00:00') + + from stock_indicators.indicators.common.quote import Quote + + dt = datetime.fromisoformat("2000-03-26T23:00:00+00:00") q = Quote( date=dt, - open=Decimal('23'), - high=Decimal('26'), - low=Decimal('20'), - close=Decimal('25'), - volume=Decimal('323') + open=Decimal("23"), + high=Decimal("26"), + low=Decimal("20"), + close=Decimal("25"), + volume=Decimal("323"), ) - assert str(q.date.tzinfo) == 'UTC' - assert str(q.date.time()) == '23:00:00' + assert str(q.date.tzinfo) == "UTC" + assert str(q.date.time()) == "23:00:00" def test_decimal_conversion(self): py_decimal = 1996.1012 cs_decimal = CsDecimal(py_decimal) - assert str(py_decimal) == '1996.1012' + assert str(py_decimal) == "1996.1012" assert to_pydecimal(cs_decimal) == PyDecimal(str(py_decimal)) def test_decimal_conversion_expressed_in_exponential_notation(self): py_decimal = 0.000018 cs_decimal = CsDecimal(py_decimal) - assert str(py_decimal) == '1.8e-05' + assert str(py_decimal) == "1.8e-05" assert to_pydecimal(cs_decimal) == PyDecimal(str(py_decimal)) def test_exponential_notation_decimal_conversion(self): diff --git a/tests/common/test_cstype_datetime_kind.py b/tests/common/test_cstype_datetime_kind.py index 4337ef1c..aa9b629b 100644 --- a/tests/common/test_cstype_datetime_kind.py +++ b/tests/common/test_cstype_datetime_kind.py @@ -1,5 +1,4 @@ from datetime import datetime, timezone -import pytest import pytest @@ -9,27 +8,34 @@ def test_to_pydatetime_sets_utc_for_tzaware(): # Start with an offset-aware datetime and ensure roundtrip yields UTC tz - dt = datetime.fromisoformat('2022-06-02T10:29:00-04:00') + dt = datetime.fromisoformat("2022-06-02T10:29:00-04:00") expected_utc = dt.astimezone(timezone.utc) cs_dt = CsDateTime(dt) py_dt = to_pydatetime(cs_dt) # tz-aware should come back as UTC - assert str(py_dt.tzinfo) == 'UTC' + assert str(py_dt.tzinfo) == "UTC" # Time should reflect conversion to UTC assert py_dt.replace(microsecond=0) == expected_utc.replace(microsecond=0) def test_to_pydatetime_keeps_naive_naive(): # Naive input should remain naive and preserve second-level components - dt = datetime.fromisoformat('2023-01-02T03:04:05') # no microseconds + dt = datetime.fromisoformat("2023-01-02T03:04:05") # no microseconds cs_dt = CsDateTime(dt) py_dt = to_pydatetime(cs_dt) assert py_dt.tzinfo is None - assert (py_dt.year, py_dt.month, py_dt.day, py_dt.hour, py_dt.minute, py_dt.second) == ( + assert ( + py_dt.year, + py_dt.month, + py_dt.day, + py_dt.hour, + py_dt.minute, + py_dt.second, + ) == ( dt.year, dt.month, dt.day, @@ -41,7 +47,7 @@ def test_to_pydatetime_keeps_naive_naive(): def test_csdatetime_rejects_non_datetime_input(): with pytest.raises(TypeError): - CsDateTime('2020-01-01') # type: ignore[arg-type] + CsDateTime("2020-01-01") # type: ignore[arg-type] def test_to_pydatetime_handles_system_local_timezone_roundtrip(): @@ -53,5 +59,7 @@ def test_to_pydatetime_handles_system_local_timezone_roundtrip(): py_dt = to_pydatetime(cs_dt) # Interop always returns tz-aware as UTC; ensure the instant is preserved - assert str(py_dt.tzinfo) == 'UTC' - assert py_dt.replace(microsecond=0) == dt_local.astimezone(timezone.utc).replace(microsecond=0) + assert str(py_dt.tzinfo) == "UTC" + assert py_dt.replace(microsecond=0) == dt_local.astimezone(timezone.utc).replace( + microsecond=0 + ) diff --git a/tests/common/test_dateof_equivalence.py b/tests/common/test_dateof_equivalence.py index 289bd4ac..5502343e 100644 --- a/tests/common/test_dateof_equivalence.py +++ b/tests/common/test_dateof_equivalence.py @@ -55,6 +55,6 @@ def test_equivalent_non_date_columns(name: str): assert len(rows) == len(ref_rows) # For each row, all columns except the date column (index 1) should match - for (ref_row, row) in zip(ref_rows, rows): + for ref_row, row in zip(ref_rows, rows): assert ref_row[0] == row[0] # index assert ref_row[2:] == row[2:] # all non-date price/volume columns diff --git a/tests/common/test_dateof_identity_roundtrip.py b/tests/common/test_dateof_identity_roundtrip.py index 7b9f942a..64c5181a 100644 --- a/tests/common/test_dateof_identity_roundtrip.py +++ b/tests/common/test_dateof_identity_roundtrip.py @@ -4,6 +4,7 @@ """ from datetime import timezone + import pytest from stock_indicators import indicators diff --git a/tests/common/test_indicator_results.py b/tests/common/test_indicator_results.py index a5206946..a6be2b73 100644 --- a/tests/common/test_indicator_results.py +++ b/tests/common/test_indicator_results.py @@ -1,61 +1,50 @@ from datetime import datetime -import pytest from stock_indicators import indicators + class TestIndicatorResults: def test_add_results(self, quotes): results = indicators.get_sma(quotes, 20) r4 = results + results + results + results - + assert len(r4) == len(results) * 4 - + for i in range(4): - idx = len(results)*i - assert r4[18+idx].sma is None - assert 214.5250 == round(float(r4[19+idx].sma), 4) - assert 215.0310 == round(float(r4[24+idx].sma), 4) - assert 234.9350 == round(float(r4[149+idx].sma), 4) - assert 255.5500 == round(float(r4[249+idx].sma), 4) - assert 251.8600 == round(float(r4[501+idx].sma), 4) - + idx = len(results) * i + assert r4[18 + idx].sma is None + assert 214.5250 == round(float(r4[19 + idx].sma), 4) + assert 215.0310 == round(float(r4[24 + idx].sma), 4) + assert 234.9350 == round(float(r4[149 + idx].sma), 4) + assert 255.5500 == round(float(r4[249 + idx].sma), 4) + assert 251.8600 == round(float(r4[501 + idx].sma), 4) + def test_mul_results(self, quotes): results = indicators.get_sma(quotes, 20) r4 = results * 4 - + assert len(r4) == len(results) * 4 for i in range(4): - idx = len(results)*i - assert r4[18+idx].sma is None - assert 214.5250 == round(float(r4[19+idx].sma), 4) - assert 215.0310 == round(float(r4[24+idx].sma), 4) - assert 234.9350 == round(float(r4[149+idx].sma), 4) - assert 255.5500 == round(float(r4[249+idx].sma), 4) - assert 251.8600 == round(float(r4[501+idx].sma), 4) - - def test_done_and_reload(self, quotes): - results = indicators.get_sma(quotes, 20) - results.done() - - with pytest.raises(ValueError): - results * 2 + idx = len(results) * i + assert r4[18 + idx].sma is None + assert 214.5250 == round(float(r4[19 + idx].sma), 4) + assert 215.0310 == round(float(r4[24 + idx].sma), 4) + assert 234.9350 == round(float(r4[149 + idx].sma), 4) + assert 255.5500 == round(float(r4[249 + idx].sma), 4) + assert 251.8600 == round(float(r4[501 + idx].sma), 4) - results.reload() - r2 = results * 2 - - assert len(r2) == len(results) * 2 - def test_find(self, quotes): results = indicators.get_sma(quotes, 20) - + # r[19] r = results.find(datetime(2017, 1, 31)) + assert r is not None assert 214.5250 == round(float(r.sma), 4) def test_not_found(self, quotes): results = indicators.get_sma(quotes, 20) - + # returns None r = results.find(datetime(1996, 10, 12)) assert r is None @@ -63,6 +52,6 @@ def test_not_found(self, quotes): def test_remove_with_period(self, quotes): results = indicators.get_sma(quotes, 20) length = len(results) - + results = results.remove_warmup_periods(50) assert len(results) == length - 50 diff --git a/tests/common/test_locale.py b/tests/common/test_locale.py index 1856d7af..4b47fe57 100644 --- a/tests/common/test_locale.py +++ b/tests/common/test_locale.py @@ -1,5 +1,6 @@ -from decimal import Decimal as PyDecimal import locale +from decimal import Decimal as PyDecimal + import pytest from stock_indicators._cslib import CsDecimal @@ -19,9 +20,7 @@ def _uses_comma_decimal_separator() -> bool: import clr # type: ignore # noqa: F401 from System.Globalization import CultureInfo # type: ignore - return ( - CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == "," - ) + return CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == "," except Exception: pass @@ -41,7 +40,10 @@ def _uses_comma_decimal_separator() -> bool: @pytest.mark.localization -@pytest.mark.skipif(not uses_comma_decimal, reason="Localization tests require a comma decimal separator culture (e.g., ru-RU)") +@pytest.mark.skipif( + not uses_comma_decimal, + reason="Localization tests require a comma decimal separator culture (e.g., ru-RU)", +) class TestLocale: """ These tests are intended for environments where a comma is used as the decimal separator, diff --git a/tests/common/test_quote.py b/tests/common/test_quote.py index cfd3b818..997ccd47 100644 --- a/tests/common/test_quote.py +++ b/tests/common/test_quote.py @@ -1,63 +1,64 @@ -from stock_indicators.indicators.common.quote import Quote -from decimal import Decimal from datetime import datetime, timezone +from decimal import Decimal + +from stock_indicators.indicators.common.quote import Quote def test_quote_constructor_retains_timezone(): - dt = datetime.fromisoformat('2000-03-26T23:00:00+00:00') + dt = datetime.fromisoformat("2000-03-26T23:00:00+00:00") q = Quote( date=dt, - open=Decimal('23'), - high=Decimal('26'), - low=Decimal('20'), - close=Decimal('25'), - volume=Decimal('323') + open=Decimal("23"), + high=Decimal("26"), + low=Decimal("20"), + close=Decimal("25"), + volume=Decimal("323"), ) - assert str(q.date.tzinfo) == 'UTC' - assert str(q.date.time()) == '23:00:00' + assert str(q.date.tzinfo) == "UTC" + assert str(q.date.time()) == "23:00:00" def test_quote_constructor_handles_various_date_formats(): - dt1 = datetime.fromisoformat('2000-03-26T23:00:00+00:00') - dt2 = datetime.strptime('2000-03-26 23:00:00', '%Y-%m-%d %H:%M:%S') - dt3 = datetime.strptime('2000-03-26', '%Y-%m-%d') + dt1 = datetime.fromisoformat("2000-03-26T23:00:00+00:00") + dt2 = datetime.strptime("2000-03-26 23:00:00", "%Y-%m-%d %H:%M:%S") + dt3 = datetime.strptime("2000-03-26", "%Y-%m-%d") q1 = Quote( date=dt1, - open=Decimal('23'), - high=Decimal('26'), - low=Decimal('20'), - close=Decimal('25'), - volume=Decimal('323') + open=Decimal("23"), + high=Decimal("26"), + low=Decimal("20"), + close=Decimal("25"), + volume=Decimal("323"), ) q2 = Quote( date=dt2, - open=Decimal('23'), - high=Decimal('26'), - low=Decimal('20'), - close=Decimal('25'), - volume=Decimal('323') + open=Decimal("23"), + high=Decimal("26"), + low=Decimal("20"), + close=Decimal("25"), + volume=Decimal("323"), ) q3 = Quote( date=dt3, - open=Decimal('23'), - high=Decimal('26'), - low=Decimal('20'), - close=Decimal('25'), - volume=Decimal('323') + open=Decimal("23"), + high=Decimal("26"), + low=Decimal("20"), + close=Decimal("25"), + volume=Decimal("323"), ) - assert str(q1.date.tzinfo) == 'UTC' - assert str(q1.date.time()) == '23:00:00' + assert str(q1.date.tzinfo) == "UTC" + assert str(q1.date.time()) == "23:00:00" - assert str(q2.date.tzinfo) == 'None' - assert str(q2.date.time()) == '23:00:00' + assert str(q2.date.tzinfo) == "None" + assert str(q2.date.time()) == "23:00:00" - assert str(q3.date.tzinfo) == 'None' - assert str(q3.date.time()) == '00:00:00' + assert str(q3.date.tzinfo) == "None" + assert str(q3.date.time()) == "00:00:00" def test_quote_constructor_handles_system_local_timezone(): @@ -67,14 +68,16 @@ def test_quote_constructor_handles_system_local_timezone(): q = Quote( date=dt, - open=Decimal('23'), - high=Decimal('26'), - low=Decimal('20'), - close=Decimal('25'), - volume=Decimal('323'), + open=Decimal("23"), + high=Decimal("26"), + low=Decimal("20"), + close=Decimal("25"), + volume=Decimal("323"), ) # tz-aware should normalize to UTC but represent the same instant - d = getattr(q, 'date') - assert str(d.tzinfo) == 'UTC' - assert d.replace(microsecond=0) == dt.astimezone(timezone.utc).replace(microsecond=0) + d = q.date + assert str(d.tzinfo) == "UTC" + assert d.replace(microsecond=0) == dt.astimezone(timezone.utc).replace( + microsecond=0 + ) diff --git a/tests/common/test_sma_roundtrip_dates.py b/tests/common/test_sma_roundtrip_dates.py index a0f2d1d8..dbd3514c 100644 --- a/tests/common/test_sma_roundtrip_dates.py +++ b/tests/common/test_sma_roundtrip_dates.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone, timedelta +from datetime import datetime, timedelta, timezone from decimal import Decimal import pytest @@ -39,7 +39,9 @@ def _clone_with_date(quotes, transform): def _mk_utc(d: datetime) -> datetime: # Force tz-aware UTC at same Y-M-D h:m:s - return datetime(d.year, d.month, d.day, d.hour, d.minute, d.second, tzinfo=timezone.utc) + return datetime( + d.year, d.month, d.day, d.hour, d.minute, d.second, tzinfo=timezone.utc + ) def _mk_offset(d: datetime) -> datetime: @@ -59,7 +61,7 @@ def _mk_date_only(d: datetime) -> datetime: @pytest.mark.parametrize( - "variant, maker", + ("variant", "maker"), [ ("utc", _mk_utc), ("offset", _mk_offset), diff --git a/tests/common/test_type_compatibility.py b/tests/common/test_type_compatibility.py index 18da8b19..8f88eb58 100644 --- a/tests/common/test_type_compatibility.py +++ b/tests/common/test_type_compatibility.py @@ -1,15 +1,16 @@ - from enum import Enum, IntEnum -from stock_indicators._cslib import CsQuote, CsCandleProperties + +from stock_indicators._cslib import CsCandleProperties, CsQuote from stock_indicators.indicators.common.candles import CandleProperties from stock_indicators.indicators.common.enums import PivotPointType from stock_indicators.indicators.common.quote import Quote + class TestTypeCompat: def test_quote_based_class(self): # Quote assert issubclass(Quote, CsQuote) - + # CandleProperties assert issubclass(CandleProperties, Quote) assert issubclass(CandleProperties, CsQuote) diff --git a/tests/conftest.py b/tests/conftest.py index c8fbc33d..b7a09bcb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,6 @@ import pytest -from stock_indicators._cslib import clr from stock_indicators.indicators.common import Quote # pre-initialized from stock_indicators.logging_config import configure_logging @@ -38,7 +37,7 @@ def setup_logging(): @pytest.fixture(autouse=True, scope="session") def setup_clr_culture(): """Configure CLR culture settings for all tests.""" - import clr + import clr # noqa: F401 from System.Globalization import CultureInfo from System.Threading import Thread @@ -63,7 +62,8 @@ def get_data_from_csv(filename): quotes_dir = base_dir / "quotes" if not base_dir.exists() or not quotes_dir.exists(): raise FileNotFoundError( - "Test data not found. Please see README.md for test data setup instructions." + "Test data not found. Please see README.md " + "for test data setup instructions." ) data_path = quotes_dir / f"{filename}.csv" @@ -72,7 +72,7 @@ def get_data_from_csv(filename): if not data_path.exists(): raise FileNotFoundError(f"Test data file not found: {filename}") - with open(data_path, "r", newline="", encoding="utf-8") as csvfile: + with open(data_path, newline="", encoding="utf-8") as csvfile: reader = csv.reader(csvfile) data = list(reader) return data[1:] # skips the first row, those are headers @@ -89,11 +89,14 @@ def parse_decimal(value): def parse_date(date_str): """Parse date value across many common formats. + Supported families: - Date-only: YYYY-MM-DD, YYYYMMDD, DD-MM-YYYY, MM/DD/YYYY - - Naive date+time: YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH:MM:SS, YYYY-MM-DDTHH:MM:SS.sss[sss], YYYYMMDDTHHMMSS - - With offset: ISO-8601 extended with offset (e.g., +00:00, -04:00, +05:30, with optional fractions); - ISO basic with offset without colon: YYYYMMDDTHHMMSS+0000 + - Naive date+time: YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH:MM:SS, + YYYY-MM-DDTHH:MM:SS.sss[sss], YYYYMMDDTHHMMSS + - With offset: ISO-8601 extended with offset (e.g., +00:00, -04:00, +05:30, + with optional fractions); ISO basic with offset without colon: + YYYYMMDDTHHMMSS+0000 - Zulu: ...Z with optional fractional seconds - RFC1123/HTTP-date: Fri, 22 Aug 2025 17:45:30 GMT - IANA zone name appended after a space: YYYY-MM-DDTHH:MM:SS America/New_York @@ -123,7 +126,8 @@ def parse_date(date_str): try: return dt.replace(tzinfo=ZoneInfo(zone)) except Exception: - # Fallback if IANA zone isn't available on the system: treat as naive + # Fallback if IANA zone isn't available on the system: + # treat as naive return dt # ZoneInfo not available; treat as naive return dt @@ -144,7 +148,8 @@ def parse_date(date_str): # ISO extended with Zulu or offset, including fractional seconds if "T" in s and (s.endswith("Z") or re.search(r"[+-]\d{2}:?\d{2}$", s)): s_norm = s.replace("Z", "+00:00") - # If offset without colon at end (e.g., +0000), insert colon for fromisoformat + # If offset without colon at end (e.g., +0000), insert colon for + # fromisoformat m_off = re.search(r"([+-])(\d{2})(\d{2})$", s_norm) if m_off and ":" not in s_norm[-6:]: s_norm = ( @@ -182,7 +187,8 @@ def parse_date(date_str): # As a final attempt, try fromisoformat on whatever remains return datetime.fromisoformat(s) except Exception: - # Last-resort fallback to keep tests running; individual tests will assert equality + # Last-resort fallback to keep tests running; individual tests will + # assert equality return datetime.now() diff --git a/tests/test_adl.py b/tests/test_adl.py index 783d4551..cbc7d153 100644 --- a/tests/test_adl.py +++ b/tests/test_adl.py @@ -1,6 +1,8 @@ import pytest + from stock_indicators import indicators + class TestADL: def test_standard(self, quotes): results = indicators.get_adl(quotes) @@ -11,15 +13,15 @@ def test_standard(self, quotes): assert 502 == len(list(filter(lambda x: x.adl_sma is None, results))) r1 = results[249] - assert 0.7778 == round(float(r1.money_flow_multiplier), 4) - assert 36433792.89 == round(float(r1.money_flow_volume), 2) - assert 3266400865.74 == round(float(r1.adl), 2) + assert 0.7778 == round(float(r1.money_flow_multiplier), 4) + assert 36433792.89 == round(float(r1.money_flow_volume), 2) + assert 3266400865.74 == round(float(r1.adl), 2) assert r1.adl_sma is None r2 = results[501] - assert 0.8052 == round(float(r2.money_flow_multiplier), 4) - assert 118396116.25 == round(float(r2.money_flow_volume), 2) - assert 3439986548.42 == round(float(r2.adl), 2) + assert 0.8052 == round(float(r2.money_flow_multiplier), 4) + assert 118396116.25 == round(float(r2.money_flow_volume), 2) + assert 3439986548.42 == round(float(r2.adl), 2) assert r2.adl_sma is None # def test_convert_to_quotes(self, quotes): @@ -47,10 +49,10 @@ def test_with_sma(self, quotes): assert 483 == len(list(filter(lambda x: x.adl_sma is not None, results))) r = results[501] - assert 0.8052 == round(float(r.money_flow_multiplier), 4) - assert 118396116.25 == round(float(r.money_flow_volume), 2) - assert 3439986548.42 == round(float(r.adl), 2) - assert 3595352721.16 == round(float(r.adl_sma), 2) + assert 0.8052 == round(float(r.money_flow_multiplier), 4) + assert 118396116.25 == round(float(r.money_flow_volume), 2) + assert 3439986548.42 == round(float(r.adl), 2) + assert 3595352721.16 == round(float(r.adl_sma), 2) def test_condense(self, quotes): results = indicators.get_adl(quotes).condense() @@ -58,12 +60,13 @@ def test_condense(self, quotes): assert 502 == len(results) r = results[-1] - assert 0.8052 == round(float(r.money_flow_multiplier), 4) - assert 118396116.25 == round(float(r.money_flow_volume), 2) - assert 3439986548.42 == round(float(r.adl), 2) + assert 0.8052 == round(float(r.money_flow_multiplier), 4) + assert 118396116.25 == round(float(r.money_flow_volume), 2) + assert 3439986548.42 == round(float(r.adl), 2) assert r.adl_sma is None def test_exceptions(self, quotes): from System import ArgumentOutOfRangeException + with pytest.raises(ArgumentOutOfRangeException): indicators.get_adl(quotes, 0) diff --git a/tests/test_adx.py b/tests/test_adx.py index e5a24b68..040e1d3a 100644 --- a/tests/test_adx.py +++ b/tests/test_adx.py @@ -10,36 +10,63 @@ def test_standard(self, quotes): # proper quantities # should always be the same number of results as there is quotes assert 502 == len(results) + assert 488 == len(list(filter(lambda x: x.dx is not None, results))) assert 475 == len(list(filter(lambda x: x.adx is not None, results))) - assert 462 == len(list(filter(lambda x: x.adxr is not None, results))) + assert 461 == len(list(filter(lambda x: x.adxr is not None, results))) # sample values + r = results[13] + assert r.pdi is None + assert r.mdi is None + assert r.dx is None + assert r.adx is None + + r = results[14] + assert 21.9669 == round(float(r.pdi), 4) + assert 18.5462 == round(float(r.mdi), 4) + assert 8.4433 == round(float(r.dx), 4) + assert r.adx is None + r = results[19] assert 21.0361 == round(float(r.pdi), 4) assert 25.0124 == round(float(r.mdi), 4) + assert 8.6351 == round(float(r.dx), 4) assert r.adx is None + r = results[26] + assert r.adx is None + + r = results[27] + assert 15.9459 == round(float(r.adx), 4) + r = results[29] assert 37.9719 == round(float(r.pdi), 4) assert 14.1658 == round(float(r.mdi), 4) + assert 45.6600 == round(float(r.dx), 4) assert 19.7949 == round(float(r.adx), 4) r = results[39] assert r.adxr is None r = results[40] - assert 29.1062 == round(float(r.adxr), 4) + assert r.adxr is None + + r = results[41] + assert r.adxr is not None r = results[248] assert 32.3167 == round(float(r.pdi), 4) assert 18.2471 == round(float(r.mdi), 4) + assert 27.8255 == round(float(r.dx), 4) assert 30.5903 == round(float(r.adx), 4) - assert 29.1252 == round(float(r.adxr), 4) + assert r.adxr is not None r = results[501] assert 17.7565 == round(float(r.pdi), 4) assert 31.1510 == round(float(r.mdi), 4) + assert 27.3873 == round(float(r.dx), 4) assert 34.2987 == round(float(r.adx), 4) + assert r.adxr is not None def test_bad_data(self, quotes_bad): results = indicators.get_adx(quotes_bad, 20) diff --git a/tests/test_basic_quote.py b/tests/test_basic_quote.py index 5c4fde01..832ea008 100644 --- a/tests/test_basic_quote.py +++ b/tests/test_basic_quote.py @@ -4,11 +4,12 @@ from stock_indicators.indicators.common.enums import CandlePart from stock_indicators.indicators.common.quote import Quote + class TestBasicQuote: def test_standard(self, quotes): o = indicators.get_basic_quote(quotes, CandlePart.OPEN) h = indicators.get_basic_quote(quotes, CandlePart.HIGH) - l = indicators.get_basic_quote(quotes, CandlePart.LOW) + low = indicators.get_basic_quote(quotes, CandlePart.LOW) c = indicators.get_basic_quote(quotes, CandlePart.CLOSE) v = indicators.get_basic_quote(quotes, CandlePart.VOLUME) hl = indicators.get_basic_quote(quotes, CandlePart.HL2) @@ -16,24 +17,24 @@ def test_standard(self, quotes): oc = indicators.get_basic_quote(quotes, CandlePart.OC2) ohl = indicators.get_basic_quote(quotes, CandlePart.OHL3) ohlc = indicators.get_basic_quote(quotes, CandlePart.OHLC4) - + assert 502 == len(c) - + assert datetime(2018, 12, 31) == c[-1].date - - assert 244.92 == o[-1].value - assert 245.54 == h[-1].value - assert 242.87 == l[-1].value - assert 245.28 == c[-1].value - assert 147031456 == v[-1].value - assert 244.205 == hl[-1].value - assert 244.5633 == round(float(hlc[-1].value), 4) - assert 245.1 == oc[-1].value - assert 244.4433 == round(float(ohl[-1].value), 4) - assert 244.6525 == ohlc[-1].value - + + assert 244.92 == o[-1].value + assert 245.54 == h[-1].value + assert 242.87 == low[-1].value + assert 245.28 == c[-1].value + assert 147031456 == v[-1].value + assert 244.205 == hl[-1].value + assert 244.5633 == round(float(hlc[-1].value), 4) + assert 245.1 == oc[-1].value + assert 244.4433 == round(float(ohl[-1].value), 4) + assert 244.6525 == ohlc[-1].value + def test_use(self, quotes): results = Quote.use(quotes, CandlePart.CLOSE) results = list(results) - + assert 502 == len(results) diff --git a/tests/test_connors_rsi.py b/tests/test_connors_rsi.py index bcb759d7..d7fcb8c1 100644 --- a/tests/test_connors_rsi.py +++ b/tests/test_connors_rsi.py @@ -41,8 +41,6 @@ def test_removed(self, quotes): streak_periods = 2 rank_periods = 100 - removed_periods = max(rsi_periods, max(streak_periods, rank_periods)) + 2 - results = indicators.get_connors_rsi( quotes, rsi_periods, streak_periods, rank_periods ) diff --git a/tests/test_gator.py b/tests/test_gator.py index 4ba3a309..c039af33 100644 --- a/tests/test_gator.py +++ b/tests/test_gator.py @@ -31,47 +31,46 @@ def test_standard(self, quotes): assert r.upper is None assert -0.0406 == round(float(r.lower), 4) assert r.is_upper_expanding is None - assert r.is_lower_expanding == False + assert not r.is_lower_expanding r = results[19] assert r.upper is None assert -1.0018 == round(float(r.lower), 4) assert r.is_upper_expanding is None - assert r.is_lower_expanding == True + assert r.is_lower_expanding r = results[20] assert 0.4004 == round(float(r.upper), 4) assert -1.0130 == round(float(r.lower), 4) assert r.is_upper_expanding is None - assert r.is_lower_expanding == True + assert r.is_lower_expanding r = results[21] assert 0.7298 == round(float(r.upper), 4) assert -0.6072 == round(float(r.lower), 4) - assert r.is_upper_expanding == True - assert r.is_lower_expanding == False + assert r.is_upper_expanding + assert not r.is_lower_expanding r = results[99] assert 0.5159 == round(float(r.upper), 4) assert -0.2320 == round(float(r.lower), 4) - assert r.is_upper_expanding == False - assert r.is_lower_expanding == True + assert not r.is_upper_expanding + assert r.is_lower_expanding r = results[249] assert 3.1317 == round(float(r.upper), 4) assert -1.8058 == round(float(r.lower), 4) - assert r.is_upper_expanding == True - assert r.is_lower_expanding == False + assert r.is_upper_expanding + assert not r.is_lower_expanding r = results[501] assert 7.4538 == round(float(r.upper), 4) assert -9.2399 == round(float(r.lower), 4) - assert r.is_upper_expanding == True - assert r.is_lower_expanding == True + assert r.is_upper_expanding + assert r.is_lower_expanding def test_gator_with_alligator(self, quotes): alligator_results = indicators.get_alligator(quotes) - alligator_results.done() results = indicators.get_gator(alligator_results) assert 502 == len(results) @@ -95,8 +94,8 @@ def test_removed(self, quotes): last = results.pop() assert 7.4538 == round(float(last.upper), 4) assert -9.2399 == round(float(last.lower), 4) - assert last.is_upper_expanding == True - assert last.is_lower_expanding == True + assert last.is_upper_expanding + assert last.is_lower_expanding def test_condense(self, quotes): results = indicators.get_gator(quotes).condense() @@ -106,5 +105,5 @@ def test_condense(self, quotes): last = results.pop() assert 7.4538 == round(float(last.upper), 4) assert -9.2399 == round(float(last.lower), 4) - assert last.is_upper_expanding == True - assert last.is_lower_expanding == True + assert last.is_upper_expanding + assert last.is_lower_expanding diff --git a/tests/test_hurst.py b/tests/test_hurst.py index 8b653f4f..47f601de 100644 --- a/tests/test_hurst.py +++ b/tests/test_hurst.py @@ -14,7 +14,9 @@ def test_standard_long(self, quotes_longest): assert 0.483563 == round(float(r.hurst_exponent), 6) # def test_to_quotes(self, quotes_longest): - # new_quotes = indicators.get_hurst(quotes_longest, len(quotes_longest) - 1).to_quotes() + # new_quotes = indicators.get_hurst( + # quotes_longest, len(quotes_longest) - 1 + # ).to_quotes() # assert 1 == len(new_quotes) diff --git a/tests/test_parabolic_sar.py b/tests/test_parabolic_sar.py index fb248f19..5af9acb2 100644 --- a/tests/test_parabolic_sar.py +++ b/tests/test_parabolic_sar.py @@ -12,19 +12,19 @@ def test_standard(self, quotes): r = results[14] assert 212.8300 == round(float(r.sar), 4) - assert True == r.is_reversal + assert r.is_reversal r = results[16] assert 212.9924 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal r = results[94] assert 228.3600 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal r = results[501] assert 229.7662 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal def test_extended(self, quotes): accleration_step = 0.02 @@ -40,23 +40,23 @@ def test_extended(self, quotes): r = results[14] assert 212.8300 == round(float(r.sar), 4) - assert True == r.is_reversal + assert r.is_reversal r = results[16] assert 212.9518 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal r = results[94] assert 228.3600 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal r = results[486] assert 273.4148 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal r = results[501] assert 246.73 == round(float(r.sar), 4) - assert False == r.is_reversal + assert not r.is_reversal def test_bad_data(self, quotes_bad): r = indicators.get_parabolic_sar(quotes_bad) @@ -71,7 +71,7 @@ def test_removed(self, quotes): last = results.pop() assert 229.7662 == round(float(last.sar), 4) - assert False == last.is_reversal + assert not last.is_reversal def test_condense(self, quotes): results = indicators.get_parabolic_sar(quotes, 0.02, 0.2).condense() @@ -80,7 +80,7 @@ def test_condense(self, quotes): last = results.pop() assert 229.7662 == round(float(last.sar), 4) - assert False == last.is_reversal + assert not last.is_reversal def test_exceptions(self, quotes): from System import ArgumentOutOfRangeException diff --git a/tests/test_renko.py b/tests/test_renko.py index 72c20f81..56e389ba 100644 --- a/tests/test_renko.py +++ b/tests/test_renko.py @@ -18,7 +18,7 @@ def test_standard_close(self, quotes): assert 212.53 == float(round(r.low, 2)) assert 215.5 == float(round(r.close, 1)) assert 1180981564 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up r = results[5] assert 225.5 == float(round(r.open, 1)) @@ -26,7 +26,7 @@ def test_standard_close(self, quotes): assert 219.77 == float(round(r.low, 2)) assert 228 == float(round(r.close, 0)) assert 4192959240 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up r = results.pop() assert 240.5 == float(round(r.open, 1)) @@ -34,7 +34,7 @@ def test_standard_close(self, quotes): assert 234.52 == float(round(r.low, 2)) assert 243 == float(round(r.close, 0)) assert 189794032 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up def test_standard_high_low(self, quotes): results = indicators.get_renko(quotes, 2.5, EndType.HIGH_LOW) @@ -47,7 +47,7 @@ def test_standard_high_low(self, quotes): assert 212.53 == float(round(r.low, 2)) assert 215.5 == float(round(r.close, 1)) assert 1180981564 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up r = results[25] assert 270.5 == float(round(r.open, 1)) @@ -55,7 +55,7 @@ def test_standard_high_low(self, quotes): assert 271.96 == float(round(r.low, 2)) assert 273 == float(round(r.close, 0)) assert 100801672 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up r = results.pop() assert 243 == float(round(r.open, 0)) @@ -63,7 +63,7 @@ def test_standard_high_low(self, quotes): assert 241.87 == float(round(r.low, 2)) assert 245.5 == float(round(r.close, 1)) assert 51999637 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up def test_renko_atr(self, quotes): results = indicators.get_renko_atr(quotes, 14, EndType.CLOSE) @@ -76,7 +76,7 @@ def test_renko_atr(self, quotes): assert 212.53 == float(round(r.low, 2)) assert 218.9497 == float(round(r.close, 4)) assert 2090292272 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up r = results.pop() assert 237.3990 == float(round(r.open, 4)) @@ -84,7 +84,7 @@ def test_renko_atr(self, quotes): assert 229.42 == float(round(r.low, 2)) assert 243.5487 == float(round(r.close, 4)) assert 715446448 == float(round(r.volume, 0)) - assert r.is_up == True + assert r.is_up def test_bad_data(self, quotes_bad): r = indicators.get_renko(quotes_bad, 100) diff --git a/tests/test_sma_analysis.py b/tests/test_sma_analysis.py index b7a49a4f..a5a0c5f3 100644 --- a/tests/test_sma_analysis.py +++ b/tests/test_sma_analysis.py @@ -1,7 +1,6 @@ import pytest from stock_indicators import indicators -from stock_indicators._cslib import CsDecimal class TestSMAExtended: @@ -10,10 +9,10 @@ def test_result_types(self, quotes): # Sample value. r = results[501] - assert float == type(r._csdata.Sma) - assert float == type(r._csdata.Mad) - assert float == type(r._csdata.Mse) - assert float == type(r._csdata.Mape) + assert isinstance(r._csdata.Sma, float) + assert isinstance(r._csdata.Mad, float) + assert isinstance(r._csdata.Mse, float) + assert isinstance(r._csdata.Mape, float) def test_extended(self, quotes): results = indicators.get_sma_analysis(quotes, 20) diff --git a/tests/test_starc_bands.py b/tests/test_starc_bands.py index e4f3bf56..981db6d7 100644 --- a/tests/test_starc_bands.py +++ b/tests/test_starc_bands.py @@ -8,7 +8,6 @@ def test_standard(self, quotes): sma_periods = 20 multiplier = 2 atr_periods = 14 - lookback_periods = max(sma_periods, atr_periods) results = indicators.get_starc_bands( quotes, sma_periods, multiplier, atr_periods diff --git a/tests/test_stdev.py b/tests/test_stdev.py index 4e622236..87836176 100644 --- a/tests/test_stdev.py +++ b/tests/test_stdev.py @@ -1,6 +1,8 @@ import pytest + from stock_indicators import indicators + class TestStdev: def test_standard(self, quotes): results = indicators.get_stdev(quotes, 10) @@ -8,7 +10,7 @@ def test_standard(self, quotes): assert 502 == len(results) assert 493 == len(list(filter(lambda x: x.stdev is not None, results))) assert 493 == len(list(filter(lambda x: x.z_score is not None, results))) - assert 0 == len(list(filter(lambda x: x.stdev_sma is not None, results))) + assert 0 == len(list(filter(lambda x: x.stdev_sma is not None, results))) r = results[8] assert r.stdev is None @@ -17,19 +19,19 @@ def test_standard(self, quotes): assert r.stdev_sma is None r = results[9] - assert 0.5020 == round(float(r.stdev), 4) - assert 214.0140 == round(float(r.mean), 4) + assert 0.5020 == round(float(r.stdev), 4) + assert 214.0140 == round(float(r.mean), 4) assert -0.525917 == round(float(r.z_score), 6) assert r.stdev_sma is None r = results[249] - assert 0.9827 == round(float(r.stdev), 4) + assert 0.9827 == round(float(r.stdev), 4) assert 257.2200 == round(float(r.mean), 4) assert 0.783563 == round(float(r.z_score), 6) assert r.stdev_sma is None r = results[501] - assert 5.4738 == round(float(r.stdev), 4) + assert 5.4738 == round(float(r.stdev), 4) assert 242.4100 == round(float(r.mean), 4) assert 0.524312 == round(float(r.z_score), 6) assert r.stdev_sma is None @@ -43,14 +45,14 @@ def test_stdev_with_sma(self, quotes): assert 489 == len(list(filter(lambda x: x.stdev_sma is not None, results))) r = results[19] - assert 1.1642 == round(float(r.stdev), 4) + assert 1.1642 == round(float(r.stdev), 4) assert -0.065282 == round(float(r.z_score), 6) - assert 1.1422 == round(float(r.stdev_sma), 4) + assert 1.1422 == round(float(r.stdev_sma), 4) r = results[501] - assert 5.4738 == round(float(r.stdev), 4) + assert 5.4738 == round(float(r.stdev), 4) assert 0.524312 == round(float(r.z_score), 6) - assert 7.6886 == round(float(r.stdev_sma), 4) + assert 7.6886 == round(float(r.stdev_sma), 4) def test_bad_data(self, quotes_bad): r = indicators.get_stdev(quotes_bad, 15, 3) @@ -73,7 +75,7 @@ def test_removed(self, quotes): assert 502 - 9 == len(results) last = results.pop() - assert 5.4738 == round(float(last.stdev), 4) + assert 5.4738 == round(float(last.stdev), 4) assert 242.4100 == round(float(last.mean), 4) assert 0.524312 == round(float(last.z_score), 6) assert last.stdev_sma is None @@ -84,15 +86,16 @@ def test_condense(self, quotes): assert 493 == len(results) last = results.pop() - assert 5.4738 == round(float(last.stdev), 4) + assert 5.4738 == round(float(last.stdev), 4) assert 242.4100 == round(float(last.mean), 4) assert 0.524312 == round(float(last.z_score), 6) assert last.stdev_sma is None def test_exceptions(self, quotes): from System import ArgumentOutOfRangeException + with pytest.raises(ArgumentOutOfRangeException): indicators.get_stdev(quotes, 1) with pytest.raises(ArgumentOutOfRangeException): - indicators.get_stdev(quotes, 14,0) + indicators.get_stdev(quotes, 14, 0) diff --git a/tests/test_stoch.py b/tests/test_stoch.py index e53cff51..c665997b 100644 --- a/tests/test_stoch.py +++ b/tests/test_stoch.py @@ -125,9 +125,6 @@ def test_boundary(self, quotes): assert 0 == len( list(filter(lambda x: x.d is not None and (x.d < 0 or x.d > 100), results)) ) - assert 0 == len( - list(filter(lambda x: x.j is not None and (x.d < 0 or x.d > 100), results)) - ) def test_removed(self, quotes): results = indicators.get_stoch(quotes, 14, 3, 3).remove_warmup_periods() diff --git a/tests/test_trix.py b/tests/test_trix.py index 12392e80..1ee5a0dd 100644 --- a/tests/test_trix.py +++ b/tests/test_trix.py @@ -1,6 +1,8 @@ import pytest + from stock_indicators import indicators + class TestTRIX: def test_standard(self, quotes): results = indicators.get_trix(quotes, 20, 5) @@ -21,7 +23,7 @@ def test_standard(self, quotes): assert 0.119769 == round(float(r.signal), 6) r = results[501] - assert 263.3216 == round(float(r.ema3), 4) + assert 263.3216 == round(float(r.ema3), 4) assert -0.230742 == round(float(r.trix), 6) assert -0.204536 == round(float(r.signal), 6) @@ -35,7 +37,7 @@ def test_removed(self, quotes): assert 502 - ((3 * 20) + 100) == len(results) last = results.pop() - assert 263.3216 == round(float(last.ema3), 4) + assert 263.3216 == round(float(last.ema3), 4) assert -0.230742 == round(float(last.trix), 6) assert -0.204536 == round(float(last.signal), 6) @@ -45,11 +47,12 @@ def test_condense(self, quotes): assert 482 == len(results) last = results.pop() - assert 263.3216 == round(float(last.ema3), 4) + assert 263.3216 == round(float(last.ema3), 4) assert -0.230742 == round(float(last.trix), 6) assert -0.204536 == round(float(last.signal), 6) def test_exceptions(self, quotes): from System import ArgumentOutOfRangeException + with pytest.raises(ArgumentOutOfRangeException): indicators.get_trix(quotes, 0) diff --git a/tests/test_tsi.py b/tests/test_tsi.py index 1012e80e..e43ad45d 100644 --- a/tests/test_tsi.py +++ b/tests/test_tsi.py @@ -1,6 +1,8 @@ import pytest + from stock_indicators import indicators + class TestTSI: def test_standard(self, quotes): results = indicators.get_tsi(quotes, 25, 13, 7) @@ -68,6 +70,7 @@ def test_condense(self, quotes): def test_exceptions(self, quotes): from System import ArgumentOutOfRangeException + with pytest.raises(ArgumentOutOfRangeException): indicators.get_tsi(quotes, 0) diff --git a/tests/test_ulcer_index.py b/tests/test_ulcer_index.py index 7747e629..770134ac 100644 --- a/tests/test_ulcer_index.py +++ b/tests/test_ulcer_index.py @@ -1,6 +1,8 @@ import pytest + from stock_indicators import indicators + class TestUlcerIndex: def test_standard(self, quotes): results = indicators.get_ulcer_index(quotes, 14) @@ -40,5 +42,6 @@ def test_condense(self, quotes): def test_exceptions(self, quotes): from System import ArgumentOutOfRangeException + with pytest.raises(ArgumentOutOfRangeException): indicators.get_ulcer_index(quotes, 0) diff --git a/tests/test_vwap.py b/tests/test_vwap.py index 32eeb694..2de05ac0 100644 --- a/tests/test_vwap.py +++ b/tests/test_vwap.py @@ -6,7 +6,6 @@ class TestVWAP: - def test_standard(self, quotes_intraday): quotes_intraday.sort(key=lambda x: x.date) results = indicators.get_vwap(quotes_intraday[:391]) diff --git a/tests/utiltest.py b/tests/utiltest.py index cab72173..41f088c6 100644 --- a/tests/utiltest.py +++ b/tests/utiltest.py @@ -1,20 +1,25 @@ -import os import json +import os from datetime import datetime from stock_indicators.indicators.common.quote import Quote + def load_quotes_from_json(json_path): base_dir = os.path.dirname(__file__) data_path = os.path.join(base_dir, json_path) quotes = [] - with open(data_path, "r", encoding="utf-8") as st_json: + with open(data_path, encoding="utf-8") as st_json: for j in json.load(st_json): - quotes.append(Quote(datetime.fromisoformat(j["Date"]), + quotes.append( + Quote( + datetime.fromisoformat(j["Date"]), j["Open"], j["High"], j["Low"], j["Close"], - j["Volume"])) + j["Volume"], + ) + ) return quotes diff --git a/typings/Skender/Stock/Indicators/__init__.pyi b/typings/Skender/Stock/Indicators/__init__.pyi new file mode 100644 index 00000000..5a21d12b --- /dev/null +++ b/typings/Skender/Stock/Indicators/__init__.pyi @@ -0,0 +1,17 @@ +from typing import Any + +BetaType: Any +CandlePart: Any +CandleProperties: Any +ChandelierType: Any +EndType: Any +Indicator: Any +Match: Any +MaType: Any +PeriodSize: Any +PivotPointType: Any +PivotTrend: Any +Quote: Any +QuoteUtility: Any +ResultBase: Any +ResultUtility: Any diff --git a/typings/System/Collections/Generic/__init__.pyi b/typings/System/Collections/Generic/__init__.pyi new file mode 100644 index 00000000..896f37b9 --- /dev/null +++ b/typings/System/Collections/Generic/__init__.pyi @@ -0,0 +1,4 @@ +from typing import Any + +IEnumerable: Any +List: Any diff --git a/typings/System/Globalization/__init__.pyi b/typings/System/Globalization/__init__.pyi new file mode 100644 index 00000000..03519532 --- /dev/null +++ b/typings/System/Globalization/__init__.pyi @@ -0,0 +1,4 @@ +from typing import Any + +CultureInfo: Any +NumberStyles: Any diff --git a/typings/System/Threading/__init__.pyi b/typings/System/Threading/__init__.pyi new file mode 100644 index 00000000..66e340af --- /dev/null +++ b/typings/System/Threading/__init__.pyi @@ -0,0 +1,3 @@ +from typing import Any + +Thread: Any diff --git a/typings/System/__init__.pyi b/typings/System/__init__.pyi new file mode 100644 index 00000000..baacb27f --- /dev/null +++ b/typings/System/__init__.pyi @@ -0,0 +1,8 @@ +from typing import Any + +ArgumentOutOfRangeException: Any +DateTime: Any +Decimal: Any +Enum: Any +IO: Any +AppDomain: Any diff --git a/typings/pythonnet/__init__.pyi b/typings/pythonnet/__init__.pyi new file mode 100644 index 00000000..4b822079 --- /dev/null +++ b/typings/pythonnet/__init__.pyi @@ -0,0 +1,3 @@ +from typing import Any + +def load(runtime: str | None = ...) -> Any: ... diff --git a/typings/stock_indicators/_cslib/__init__.pyi b/typings/stock_indicators/_cslib/__init__.pyi new file mode 100644 index 00000000..116d292c --- /dev/null +++ b/typings/stock_indicators/_cslib/__init__.pyi @@ -0,0 +1,25 @@ +from typing import Any + +clr: Any +CsIndicator: Any +CsResultUtility: Any +CsQuote: Any +CsQuoteUtility: Any +CsResultBase: Any +CsBetaType: Any +CsCandlePart: Any +CsCandleProperties: Any +CsChandelierType: Any +CsEndType: Any +CsMatch: Any +CsMaType: Any +CsPeriodSize: Any +CsPivotPointType: Any +CsPivotTrend: Any +CsDateTime: Any +CsDecimal: Any +CsEnum: Any +CsIEnumerable: Any +CsList: Any +CsCultureInfo: Any +CsNumberStyles: Any