diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 780d840e..a7cfc876 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" + UV_VERSION: "0.12.7" UV_PROJECT_ENVIRONMENT: venv concurrency: diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 00000000..7aece740 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,121 @@ +name: codecov.yml + +permissions: + contents: read + actions: read # Required for setup-python and other actions to read action metadata + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + UV_VERSION: "0.12.7" + UV_PROJECT_ENVIRONMENT: venv + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - master + paths: + - 'openseries/**' + - 'tests/**' + - 'pyproject.toml' + - 'uv.lock' + - '.python-version' + - '.github/workflows/codecov.yml' + - 'Makefile' + - 'make.ps1' + - '.pre-commit-config.yaml' + - 'scripts/ci-pr-paths.sh' + workflow_dispatch: {} + +jobs: + upload_coverage: + name: Upload coverage + runs-on: ubuntu-latest + environment: codecov + defaults: + run: + shell: bash + permissions: + contents: read + actions: read # Required for setup-python and other actions to read action metadata + issues: write # Required for actions/github-script to create issues on test failure + + steps: + - name: Require master branch + env: + REF: ${{ github.ref }} + run: | + if [ "$REF" != "refs/heads/master" ]; then + echo "::error::Codecov reporting is limited to the master branch." + exit 1 + fi + + - name: Check out GitHub repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || github.sha }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version-file: '.python-version' + + - name: Set up uv + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + version: ${{ env.UV_VERSION }} + + - name: Sync dependencies (locked) + run: uv sync --locked --extra dev + + - name: Tests with Pytest + id: pytest + continue-on-error: true + run: uv run pytest + env: + PYTHONPATH: ${{ github.workspace }} + + - name: Create GitHub issue on failure + if: ${{ steps.pytest.outcome == 'failure' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_OWNER: ${{ github.repository_owner }} + GH_REPO: ${{ github.event.repository.name }} + GH_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + script: | + const owner = process.env.GH_OWNER; + const repo = process.env.GH_REPO; + const runUrl = process.env.GH_RUN_URL; + + await github.rest.issues.create({ + owner, + repo, + title: `Tests failed on ${new Date().toDateString()}`, + body: `See the full logs here: ${runUrl}`, + }); + + - name: Fail job if tests failed + if: ${{ steps.pytest.outcome == 'failure' }} + run: exit 1 + + - name: Upload test results to Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: junit.xml + report_type: test_results + verbose: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: CaptorAB/openseries + files: coverage.xml + verbose: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3a5ff871..f03b2dc4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,20 +3,19 @@ name: codeql.yml permissions: contents: read actions: read # Required for setup-python and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" + UV_VERSION: "0.12.7" UV_PROJECT_ENVIRONMENT: venv concurrency: - group: codeql-${{ github.ref }} + group: codeql-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} cancel-in-progress: true on: workflow_dispatch: {} - push: - branches: [master] pull_request: branches: [master] schedule: @@ -28,6 +27,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + actions: read # Required for setup-python and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering security-events: write # Required for github/codeql-action/analyze to upload results steps: - name: Check out repository @@ -35,25 +36,45 @@ jobs: with: persist-credentials: false + - name: Filter paths + id: paths + env: + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash scripts/ci-pr-paths.sh + 'openseries/*' + 'tests/*' + 'pyproject.toml' + 'uv.lock' + '.github/workflows/codeql.yml' + 'scripts/ci-pr-paths.sh' + - name: Initialize CodeQL + if: ${{ steps.paths.outputs.run == 'true' }} uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: python - name: Set up Python + if: ${{ steps.paths.outputs.run == 'true' }} uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: .python-version - name: Set up uv + if: ${{ steps.paths.outputs.run == 'true' }} uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} - name: Sync dependencies (locked) + if: ${{ steps.paths.outputs.run == 'true' }} run: uv sync --locked --extra dev - name: Perform CodeQL analysis + if: ${{ steps.paths.outputs.run == 'true' }} uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: category: /language:python diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 98967457..21f4a092 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,7 +5,7 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" + UV_VERSION: "0.12.7" UV_PROJECT_ENVIRONMENT: venv concurrency: @@ -33,7 +33,6 @@ jobs: name: Build and test needs: gate runs-on: ubuntu-latest - environment: codecov permissions: contents: read issues: write # Required for actions/github-script to create issues on test failure @@ -93,24 +92,6 @@ jobs: body: `See the full logs here: ${GH_RUN_URL}`, }); - - name: Upload test results to Codecov - if: ${{ github.ref_name == 'master' && success() }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - report_type: test_results - files: junit.xml - verbose: true - - - name: Upload coverage to Codecov - if: ${{ github.ref_name == 'master' && success() }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: CaptorAB/openseries - files: coverage.xml - verbose: true - tag_and_release: name: Tag and create release needs: build_test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 60072f34..29948c14 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,7 +5,7 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" + UV_VERSION: "0.12.7" UV_PROJECT_ENVIRONMENT: venv concurrency: @@ -13,32 +13,39 @@ concurrency: cancel-in-progress: true on: - # Only run on pushes to master for documentation-related changes - # This prevents running on every commit, only when docs actually change push: branches: - master paths: - 'docs/**' + - 'openseries/**' + - 'pyproject.toml' + - 'uv.lock' + - '.python-version' - '.readthedocs.yaml' - # Manual trigger for immediate updates (e.g., after releases) + - '.github/workflows/docs.yml' + pull_request: + branches: + - master + paths: + - 'docs/**' + - 'openseries/**' + - 'pyproject.toml' + - 'uv.lock' + - '.python-version' + - '.readthedocs.yaml' + - '.github/workflows/docs.yml' workflow_dispatch: {} jobs: - build-and-deploy: - name: Build and deploy documentation + build: + name: Build documentation defaults: run: shell: bash runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/master' permissions: contents: read - pages: write # Required for actions/deploy-pages to deploy to GitHub Pages - id-token: write # Required for OIDC token for GitHub Pages deployment - environment: - name: github-pages - steps: - name: Check out GitHub repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -65,12 +72,36 @@ jobs: run: uv run --directory docs make builddocs - name: Check for documentation warnings - run: | - uv run --directory docs make strict || { - echo "Documentation build failed with warnings/errors:" - echo "This indicates potential issues that should be fixed." - exit 1 - } + run: uv run --directory docs make strict + + - name: Upload HTML artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: docs-html + path: docs/build/html + if-no-files-found: error + retention-days: 1 + + deploy: + name: Deploy documentation + needs: build + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'push' }} + defaults: + run: + shell: bash + runs-on: ubuntu-latest + permissions: + contents: read + pages: write # Required for actions/deploy-pages to deploy to GitHub Pages + id-token: write # Required for OIDC token for GitHub Pages deployment + environment: + name: github-pages + steps: + - name: Download HTML artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: docs-html + path: docs/build/html - name: Setup Pages uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index cd1843e7..e78fe6b9 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -3,10 +3,11 @@ name: supply-chain.yml permissions: contents: read actions: read # Required for setup-uv and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" + UV_VERSION: "0.12.7" UV_PROJECT_ENVIRONMENT: venv PIP_AUDIT_VERSION: "2.10.0" @@ -16,10 +17,10 @@ concurrency: on: workflow_dispatch: {} - push: - branches: [master] pull_request: branches: [master] + schedule: + - cron: "15 6 * * 1" jobs: lockfile: @@ -31,12 +32,27 @@ jobs: with: persist-credentials: false + - name: Filter paths + id: paths + env: + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash scripts/ci-pr-paths.sh + 'uv.lock' + 'pyproject.toml' + '.github/workflows/supply-chain.yml' + 'scripts/ci-pr-paths.sh' + - name: Set up uv + if: ${{ steps.paths.outputs.run == 'true' }} uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} - name: Check uv.lock is up to date + if: ${{ steps.paths.outputs.run == 'true' }} run: uv lock --check osv-scanner: @@ -48,7 +64,21 @@ jobs: with: persist-credentials: false + - name: Filter paths + id: paths + env: + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash scripts/ci-pr-paths.sh + 'uv.lock' + 'pyproject.toml' + '.github/workflows/supply-chain.yml' + 'scripts/ci-pr-paths.sh' + - name: Run OSV-Scanner + if: ${{ steps.paths.outputs.run == 'true' }} uses: google/osv-scanner-action/osv-scanner-action@8deb546fdb875b9996d27d4950be7312dac076a1 # v2.5.0 with: scan-args: --lockfile=uv.lock @@ -63,23 +93,41 @@ jobs: with: persist-credentials: false + - name: Filter paths + id: paths + env: + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash scripts/ci-pr-paths.sh + 'uv.lock' + 'pyproject.toml' + '.github/workflows/supply-chain.yml' + 'scripts/ci-pr-paths.sh' + - name: Set up Python + if: ${{ steps.paths.outputs.run == 'true' }} uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: .python-version - name: Set up uv + if: ${{ steps.paths.outputs.run == 'true' }} uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} - name: Sync dependencies (locked) + if: ${{ steps.paths.outputs.run == 'true' }} run: uv sync --locked --extra dev - name: Export requirements for pip-audit + if: ${{ steps.paths.outputs.run == 'true' }} run: uv export --locked --extra dev --no-emit-project -o requirements-audit.txt - name: Run pip-audit + if: ${{ steps.paths.outputs.run == 'true' }} env: PIP_AUDIT_VERSION: ${{ env.PIP_AUDIT_VERSION }} run: uvx pip-audit==${PIP_AUDIT_VERSION} -r requirements-audit.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4c77b76..0a77483f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,10 +3,11 @@ name: tests.yml permissions: contents: read actions: read # Required for setup-python and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" + UV_VERSION: "0.12.7" UV_PROJECT_ENVIRONMENT: venv concurrency: @@ -15,9 +16,6 @@ concurrency: on: workflow_dispatch: {} - push: - branches: - - master pull_request: branches: - master @@ -26,11 +24,13 @@ jobs: run_tests: name: Run tests runs-on: ubuntu-latest - environment: codecov defaults: run: shell: bash permissions: + contents: read + actions: read # Required for setup-python and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering issues: write # Required for actions/github-script to create issues on test failure steps: @@ -41,27 +41,52 @@ jobs: ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || github.sha }} persist-credentials: false + - name: Filter paths + id: paths + env: + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash scripts/ci-pr-paths.sh + 'openseries/*' + 'tests/*' + 'pyproject.toml' + 'uv.lock' + '.python-version' + '.github/workflows/test.yml' + '.github/workflows/codecov.yml' + 'Makefile' + 'make.ps1' + '.pre-commit-config.yaml' + 'scripts/ci-pr-paths.sh' + - name: Set up Python + if: ${{ steps.paths.outputs.run == 'true' }} uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: '.python-version' - name: Set up uv + if: ${{ steps.paths.outputs.run == 'true' }} uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} - name: Sync dependencies (locked) + if: ${{ steps.paths.outputs.run == 'true' }} run: uv sync --locked --extra dev - name: Check and fix with Ruff + if: ${{ steps.paths.outputs.run == 'true' }} run: uv run ruff check ./tests/*.py ./openseries/*.py --fix --exit-non-zero-on-fix - name: Format with Ruff + if: ${{ steps.paths.outputs.run == 'true' }} run: uv run ruff format - name: Manage Mypy cache - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + if: ${{ steps.paths.outputs.run == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: mypy-cache with: @@ -70,9 +95,11 @@ jobs: restore-keys: mypy-cache- - name: Type check with Mypy + if: ${{ steps.paths.outputs.run == 'true' }} run: uv run mypy --cache-dir .mypy_cache . - name: Tests with Pytest + if: ${{ steps.paths.outputs.run == 'true' }} id: pytest continue-on-error: true run: uv run pytest @@ -102,21 +129,3 @@ jobs: - name: Fail job if tests failed if: ${{ steps.pytest.outcome == 'failure' }} run: exit 1 - - - name: Upload test results to Codecov - if: ${{ github.ref_name == 'master' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: junit.xml - report_type: test_results - verbose: true - - - name: Upload coverage to Codecov - if: ${{ github.ref_name == 'master' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: CaptorAB/openseries - files: coverage.xml - verbose: true diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 57621d47..8aefd481 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -3,11 +3,12 @@ name: zizmor.yml permissions: contents: read actions: read # Required for setup-uv and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - UV_VERSION: "0.11.21" - ZIZMOR_VERSION: "1.25.2" + UV_VERSION: "0.12.7" + ZIZMOR_VERSION: "1.29.0" concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} @@ -15,8 +16,6 @@ concurrency: on: workflow_dispatch: {} - push: - branches: [master] pull_request: branches: [master] @@ -25,6 +24,9 @@ jobs: name: Run zizmor audit runs-on: ubuntu-latest permissions: + contents: read + actions: read # Required for setup-uv and other actions to read action metadata + pull-requests: read # Required to list PR files for path filtering security-events: write # Required for github/codeql-action/upload-sarif to upload SARIF results steps: - name: Checkout repository @@ -34,26 +36,40 @@ jobs: ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || github.sha }} persist-credentials: false + - name: Filter paths + id: paths + env: + GH_TOKEN: ${{ github.token }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash scripts/ci-pr-paths.sh + '.github/*' + 'scripts/run-zizmor.sh' + 'scripts/ci-pr-paths.sh' + - name: Set up uv + if: ${{ steps.paths.outputs.run == 'true' }} uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} - name: Run zizmor audit + if: ${{ steps.paths.outputs.run == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ZIZMOR_VERSION: ${{ env.ZIZMOR_VERSION }} run: uvx zizmor==${ZIZMOR_VERSION} --strict-collection --pedantic . - name: Generate zizmor SARIF report - if: always() + if: ${{ always() && steps.paths.outputs.run == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ZIZMOR_VERSION: ${{ env.ZIZMOR_VERSION }} run: uvx zizmor==${ZIZMOR_VERSION} --strict-collection --pedantic --format=sarif . > results.sarif - name: Upload SARIF file - if: always() + if: ${{ always() && steps.paths.outputs.run == 'true' }} uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b49afa2..f4051697 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,12 +3,12 @@ default_language_version: repos: - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.11.21 + rev: 0.12.7 hooks: - id: uv-lock args: [ --check ] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.18 + rev: v0.16.5 hooks: - id: ruff-check args: [ --fix, --exit-non-zero-on-fix ] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e411019b..8fb2ff3a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -15,6 +15,7 @@ sphinx: python: install: - - requirements: docs/requirements.txt - method: pip path: . + extra_requirements: + - docs diff --git a/Makefile b/Makefile index 0afe3122..e11b6544 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .ONESHELL: -UV_VERSION ?= 0.11.21 +UV_VERSION ?= 0.12.7 PIP_AUDIT_VERSION ?= 2.10.0 .PHONY: all install update test lint audit clean builddocs servedocs cleandocs diff --git a/README.md b/README.md index 01c891b0..d7096641 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Python version](https://img.shields.io/pypi/pyversions/openseries.svg)](https://www.python.org/) [![GitHub Action Test Suite](https://github.com/CaptorAB/openseries/actions/workflows/test.yml/badge.svg)](https://github.com/CaptorAB/openseries/actions/workflows/test.yml) [![codecov](https://img.shields.io/codecov/c/gh/CaptorAB/openseries?logo=codecov)](https://codecov.io/gh/CaptorAB/openseries/branch/master) -[![Documentation Status](https://readthedocs.org/projects/openseries/badge/?version=latest)](https://captorab.github.io/openseries/) +[![Documentation Status](https://readthedocs.org/projects/openseries/badge/?version=latest)](https://openseries.readthedocs.io/) [![uv](https://img.shields.io/badge/package%20manager-uv-blueviolet)](https://github.com/astral-sh/uv) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://beta.ruff.rs/docs/) [![GitHub License](https://img.shields.io/github/license/CaptorAB/openseries)](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md) @@ -20,7 +20,7 @@ Tools for analyzing financial timeseries of a single asset or a group of assets. ## Documentation -Complete documentation is available at: [https://captorab.github.io/openseries/](https://captorab.github.io/openseries/) +Complete documentation is available at: [https://openseries.readthedocs.io/](https://openseries.readthedocs.io/) The documentation includes: @@ -47,11 +47,11 @@ conda install -c conda-forge openseries from openseries import OpenTimeSeries import yfinance as yf -move=yf.Ticker(ticker="^MOVE") -history=move.history(period="max") -series=OpenTimeSeries.from_df(dframe=history.loc[:, "Close"]) -_=series.set_new_label(lvl_zero="ICE BofAML MOVE Index") -_,_=series.plot_series() +move = yf.Ticker(ticker="^MOVE") +history = move.history(period="max") +series = OpenTimeSeries.from_df(dframe=history.loc[:, "Close"]) +_ = series.set_new_label(lvl_zero="ICE BofAML MOVE Index") +_, _ = series.plot_series() ``` ### Sample output using the report_html() function diff --git a/SECURITY.md b/SECURITY.md index 2cb5d5c7..31e53fd7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,11 +19,22 @@ vulnerabilities. - **CI workflows** use hash-pinned actions, default read-only `contents`, and [zizmor](https://github.com/woodruffw/zizmor) audits. - **Dependencies** are locked in `uv.lock`; CI runs `uv sync --locked` and - supply-chain scans (`supply-chain.yml`). + supply-chain scans (`supply-chain.yml`) on pull requests that change lockfiles + and on a weekly schedule. - **Releases** build from the signed git tag, record `SHA256SUMS` for artifacts, and verify checksums before publish. - **`pull_request_target` is not used**; PR CI runs on `pull_request` with read-only defaults and fork guards on cache restore and issue creation. +- **Validation workflows** (`tests.yml`, `supply-chain.yml`, `zizmor.yml`, + `codeql.yml`) run on pull requests, not again on merge to `master`. Path + filters skip heavy steps when the PR does not touch relevant files, while the + jobs still report success so required checks are not left pending. +- **CodeQL** full analysis runs on pull requests that change Python sources and + on a weekly schedule; it is not repeated on every push to `master`. +- **Documentation** is published at + [openseries.readthedocs.io](https://openseries.readthedocs.io/) (the URL in + PyPI and conda-forge metadata) and also deployed to GitHub Pages by + `docs.yml`. Pull requests build docs without deploying Pages. - **Release tagging** is isolated in a reusable workflow (`release-tag.yml`); build and PyPI publish run in `deploy.yml` because PyPI Trusted Publishing does not support reusable workflows. `deploy.yml` is the only manual entry @@ -47,10 +58,16 @@ YAML alone: - Required reviewers before deployment - Restrict deployment branches to `master` - Do not expose secrets to fork PR workflows + - `codecov` is used only by `codecov.yml` on `master` (not PR tests or + `deploy.yml`) 4. **Branch protection on `master`**: - - Require status checks from `tests.yml`, `supply-chain.yml`, `zizmor.yml`, - and CodeQL before merge + - Require status checks from `tests.yml`, `supply-chain.yml`, and + `zizmor.yml` before merge. Do not require the Read the Docs PR check. + CodeQL may remain required; the `codeql.yml` job no-ops on PRs that do + not change Python sources. - Require review for changes under `.github/workflows/` + - Keep the Read the Docs GitHub integration so `latest` still builds; turn + off **Build pull requests** in the RTD project so PRs are not double-built. 5. **Dependabot**: keep weekly updates with cooldown enabled (see `.github/dependabot.yml`). 6. **Audit log**: periodically review GitHub audit log for workflow or secret diff --git a/docs/README.md b/docs/README.md index 1a3bfb20..bc37cedc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -83,13 +83,23 @@ docs/ └── make.bat # Build commands (Windows) ``` -## ReadTheDocs Integration - -This documentation is configured for [ReadTheDocs](https://readthedocs.org/) hosting: - -- Configuration: `.readthedocs.yaml` in the project root -- Dependencies: Managed through `pyproject.toml` optional docs dependencies -- Build process: Automated on ReadTheDocs +## Hosting + +Canonical documentation is [https://openseries.readthedocs.io/](https://openseries.readthedocs.io/) +(the URL published on PyPI and conda-forge). The same Sphinx site is also +deployed to [GitHub Pages](https://captorab.github.io/openseries/) by +`.github/workflows/docs.yml` (Homepage in package metadata). + +- Pull requests that touch documentation sources or library code build docs + in GitHub Actions (warnings as errors) without deploying Pages +- Pushes to `master` and manual `workflow_dispatch` (including after a PyPI + release) build and deploy Pages +- Read the Docs builds `latest` from `master` using `.readthedocs.yaml`; + disable **Build pull requests** in the RTD dashboard so PRs are not also + built there +- Dependencies come from the `docs` extra in `pyproject.toml` / `uv.lock`. + Read the Docs installs that extra via `.readthedocs.yaml` + (`pip install .[docs]`) ## Writing Documentation @@ -196,7 +206,6 @@ When contributing to documentation: **Missing modules:** - Install missing dependencies: `uv sync --locked --extra docs` -- For ReadTheDocs builds, `docs/requirements.txt` must match the `docs` extra in `pyproject.toml` **Broken links:** diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo deleted file mode 100644 index 226d20d2..00000000 --- a/docs/build/html/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: c7ef2e0edce5614b95a3d168aacf8abb -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/.doctrees/api/datefixer.doctree b/docs/build/html/.doctrees/api/datefixer.doctree deleted file mode 100644 index c15443d0..00000000 Binary files a/docs/build/html/.doctrees/api/datefixer.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/frame.doctree b/docs/build/html/.doctrees/api/frame.doctree deleted file mode 100644 index 4eb04237..00000000 Binary files a/docs/build/html/.doctrees/api/frame.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.OpenFrame.doctree b/docs/build/html/.doctrees/api/generated/openseries.OpenFrame.doctree deleted file mode 100644 index d62ce082..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.OpenFrame.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.OpenTimeSeries.doctree b/docs/build/html/.doctrees/api/generated/openseries.OpenTimeSeries.doctree deleted file mode 100644 index 686008b4..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.OpenTimeSeries.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.ReturnSimulation.doctree b/docs/build/html/.doctrees/api/generated/openseries.ReturnSimulation.doctree deleted file mode 100644 index d50751f3..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.ReturnSimulation.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.ValueType.doctree b/docs/build/html/.doctrees/api/generated/openseries.ValueType.doctree deleted file mode 100644 index 96bbcd0f..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.ValueType.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.constrain_optimized_portfolios.doctree b/docs/build/html/.doctrees/api/generated/openseries.constrain_optimized_portfolios.doctree deleted file mode 100644 index 8502ec06..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.constrain_optimized_portfolios.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.date_fix.doctree b/docs/build/html/.doctrees/api/generated/openseries.date_fix.doctree deleted file mode 100644 index a78cc23e..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.date_fix.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.date_offset_foll.doctree b/docs/build/html/.doctrees/api/generated/openseries.date_offset_foll.doctree deleted file mode 100644 index 769805cc..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.date_offset_foll.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.efficient_frontier.doctree b/docs/build/html/.doctrees/api/generated/openseries.efficient_frontier.doctree deleted file mode 100644 index 116f1407..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.efficient_frontier.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.generate_calendar_date_range.doctree b/docs/build/html/.doctrees/api/generated/openseries.generate_calendar_date_range.doctree deleted file mode 100644 index 46576a04..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.generate_calendar_date_range.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.get_previous_business_day_before_today.doctree b/docs/build/html/.doctrees/api/generated/openseries.get_previous_business_day_before_today.doctree deleted file mode 100644 index ce0091bd..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.get_previous_business_day_before_today.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.holiday_calendar.doctree b/docs/build/html/.doctrees/api/generated/openseries.holiday_calendar.doctree deleted file mode 100644 index 1746d627..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.holiday_calendar.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.load_plotly_dict.doctree b/docs/build/html/.doctrees/api/generated/openseries.load_plotly_dict.doctree deleted file mode 100644 index 365bd04d..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.load_plotly_dict.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.offset_business_days.doctree b/docs/build/html/.doctrees/api/generated/openseries.offset_business_days.doctree deleted file mode 100644 index b960059e..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.offset_business_days.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.prepare_plot_data.doctree b/docs/build/html/.doctrees/api/generated/openseries.prepare_plot_data.doctree deleted file mode 100644 index 5fcb044d..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.prepare_plot_data.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.report_html.doctree b/docs/build/html/.doctrees/api/generated/openseries.report_html.doctree deleted file mode 100644 index 7b736cfd..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.report_html.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.sharpeplot.doctree b/docs/build/html/.doctrees/api/generated/openseries.sharpeplot.doctree deleted file mode 100644 index 4cbe6eb6..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.sharpeplot.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.simulate_portfolios.doctree b/docs/build/html/.doctrees/api/generated/openseries.simulate_portfolios.doctree deleted file mode 100644 index 8d89a578..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.simulate_portfolios.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/generated/openseries.timeseries_chain.doctree b/docs/build/html/.doctrees/api/generated/openseries.timeseries_chain.doctree deleted file mode 100644 index 0391d743..00000000 Binary files a/docs/build/html/.doctrees/api/generated/openseries.timeseries_chain.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/openseries.doctree b/docs/build/html/.doctrees/api/openseries.doctree deleted file mode 100644 index 6de3d26f..00000000 Binary files a/docs/build/html/.doctrees/api/openseries.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/portfoliotools.doctree b/docs/build/html/.doctrees/api/portfoliotools.doctree deleted file mode 100644 index 87044831..00000000 Binary files a/docs/build/html/.doctrees/api/portfoliotools.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/report.doctree b/docs/build/html/.doctrees/api/report.doctree deleted file mode 100644 index 131d6bf6..00000000 Binary files a/docs/build/html/.doctrees/api/report.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/series.doctree b/docs/build/html/.doctrees/api/series.doctree deleted file mode 100644 index 5f8937c6..00000000 Binary files a/docs/build/html/.doctrees/api/series.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/simulation.doctree b/docs/build/html/.doctrees/api/simulation.doctree deleted file mode 100644 index 6c135d8d..00000000 Binary files a/docs/build/html/.doctrees/api/simulation.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/api/types.doctree b/docs/build/html/.doctrees/api/types.doctree deleted file mode 100644 index 5a017b19..00000000 Binary files a/docs/build/html/.doctrees/api/types.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/development/changelog.doctree b/docs/build/html/.doctrees/development/changelog.doctree deleted file mode 100644 index c85b625f..00000000 Binary files a/docs/build/html/.doctrees/development/changelog.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/development/contributing.doctree b/docs/build/html/.doctrees/development/contributing.doctree deleted file mode 100644 index 5f417bf5..00000000 Binary files a/docs/build/html/.doctrees/development/contributing.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/environment.pickle b/docs/build/html/.doctrees/environment.pickle deleted file mode 100644 index 9fe326b1..00000000 Binary files a/docs/build/html/.doctrees/environment.pickle and /dev/null differ diff --git a/docs/build/html/.doctrees/examples/custom_reports.doctree b/docs/build/html/.doctrees/examples/custom_reports.doctree deleted file mode 100644 index 34906b7e..00000000 Binary files a/docs/build/html/.doctrees/examples/custom_reports.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/examples/multi_asset.doctree b/docs/build/html/.doctrees/examples/multi_asset.doctree deleted file mode 100644 index 776c5443..00000000 Binary files a/docs/build/html/.doctrees/examples/multi_asset.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/examples/portfolio_optimization.doctree b/docs/build/html/.doctrees/examples/portfolio_optimization.doctree deleted file mode 100644 index 0dfcda1a..00000000 Binary files a/docs/build/html/.doctrees/examples/portfolio_optimization.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/examples/single_asset.doctree b/docs/build/html/.doctrees/examples/single_asset.doctree deleted file mode 100644 index 3c862fe4..00000000 Binary files a/docs/build/html/.doctrees/examples/single_asset.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/index.doctree b/docs/build/html/.doctrees/index.doctree deleted file mode 100644 index 9c16b540..00000000 Binary files a/docs/build/html/.doctrees/index.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/tutorials/advanced_features.doctree b/docs/build/html/.doctrees/tutorials/advanced_features.doctree deleted file mode 100644 index bb2624fb..00000000 Binary files a/docs/build/html/.doctrees/tutorials/advanced_features.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/tutorials/basic_analysis.doctree b/docs/build/html/.doctrees/tutorials/basic_analysis.doctree deleted file mode 100644 index 5778be24..00000000 Binary files a/docs/build/html/.doctrees/tutorials/basic_analysis.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/tutorials/portfolio_analysis.doctree b/docs/build/html/.doctrees/tutorials/portfolio_analysis.doctree deleted file mode 100644 index 9c5bc063..00000000 Binary files a/docs/build/html/.doctrees/tutorials/portfolio_analysis.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/tutorials/risk_management.doctree b/docs/build/html/.doctrees/tutorials/risk_management.doctree deleted file mode 100644 index d2d2cdbf..00000000 Binary files a/docs/build/html/.doctrees/tutorials/risk_management.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/user_guide/core_concepts.doctree b/docs/build/html/.doctrees/user_guide/core_concepts.doctree deleted file mode 100644 index 68079153..00000000 Binary files a/docs/build/html/.doctrees/user_guide/core_concepts.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/user_guide/data_handling.doctree b/docs/build/html/.doctrees/user_guide/data_handling.doctree deleted file mode 100644 index bf8333c9..00000000 Binary files a/docs/build/html/.doctrees/user_guide/data_handling.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/user_guide/installation.doctree b/docs/build/html/.doctrees/user_guide/installation.doctree deleted file mode 100644 index 0208f8d1..00000000 Binary files a/docs/build/html/.doctrees/user_guide/installation.doctree and /dev/null differ diff --git a/docs/build/html/.doctrees/user_guide/quickstart.doctree b/docs/build/html/.doctrees/user_guide/quickstart.doctree deleted file mode 100644 index 7358a818..00000000 Binary files a/docs/build/html/.doctrees/user_guide/quickstart.doctree and /dev/null differ diff --git a/docs/build/html/.nojekyll b/docs/build/html/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/build/html/_modules/index.html b/docs/build/html/_modules/index.html deleted file mode 100644 index b530b689..00000000 --- a/docs/build/html/_modules/index.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - Overview: module code — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/datefixer.html b/docs/build/html/_modules/openseries/datefixer.html deleted file mode 100644 index 6bcd3a54..00000000 --- a/docs/build/html/_modules/openseries/datefixer.html +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - - - openseries.datefixer — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.datefixer

-"""Date related utilities."""
-
-from __future__ import annotations
-
-import datetime as dt
-from typing import TYPE_CHECKING, cast
-
-import exchange_calendars as exchcal  # type: ignore[import-untyped]
-from dateutil.relativedelta import relativedelta
-from holidays import (
-    country_holidays,
-    list_supported_countries,
-)
-from numpy import array, busdaycalendar, datetime64, is_busday, where
-from pandas import (
-    DataFrame,
-    DatetimeIndex,
-    Index,
-    Timestamp,
-    concat,
-    date_range,
-)
-from pandas.tseries.offsets import CustomBusinessDay
-
-from .owntypes import (
-    BothStartAndEndError,
-    CountriesNotStringNorListStrError,
-    MarketsNotStringNorListStrError,
-    TradingDaysNotAboveZeroError,
-)
-
-if TYPE_CHECKING:
-    from .owntypes import (  # pragma: no cover
-        CountriesType,
-        DateType,
-        LiteralBizDayFreq,
-    )
-
-__all__ = [
-    "date_fix",
-    "date_offset_foll",
-    "generate_calendar_date_range",
-    "get_previous_business_day_before_today",
-    "holiday_calendar",
-    "offset_business_days",
-]
-
-
-def market_holidays(
-    startyear: int,
-    endyear: int,
-    markets: str | list[str],
-) -> list[str]:
-    """Return a list of holiday dates mapping to list of markets closed.
-
-    Args:
-        startyear: First year (inclusive) to consider.
-        endyear: Last year (inclusive) to consider.
-        markets: String or list of market codes supported by exchange_calendars.
-
-    Returns:
-        List of holiday dates.
-
-    Raises:
-        MarketsNotStringNorListStrError: If any market code is not supported by
-            ``exchange_calendars`` or the input is not a string or list of strings.
-    """
-    market_list = [markets] if isinstance(markets, str) else list(markets)
-
-    supported = exchcal.get_calendar_names()
-
-    if not all(m in supported for m in market_list):
-        msg = (
-            "Argument markets must be a string market code or a list of market "
-            "codes supported by exchange_calendars."
-        )
-        raise MarketsNotStringNorListStrError(msg)
-
-    holidays: list[str] = []
-    for m in market_list:
-        cal = exchcal.get_calendar(m)
-        cal_hols = cal.regular_holidays.holidays()
-        my_hols: list[str] = [
-            date.date().strftime("%Y-%m-%d")
-            for date in cal_hols
-            if (startyear <= date.date().year <= endyear)
-        ]
-        holidays.extend(my_hols)
-
-    return list(set(holidays))
-
-
-
-[docs] -def holiday_calendar( - startyear: int, - endyear: int, - countries: CountriesType = "SE", - markets: list[str] | str | None = None, - custom_holidays: list[str] | str | None = None, -) -> busdaycalendar: - """Generate a business calendar. - - Args: - startyear: First year in date range generated. - endyear: Last year in date range generated. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - custom_holidays: Argument where missing holidays can be added. - - Returns: - Generate a business calendar. - - Raises: - CountriesNotStringNorListStrError: If ``countries`` is not a supported - ISO 3166-1 alpha-2 string or a list of such strings. - """ - startyear -= 1 - endyear += 1 - if startyear == endyear: - endyear += 1 - years = list(range(startyear, endyear)) - - if isinstance(countries, str) and countries in list_supported_countries(): - staging = country_holidays(country=countries, years=years) - hols = list(staging.keys()) - elif isinstance(countries, (list, set)) and all( - country in list_supported_countries() for country in countries - ): - country: str - countryholidays: list[dt.date | str] = [] - for country in countries: - staging = country_holidays(country=country, years=years) - countryholidays += list(staging) - hols = cast("list[dt.date]", list(countryholidays)) - else: - msg = ( - "Argument countries must be a string country code or " - "a list of string country codes according to ISO 3166-1 alpha-2." - ) - raise CountriesNotStringNorListStrError(msg) - - if markets: - market_hols = market_holidays( - startyear=startyear, - endyear=endyear, - markets=markets, - ) - dt_mkt_hols = [date_fix(fixerdate=ddate) for ddate in market_hols] - hols.extend(dt_mkt_hols) - - if custom_holidays: - custom_list = ( - [custom_holidays] - if isinstance(custom_holidays, str) - else list(custom_holidays) - ) - hols.extend([date_fix(fixerdate=ddate) for ddate in custom_list]) - - return busdaycalendar(holidays=array(sorted(set(hols)), dtype="datetime64[D]"))
- - - -
-[docs] -def date_fix( - fixerdate: DateType, -) -> dt.date: - """Parse different date formats into datetime.date. - - Args: - fixerdate: The data item to parse. - - Returns: - Parsed date. - - Raises: - TypeError: If the provided ``fixerdate`` type is not supported. - """ - msg = f"Unknown date format {fixerdate!s} of type {type(fixerdate)!s} encountered" - if isinstance(fixerdate, Timestamp | dt.datetime): - return fixerdate.date() - if isinstance(fixerdate, dt.date): - return fixerdate - if isinstance(fixerdate, datetime64): - return ( - dt.datetime.strptime(str(fixerdate)[:10], "%Y-%m-%d").astimezone().date() - ) - if isinstance(fixerdate, str): - return dt.datetime.strptime(fixerdate, "%Y-%m-%d").astimezone().date() - raise TypeError(msg)
- - - -
-[docs] -def date_offset_foll( - raw_date: DateType, - months_offset: int = 12, - countries: CountriesType = "SE", - markets: list[str] | str | None = None, - custom_holidays: list[str] | str | None = None, - *, - adjust: bool = False, - following: bool = True, -) -> dt.date: - """Offset dates according to a given calendar. - - Args: - raw_date: The date to offset from. - months_offset: Number of months as integer. Defaults to 12. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - custom_holidays: Argument where missing holidays can be added. - adjust: Determines if offset should adjust for business days. - Defaults to False. - following: Determines if days should be offset forward (following) or backward. - Defaults to True. - - Returns: - Offset date. - """ - raw_date = date_fix(raw_date) - month_delta = relativedelta(months=months_offset) - - day_delta = relativedelta(days=1) if following else relativedelta(days=-1) - - new_date = raw_date + month_delta - - if adjust: - startyear = min([raw_date.year, new_date.year]) - endyear = max([raw_date.year, new_date.year]) - calendar = holiday_calendar( - startyear=startyear, - endyear=endyear, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - ) - while not is_busday(dates=new_date, busdaycal=calendar): - new_date += day_delta - - return new_date
- - - -
-[docs] -def get_previous_business_day_before_today( - today: dt.date | None = None, - countries: CountriesType = "SE", - markets: list[str] | str | None = None, - custom_holidays: list[str] | str | None = None, -) -> dt.date: - """Bump date backwards to find the previous business day. - - Args: - today: Manual input of the day from where the previous business day is found. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - custom_holidays: Argument where missing holidays can be added. - - Returns: - The previous business day. - """ - if today is None: - today = dt.datetime.now().astimezone().date() - - return date_offset_foll( - raw_date=today - dt.timedelta(days=1), - months_offset=0, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - adjust=True, - following=False, - )
- - - -
-[docs] -def offset_business_days( - ddate: dt.date, - days: int, - countries: CountriesType = "SE", - markets: list[str] | str | None = None, - custom_holidays: list[str] | str | None = None, -) -> dt.date: - """Bump date by business days. - - It first adjusts to a valid business day and then bumps with given - number of business days from there. - - Args: - ddate: A starting date that does not have to be a business day. - days: The number of business days to offset from the business day - that is given. - If days is set as anything other than an integer its value is set to zero. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - custom_holidays: Argument where missing holidays can be added. - - Returns: - The new offset business day. - """ - try: - days = int(days) - except TypeError: - days = 0 - - if days <= 0: - scaledtoyeardays = int((days * 372 / 250) // 1) - 365 - ndate = ddate + dt.timedelta(days=scaledtoyeardays) - calendar = holiday_calendar( - startyear=ndate.year, - endyear=ddate.year, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - ) - local_bdays: list[dt.date] = [ - bday.date() - for bday in date_range( - periods=abs(scaledtoyeardays), - end=ddate, - freq=CustomBusinessDay(calendar=calendar), - ) - ] - else: - scaledtoyeardays = int((days * 372 / 250) // 1) + 365 - ndate = ddate + dt.timedelta(days=scaledtoyeardays) - calendar = holiday_calendar( - startyear=ddate.year, - endyear=ndate.year, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - ) - local_bdays = [ - bday.date() - for bday in date_range( - start=ddate, - periods=scaledtoyeardays, - freq=CustomBusinessDay(calendar=calendar), - ) - ] - - while ddate not in local_bdays: - if days <= 0: - ddate -= dt.timedelta(days=1) - else: - ddate += dt.timedelta(days=1) - - idx = where(array(local_bdays) == ddate)[0] - - return cast("dt.date", local_bdays[idx[0] + days])
- - - -
-[docs] -def generate_calendar_date_range( - trading_days: int, - start: dt.date | None = None, - end: dt.date | None = None, - countries: CountriesType = "SE", - markets: list[str] | str | None = None, - custom_holidays: list[str] | str | None = None, -) -> list[dt.date]: - """Generate a list of business day calendar dates. - - Args: - trading_days: Number of days to generate. Must be greater than zero. - start: Date when the range starts. - end: Date when the range ends. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - custom_holidays: Argument where missing holidays can be added. - - Returns: - List of business day calendar dates. - """ - if trading_days < 1: - msg = "Argument trading_days must be greater than zero." - raise TradingDaysNotAboveZeroError(msg) - - if start and not end: - adjusted_start = date_offset_foll( - raw_date=start, - months_offset=0, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - adjust=True, - following=True, - ) - tmp_range = date_range( - start=adjusted_start, - periods=trading_days * 365 // 252, - freq="D", - ) - calendar = holiday_calendar( - startyear=adjusted_start.year, - endyear=date_fix(tmp_range.tolist()[-1]).year, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - ) - return [ - d.date() - for d in date_range( - start=adjusted_start, - periods=trading_days, - freq=CustomBusinessDay(calendar=calendar), - ) - ] - - if end and not start: - adjusted_end = date_offset_foll( - raw_date=end, - months_offset=0, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - adjust=True, - following=False, - ) - tmp_range = date_range( - end=adjusted_end, - periods=trading_days * 365 // 252, - freq="D", - ) - calendar = holiday_calendar( - startyear=date_fix(tmp_range.tolist()[0]).year, - endyear=adjusted_end.year, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - ) - return [ - d.date() - for d in date_range( - end=adjusted_end, - periods=trading_days, - freq=CustomBusinessDay(calendar=calendar), - ) - ] - - msg = ( - "Provide exactly one of start or end date. " - "Date range is inferred from number of trading days." - ) - raise BothStartAndEndError(msg)
- - - -def _do_resample_to_business_period_ends( - data: DataFrame, - freq: LiteralBizDayFreq, - countries: CountriesType, - markets: list[str] | str | None = None, - custom_holidays: list[str] | str | None = None, -) -> DatetimeIndex: - """Resample timeseries frequency to business calendar month end dates. - - Stubs left in place. Stubs will be aligned to the shortest stub. - - Args: - data: The timeseries data. - freq: The date offset string that sets the resampled frequency. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - markets: (List of) markets code(s) supported by exchange_calendars. - custom_holidays: Argument where missing holidays can be added. - - Returns: - A date range aligned to business period ends. - - """ - copydata = data.copy() - copydata.index = DatetimeIndex(copydata.index) - copydata = copydata.resample(rule=freq).last() - copydata = copydata.drop(index=copydata.index[-1]) - copydata.index = Index(d.date() for d in DatetimeIndex(copydata.index)) - - copydata = concat([data.head(n=1), copydata, data.tail(n=1)]).sort_index() - - dates = DatetimeIndex( - [copydata.index[0]] - + [ - date_offset_foll( - raw_date=dt.date(d.year, d.month, 1) - + relativedelta(months=1) - - dt.timedelta(days=1), - months_offset=0, - countries=countries, - markets=markets, - custom_holidays=custom_holidays, - adjust=True, - following=False, - ) - for d in copydata.index[1:-1] - ] - + [copydata.index[-1]], - ) - return DatetimeIndex(dates.drop_duplicates()) -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/frame.html b/docs/build/html/_modules/openseries/frame.html deleted file mode 100644 index fc9a7955..00000000 --- a/docs/build/html/_modules/openseries/frame.html +++ /dev/null @@ -1,2273 +0,0 @@ - - - - - - - - openseries.frame — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.frame

-"""The OpenFrame class."""
-
-from __future__ import annotations
-
-from copy import deepcopy
-from functools import reduce
-from logging import getLogger
-from typing import TYPE_CHECKING, Any, Self, cast
-
-from numpy import (
-    array,
-    asarray,
-    bool_,
-    concatenate,
-    corrcoef,
-    cov,
-    diff,
-    divide,
-    float64,
-    isinf,
-    isnan,
-    linalg,
-    log,
-    nan,
-    sqrt,
-    std,
-)
-from pandas import (
-    DataFrame,
-    DatetimeIndex,
-    Index,
-    MultiIndex,
-    Series,
-    concat,
-    merge,
-)
-
-if TYPE_CHECKING:  # pragma: no cover
-    import datetime as dt
-
-    from numpy.typing import NDArray
-    from pandas import Series as _Series
-    from pandas import Timestamp
-
-    SeriesFloat = _Series[float]
-else:
-    SeriesFloat = Series
-
-from pydantic import field_validator
-from sklearn.linear_model import LinearRegression  # type: ignore[import-untyped]
-
-from ._common_model import _calculate_time_factor, _CommonModel, _get_base_column_data
-from .datefixer import _do_resample_to_business_period_ends
-from .owntypes import (
-    DaysInYearType,
-    LabelsNotUniqueError,
-    LiteralBizDayFreq,
-    LiteralCaptureRatio,
-    LiteralFrameProps,
-    LiteralHowMerge,
-    LiteralPandasReindexMethod,
-    LiteralPortfolioWeightings,
-    LiteralTrunc,
-    MaxDiversificationNaNError,
-    MaxDiversificationNegativeWeightsError,
-    MergingResultedInEmptyError,
-    MixedValuetypesError,
-    MultipleCurrenciesError,
-    NoWeightsError,
-    OpenFramePropertiesList,
-    PortfolioItemsNotWithinFrameError,
-    RatioInputError,
-    ResampleDataLossError,
-    ValueType,
-    WeightsNotProvidedError,
-)
-from .series import OpenTimeSeries
-
-logger = getLogger(__name__)
-
-__all__ = ["OpenFrame"]
-
-
-
-[docs] -class OpenFrame(_CommonModel[SeriesFloat]): - """OpenFrame objects hold OpenTimeSeries in the list constituents. - - The intended use is to allow comparisons across these timeseries. - - Args: - constituents: List of objects of Class OpenTimeSeries. - weights: List of weights in float format. Optional. - """ - - @field_validator("constituents") - @classmethod - def _check_labels_unique( - cls: type[OpenFrame], - tseries: list[OpenTimeSeries], - ) -> list[OpenTimeSeries]: - """Pydantic validator ensuring that OpenFrame labels are unique.""" - labls = [x.label for x in tseries] - if len(set(labls)) != len(labls): - msg = "TimeSeries names/labels must be unique" - raise LabelsNotUniqueError(msg) - return tseries - -
-[docs] - def __init__( - self: Self, - constituents: list[OpenTimeSeries], - weights: list[float] | None = None, - ) -> None: - """OpenFrame objects hold OpenTimeSeries in the list constituents. - - The intended use is to allow comparisons across these timeseries. - - Args: - constituents: List of objects of Class OpenTimeSeries. - weights: List of weights in float format. Optional. - """ - copied_constituents = [ts.from_deepcopy() for ts in constituents] - - super().__init__( - constituents=copied_constituents, - weights=weights, - ) - self._set_tsdf()
- - - def _set_tsdf(self: Self) -> None: - """Set the tsdf DataFrame.""" - if self.constituents is not None and len(self.constituents) != 0: - if len(self.constituents) == 1: - self.tsdf = self.constituents[0].tsdf.copy() - else: - self.tsdf = concat( - [x.tsdf for x in self.constituents], axis="columns", sort=True - ) - else: - logger.warning("OpenFrame() was passed an empty list.") - - def _coerce_result( - self: Self, - result: Series[float], - name: str, - ) -> SeriesFloat: - return Series( - data=result, - index=self.tsdf.columns, - name=name, - dtype="float64", - ) - -
-[docs] - def from_deepcopy(self: Self) -> Self: - """Create copy of the OpenFrame object. - - Returns: - An OpenFrame object. - """ - return deepcopy(self)
- - -
-[docs] - def merge_series( - self: Self, - how: LiteralHowMerge = "outer", - ) -> Self: - """Merge index of Pandas Dataframes of the constituent OpenTimeSeries. - - Args: - how: The Pandas merge method. Defaults to "outer". - - Returns: - An OpenFrame object. - """ - lvl_zero = list(self.columns_lvl_zero) - self.tsdf = reduce( - lambda left, right: merge( - left=left, - right=right, - how=how, - left_index=True, - right_index=True, - ), - [x.tsdf for x in self.constituents], - ) - - mapper = dict(zip(self.columns_lvl_zero, lvl_zero, strict=True)) - self.tsdf = self.tsdf.rename(columns=mapper, level=0) - - if self.tsdf.empty: - msg = ( - "Merging OpenTimeSeries DataFrames with " - f"argument how={how} produced an empty DataFrame." - ) - raise MergingResultedInEmptyError(msg) - - if how == "inner": - for xerie in self.constituents: - xerie.tsdf = xerie.tsdf.loc[self.tsdf.index] - return self
- - -
-[docs] - def all_properties( - self: Self, - properties: list[LiteralFrameProps] | None = None, - ) -> DataFrame: - """Calculate chosen timeseries properties. - - Args: - properties: The properties to calculate. Defaults to calculating all - available. Optional. - - Returns: - Properties of the constituent OpenTimeSeries. - """ - if properties: - props = OpenFramePropertiesList(*properties) - prop_list = [getattr(self, x) for x in props] - else: - prop_list = [ - getattr(self, x) for x in OpenFramePropertiesList.allowed_strings - ] - return cast("DataFrame", concat(prop_list, axis="columns").T)
- - - @property - def lengths_of_items(self: Self) -> Series[int]: - """Number of observations of all constituents. - - Returns: - Number of observations of all constituents. - """ - return Series( - data=[self.tsdf[col].count() for col in self.tsdf.columns], - index=self.tsdf.columns, - name="observations", - ).astype(int) - - @property - def item_count(self: Self) -> int: - """Number of constituents. - - Returns: - Number of constituents. - """ - return len(self.constituents) - - @property - def columns_lvl_zero(self: Self) -> list[str]: - """Level 0 values of the MultiIndex columns in the .tsdf DataFrame. - - Returns: - Level 0 values of the MultiIndex columns in the .tsdf DataFrame. - """ - return list(self.tsdf.columns.get_level_values(0)) - - @property - def columns_lvl_one(self: Self) -> list[ValueType]: - """Level 1 values of the MultiIndex columns in the .tsdf DataFrame. - - Returns: - Level 1 values of the MultiIndex columns in the .tsdf DataFrame. - """ - return list(self.tsdf.columns.get_level_values(1)) - - @property - def _value_types(self: Self) -> list[bool]: - """Cached value type checks for efficiency. - - Returns: - List of booleans indicating if each column is ValueType.RTRN. - """ - return [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)] - - @property - def first_indices(self: Self) -> Series[dt.date]: - """The first dates in the timeseries of all constituents. - - Returns: - The first dates in the timeseries of all constituents. - """ - return Series( - data=[i.first_idx for i in self.constituents], - index=self.tsdf.columns, - name="first indices", - dtype="datetime64[ns]", - ).dt.date - - @property - def last_indices(self: Self) -> Series[dt.date]: - """The last dates in the timeseries of all constituents. - - Returns: - The last dates in the timeseries of all constituents. - """ - return Series( - data=[i.last_idx for i in self.constituents], - index=self.tsdf.columns, - name="last indices", - dtype="datetime64[ns]", - ).dt.date - - @property - def span_of_days_all(self: Self) -> Series[int]: - """Number of days from the first date to the last for all items in the frame. - - Returns: - Number of days from the first date to the last for all - items in the frame. - """ - return Series( - data=[c.span_of_days for c in self.constituents], - index=self.tsdf.columns, - name="span of days", - ).astype(int) - -
-[docs] - def value_to_ret(self: Self) -> Self: - """Convert series of values into series of returns. - - Returns: - The returns of the values in the series. - """ - returns = self.tsdf.ffill().pct_change() - returns.iloc[0] = 0 - new_labels: list[ValueType] = [ValueType.RTRN] * self.item_count - arrays = cast( - "Any", - [ - self.tsdf.columns.get_level_values(0), - new_labels, - ], - ) - returns.columns = MultiIndex.from_arrays(arrays) - self.tsdf = returns.copy() - return self
- - -
-[docs] - def value_to_diff(self: Self, periods: int = 1) -> Self: - """Convert series of values to series of their period differences. - - Args: - periods: The number of periods between observations over which - difference is calculated. Defaults to 1. - - Returns: - An OpenFrame object. - """ - self.tsdf = self.tsdf.diff(periods=periods) - self.tsdf.iloc[0] = 0 - new_labels: list[ValueType] = [ValueType.RTRN] * self.item_count - arrays = cast( - "Any", - [ - self.tsdf.columns.get_level_values(0), - new_labels, - ], - ) - self.tsdf.columns = MultiIndex.from_arrays(arrays) - return self
- - -
-[docs] - def to_cumret(self: Self) -> Self: - """Convert series of returns into cumulative series of values. - - Returns: - An OpenFrame object. - """ - vtypes = self._value_types - if not any(vtypes): - returns = self.tsdf.ffill().pct_change() - returns.iloc[0] = 0 - elif all(vtypes): - returns = self.tsdf.copy() - returns.iloc[0] = 0 - else: - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - returns = returns.add(1.0) - self.tsdf = returns.cumprod(axis=0) / returns.iloc[0] - - new_labels: list[ValueType] = [ValueType.PRICE] * self.item_count - arrays = cast( - "Any", - [ - self.tsdf.columns.get_level_values(0), - new_labels, - ], - ) - self.tsdf.columns = MultiIndex.from_arrays(arrays) - return self
- - -
-[docs] - def resample( - self: Self, - freq: LiteralBizDayFreq | str = "BME", - ) -> Self: - """Resample the timeseries frequency. - - Args: - freq: The date offset string that sets the resampled frequency. - Defaults to "BME". - - Returns: - An OpenFrame object. - """ - vtypes = self._value_types - if not any(vtypes): - value_type = ValueType.PRICE - elif all(vtypes): - value_type = ValueType.RTRN - else: - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - self.tsdf.index = DatetimeIndex(self.tsdf.index) - if value_type == ValueType.PRICE: - self.tsdf = self.tsdf.resample(freq).last() - else: - self.tsdf = self.tsdf.resample(freq).sum() - self.tsdf.index = Index(DatetimeIndex(self.tsdf.index).date) - for xerie in self.constituents: - xerie.tsdf.index = DatetimeIndex(xerie.tsdf.index) - if value_type == ValueType.PRICE: - xerie.tsdf = xerie.tsdf.resample(freq).last() - else: - xerie.tsdf = xerie.tsdf.resample(freq).sum() - xerie.tsdf.index = Index(DatetimeIndex(xerie.tsdf.index).date) - - return self
- - -
-[docs] - def resample_to_business_period_ends( - self: Self, - freq: LiteralBizDayFreq = "BME", - method: LiteralPandasReindexMethod = "nearest", - ) -> Self: - """Resamples timeseries frequency to the business calendar month end dates. - - Stubs left in place. Stubs will be aligned to the shortest stub. - - Args: - freq: The date offset string that sets the resampled frequency. - Defaults to "BME". - method: Controls the method used to align values across columns. - Defaults to nearest. - - Returns: - An OpenFrame object. - """ - vtypes = self._value_types - if any(vtypes): - msg = ( - "Do not run resample_to_business_period_ends on return series. " - "The operation will pick the last data point in the sparser series. " - "It will not sum returns and therefore data will be lost." - ) - raise ResampleDataLossError(msg) - - for xerie in self.constituents: - dates = _do_resample_to_business_period_ends( - data=xerie.tsdf, - freq=freq, - countries=xerie.countries, - markets=xerie.markets, - ) - xerie.tsdf = xerie.tsdf.reindex( - [deyt.date() for deyt in dates], - method=method, - ) - - arrays = [ - self.tsdf.columns.get_level_values(0), - self.tsdf.columns.get_level_values(1), - ] - - self._set_tsdf() - - self.tsdf.columns = MultiIndex.from_arrays(arrays) - - return self
- - -
-[docs] - def ewma_risk( - self: Self, - lmbda: float = 0.94, - day_chunk: int = 11, - dlta_degr_freedms: int = 0, - first_column: int = 0, - second_column: int = 1, - corr_scale: float = 2.0, - months_from_last: int | None = None, - from_date: dt.date | None = None, - to_date: dt.date | None = None, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> DataFrame: - """Exponentially Weighted Moving Average Volatilities and Correlation. - - Exponentially Weighted Moving Average (EWMA) for Volatilities and - Correlation. - - Reference: https://www.investopedia.com/articles/07/ewma.asp. - - Args: - lmbda: Scaling factor to determine weighting. Defaults to 0.94. - day_chunk: Sampling the data which is assumed to be daily. Defaults to 11. - dlta_degr_freedms: Variance bias factor taking the value 0 or 1. - Defaults to 0. - first_column: Column of first timeseries. Defaults to 0. - second_column: Column of second timeseries. Defaults to 1. - corr_scale: Correlation scale factor. Defaults to 2.0. - months_from_last: Number of months offset as positive integer. Overrides - use of from_date and to_date. Optional. - from_date: Specific from date. Optional. - to_date: Specific to date. Optional. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Series volatilities and correlation. - """ - earlier, later = self.calc_range( - months_offset=months_from_last, - from_dt=from_date, - to_dt=to_date, - ) - if periods_in_a_year_fixed is None: - fraction = (later - earlier).days / 365.25 - how_many = ( - self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)] - .count() - .iloc[0] - ) - time_factor = how_many / fraction - else: - time_factor = periods_in_a_year_fixed - - corr_label = ( - cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0] - + "_VS_" - + cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0] - ) - cols = [ - cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0], - cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0], - ] - - data = self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ].copy() - - for rtn in cols: - arr = concatenate([array([nan]), diff(log(data[(rtn, ValueType.PRICE)]))]) - data[rtn, ValueType.RTRN] = arr - - raw_one = [ - data[(cols[0], ValueType.RTRN)] - .iloc[1:day_chunk] - .std(ddof=dlta_degr_freedms) - * sqrt(time_factor), - ] - raw_two = [ - data[(cols[1], ValueType.RTRN)] - .iloc[1:day_chunk] - .std(ddof=dlta_degr_freedms) - * sqrt(time_factor), - ] - rm = data[(cols[0], ValueType.RTRN)].iloc[1:day_chunk] - m: NDArray[float64] = asarray(rm, dtype=float64) - ry = data[(cols[1], ValueType.RTRN)].iloc[1:day_chunk] - y: NDArray[float64] = asarray(ry, dtype=float64) - - raw_cov = [cov(m=m, y=y, ddof=dlta_degr_freedms)[0][1]] - - r1 = data[(cols[0], ValueType.RTRN)] - r2 = data[(cols[1], ValueType.RTRN)] - - alpha = 1.0 - lmbda - - s1 = r1.pow(2) * time_factor - s2 = r2.pow(2) * time_factor - sc = r1 * r2 * time_factor - - s1.iloc[0] = float(raw_one[0] ** 2) - s2.iloc[0] = float(raw_two[0] ** 2) - sc.iloc[0] = float(raw_cov[0]) - - m1 = s1.ewm(alpha=alpha, adjust=False).mean() - m2 = s2.ewm(alpha=alpha, adjust=False).mean() - mc = sc.ewm(alpha=alpha, adjust=False).mean() - - m1v = m1.to_numpy(copy=False) - m2v = m2.to_numpy(copy=False) - mcv = mc.to_numpy(copy=False) - - vol1 = sqrt(m1v) - vol2 = sqrt(m2v) - denom = corr_scale * vol1 * vol2 - - corr = mcv / denom - corr[denom == 0.0] = nan - - return DataFrame( - index=[*cols, corr_label], - columns=data.index, - data=[vol1, vol2, corr], - ).T
- - - @property - def correl_matrix(self: Self) -> DataFrame: - """Correlation matrix. - - This property returns the correlation matrix of the time series - in the frame. - - Returns: - Correlation matrix of the time series in the frame. - """ - corr_matrix = ( - self.tsdf.ffill() - .pct_change() - .corr( - method="pearson", - min_periods=1, - ) - ) - corr_matrix.columns = corr_matrix.columns.get_level_values(0) - corr_matrix.index = corr_matrix.index.get_level_values(0) - corr_matrix.index.name = "Correlation" - return corr_matrix - -
-[docs] - def add_timeseries( - self: Self, - new_series: OpenTimeSeries, - ) -> Self: - """To add an OpenTimeSeries object. - - Args: - new_series: The timeseries to add. - - Returns: - An OpenFrame object. - """ - self.constituents += [new_series] - self.tsdf = concat([self.tsdf, new_series.tsdf], axis="columns", sort=True) - return self
- - -
-[docs] - def delete_timeseries(self: Self, lvl_zero_item: str) -> Self: - """To delete an OpenTimeSeries object. - - Args: - lvl_zero_item: The .tsdf column level 0 value of the timeseries to delete. - - Returns: - An OpenFrame object. - """ - if self.weights: - new_c, new_w = [], [] - for serie, weight in zip(self.constituents, self.weights, strict=True): - if serie.label != lvl_zero_item: - new_c.append(serie) - new_w.append(weight) - self.constituents = new_c - self.weights = new_w - else: - self.constituents = [ - item for item in self.constituents if item.label != lvl_zero_item - ] - self.tsdf = self.tsdf.drop(lvl_zero_item, axis="columns", level=0) - return self
- - -
-[docs] - def trunc_frame( - self: Self, - start_cut: dt.date | None = None, - end_cut: dt.date | None = None, - where: LiteralTrunc = "both", - ) -> Self: - """Truncate DataFrame such that all timeseries have the same time span. - - Args: - start_cut: New first date. Optional. - end_cut: New last date. Optional. - where: Determines where dataframe is truncated also when start_cut - or end_cut is None. Defaults to both. - - Returns: - An OpenFrame object. - """ - if not start_cut and where in ["before", "both"]: - start_cut = self.first_indices.max() - if not end_cut and where in ["after", "both"]: - end_cut = self.last_indices.min() - self.tsdf = self.tsdf.sort_index() - self.tsdf = self.tsdf.truncate(before=start_cut, after=end_cut) - - for xerie in self.constituents: - xerie.tsdf = xerie.tsdf.truncate( - before=start_cut, - after=end_cut, - ) - if len(set(self.first_indices)) != 1: - msg = ( - f"One or more constituents still " - f"not truncated to same start dates.\n" - f"{self.tsdf.head()}" - ) - logger.warning(msg) - if len(set(self.last_indices)) != 1: - msg = ( - f"One or more constituents still " - f"not truncated to same end dates.\n" - f"{self.tsdf.tail()}" - ) - logger.warning(msg) - return self
- - -
-[docs] - def relative( - self: Self, - long_column: int = 0, - short_column: int = 1, - *, - base_zero: bool = True, - ) -> None: - """Calculate cumulative relative return between two series. - - Args: - long_column: Column number of timeseries bought. Defaults to 0. - short_column: Column number of timeseries sold. Defaults to 1. - base_zero: If set to False 1.0 is added to allow for a capital base and - to allow a volatility calculation. Defaults to True. - """ - rel_label = ( - cast("tuple[str, str]", self.tsdf.iloc[:, long_column].name)[0] - + "_over_" - + cast("tuple[str, str]", self.tsdf.iloc[:, short_column].name)[0] - ) - if base_zero: - self.tsdf[rel_label, ValueType.RELRTRN] = ( - self.tsdf.iloc[:, long_column] - self.tsdf.iloc[:, short_column] - ) - else: - self.tsdf[rel_label, ValueType.RELRTRN] = ( - 1.0 + self.tsdf.iloc[:, long_column] - self.tsdf.iloc[:, short_column] - ) - self.constituents += [ - OpenTimeSeries.from_df(self.tsdf.iloc[:, -1]), - ]
- - -
-[docs] - def tracking_error_func( - self: Self, - base_column: tuple[str, ValueType] | int = -1, - months_from_last: int | None = None, - from_date: dt.date | None = None, - to_date: dt.date | None = None, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> Series[float]: - """Tracking Error. - - Calculates Tracking Error which is the standard deviation of the - difference between the fund and its index returns. - - Reference: https://www.investopedia.com/terms/t/trackingerror.asp. - - Args: - base_column: Column of timeseries that is the denominator in the ratio. - Defaults to -1. - months_from_last: Number of months offset as positive integer. Overrides - use of from_date and to_date. Optional. - from_date: Specific from date. Optional. - to_date: Specific to date. Optional. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Tracking Errors. - """ - earlier, later = self.calc_range( - months_offset=months_from_last, - from_dt=from_date, - to_dt=to_date, - ) - - shortdf, short_item, short_label = _get_base_column_data( - self=self, - base_column=base_column, - earlier=earlier, - later=later, - ) - - time_factor = _calculate_time_factor( - data=shortdf, - earlier=earlier, - later=later, - periods_in_a_year_fixed=periods_in_a_year_fixed, - ) - - shortdf_returns = shortdf.ffill().pct_change() - - terrors = [] - for item in self.tsdf: - if item == short_item: - terrors.append(0.0) - else: - longdf = self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ][item] - relative = longdf.ffill().pct_change() - shortdf_returns - vol = float(relative.std() * sqrt(time_factor)) - terrors.append(vol) - - return Series( - data=terrors, - index=self.tsdf.columns, - name=f"Tracking Errors vs {short_label}", - dtype="float64", - )
- - -
-[docs] - def info_ratio_func( - self: Self, - base_column: tuple[str, ValueType] | int = -1, - months_from_last: int | None = None, - from_date: dt.date | None = None, - to_date: dt.date | None = None, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> Series[float]: - """Information Ratio. - - The Information Ratio equals ( fund return less index return ) divided - by the Tracking Error. And the Tracking Error is the standard deviation of - the difference between the fund and its index returns. - The ratio is calculated using the annualized arithmetic mean of returns. - - Args: - base_column: Column of timeseries that is the denominator in the ratio. - Defaults to -1. - months_from_last: Number of months offset as positive integer. Overrides - use of from_date and to_date. Optional. - from_date: Specific from date. Optional. - to_date: Specific to date. Optional. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Information Ratios. - """ - earlier, later = self.calc_range( - months_offset=months_from_last, - from_dt=from_date, - to_dt=to_date, - ) - - shortdf, short_item, short_label = _get_base_column_data( - self=self, - base_column=base_column, - earlier=earlier, - later=later, - ) - - time_factor = _calculate_time_factor( - data=shortdf, - earlier=earlier, - later=later, - periods_in_a_year_fixed=periods_in_a_year_fixed, - ) - - shortdf_returns = shortdf.ffill().pct_change() - - ratios = [] - for item in self.tsdf: - if item == short_item: - ratios.append(0.0) - else: - longdf = self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ][item] - relative = longdf.ffill().pct_change() - shortdf_returns - ret = float(relative.mean() * time_factor) - vol = float(relative.std() * sqrt(time_factor)) - ratios.append(ret / vol) - - return Series( - data=ratios, - index=self.tsdf.columns, - name=f"Info Ratios vs {short_label}", - dtype="float64", - )
- - - def _calculate_cagr_from_returns( - self: Self, - returns_array: NDArray[float64], - mask: NDArray[bool_], - time_factor: float, - ) -> float: - """Calculate CAGR from returns array with mask. - - Args: - returns_array: Returns array. - mask: Boolean mask. - time_factor: Time factor for annualization. - - Returns: - CAGR value. - """ - masked_array = returns_array[mask] + 1.0 - if len(masked_array) == 0: - return 0.0 - exponent = 1 / (len(masked_array) / time_factor) - return float(masked_array.prod() ** exponent - 1) - - def _calculate_capture_ratio_for_item( - self: Self, - ratio: LiteralCaptureRatio, - longdf_returns_np: NDArray[float64], - shortdf_returns_np: NDArray[float64], - up_mask: NDArray[bool_], - down_mask: NDArray[bool_], - time_factor: float, - ) -> float: - """Calculate capture ratio for a single item. - - Args: - ratio: Ratio type to calculate. - longdf_returns_np: Long returns array. - shortdf_returns_np: Short returns array. - up_mask: Up mask. - down_mask: Down mask. - time_factor: Time factor. - - Returns: - Capture ratio value. - - Raises: - RatioInputError: If ratio is invalid. - """ - if ratio == "up": - up_rtrn = self._calculate_cagr_from_returns( - longdf_returns_np, up_mask, time_factor - ) - up_idx_return = self._calculate_cagr_from_returns( - shortdf_returns_np, up_mask, time_factor - ) - if up_idx_return == 0.0: - return 0.0 - return up_rtrn / up_idx_return - - if ratio == "down": - down_return = self._calculate_cagr_from_returns( - longdf_returns_np, down_mask, time_factor - ) - down_idx_return = self._calculate_cagr_from_returns( - shortdf_returns_np, down_mask, time_factor - ) - if down_idx_return == 0.0: - return 0.0 - return down_return / down_idx_return - - if ratio == "both": - up_rtrn = self._calculate_cagr_from_returns( - longdf_returns_np, up_mask, time_factor - ) - up_idx_return = self._calculate_cagr_from_returns( - shortdf_returns_np, up_mask, time_factor - ) - down_return = self._calculate_cagr_from_returns( - longdf_returns_np, down_mask, time_factor - ) - down_idx_return = self._calculate_cagr_from_returns( - shortdf_returns_np, down_mask, time_factor - ) - if up_idx_return == 0.0 or down_idx_return == 0.0: - return 0.0 - return (up_rtrn / up_idx_return) / (down_return / down_idx_return) - - msg = "ratio must be one of 'up', 'down' or 'both'." - raise RatioInputError(msg) - -
-[docs] - def capture_ratio_func( - self: Self, - ratio: LiteralCaptureRatio, - base_column: tuple[str, ValueType] | int = -1, - months_from_last: int | None = None, - from_date: dt.date | None = None, - to_date: dt.date | None = None, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> Series[float]: - """Capture Ratio. - - The Up (Down) Capture Ratio is calculated by dividing the CAGR - of the asset during periods that the benchmark returns are positive (negative) - by the CAGR of the benchmark during the same periods. - CaptureRatio.BOTH is the Up ratio divided by the Down ratio. - Source: 'Capture Ratios: A Popular Method of Measuring Portfolio Performance - in Practice', Don R. Cox and Delbert C. Goff, Journal of Economics and - Finance Education (Vol 2 Winter 2013). - - Reference: https://www.economics-finance.org/jefe/volume12-2/11ArticleCox.pdf. - - Args: - ratio: The ratio to calculate. - base_column: Column of timeseries that is the denominator in the ratio. - Defaults to -1. - months_from_last: Number of months offset as positive integer. Overrides - use of from_date and to_date. Optional. - from_date: Specific from date. Optional. - to_date: Specific to date. Optional. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Capture Ratios. - """ - loss_limit: float = 0.0 - earlier, later = self.calc_range( - months_offset=months_from_last, - from_dt=from_date, - to_dt=to_date, - ) - fraction: float = (later - earlier).days / 365.25 - - shortdf, short_item, short_label = _get_base_column_data( - self=self, - base_column=base_column, - earlier=earlier, - later=later, - ) - - if periods_in_a_year_fixed: - time_factor = float(periods_in_a_year_fixed) - else: - time_factor = shortdf.count() / fraction - - shortdf_returns = shortdf.ffill().pct_change() - shortdf_returns_np = cast("NDArray[float64]", shortdf_returns.to_numpy()) - up_mask = shortdf_returns_np > loss_limit - down_mask = shortdf_returns_np < loss_limit - - ratios = [] - for item in self.tsdf: - if item == short_item: - ratios.append(0.0) - else: - longdf = self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ][item] - longdf_returns = longdf.ffill().pct_change() - longdf_returns_np = cast("NDArray[float64]", longdf_returns.to_numpy()) - ratio_value = self._calculate_capture_ratio_for_item( - ratio=ratio, - longdf_returns_np=longdf_returns_np, - shortdf_returns_np=shortdf_returns_np, - up_mask=up_mask, - down_mask=down_mask, - time_factor=time_factor, - ) - ratios.append(ratio_value) - - ratio_names = { - "up": f"Up Capture Ratios vs {short_label}", - "down": f"Down Capture Ratios vs {short_label}", - "both": f"Up-Down Capture Ratios vs {short_label}", - } - resultname = ratio_names[ratio] - - return Series( - data=ratios, - index=self.tsdf.columns, - name=resultname, - dtype="float64", - )
- - - def _extract_column_value( - self: Self, - column: tuple[str, ValueType] | int, - vtypes: list[bool], - param_name: str = "column", - ) -> Series[float]: - """Extract column value based on value types. - - Args: - column: Column reference. - vtypes: Value types list. - param_name: Parameter name for error messages. - - Returns: - Series value. - - Raises: - TypeError: If column type is invalid. - """ - msg = f"{param_name} should be a tuple[str, ValueType] or an integer." - if isinstance(column, tuple): - if all(vtypes): - return self.tsdf[column] - return self.tsdf[column].ffill().pct_change().iloc[1:] - if isinstance(column, int): - if all(vtypes): - return self.tsdf.iloc[:, column] - return self.tsdf.iloc[:, column].ffill().pct_change().iloc[1:] - raise TypeError(msg) - -
-[docs] - def beta( - self: Self, - asset: tuple[str, ValueType] | int, - market: tuple[str, ValueType] | int, - dlta_degr_freedms: int = 1, - ) -> float: - """Market Beta. - - Calculates Beta as Co-variance of asset & market divided by Variance - of the market. - - Reference: https://www.investopedia.com/terms/b/beta.asp. - - Args: - asset: The column of the asset. - market: The column of the market against which Beta is measured. - dlta_degr_freedms: Variance bias factor taking the value 0 or 1. - Defaults to 1. - - Returns: - Beta as Co-variance of x & y divided by Variance of x. - """ - vtypes = self._value_types - if not (all(vtypes) or not any(vtypes)): - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - y_value = self._extract_column_value(asset, vtypes, param_name="asset") - x_value = self._extract_column_value(market, vtypes, param_name="market") - - covariance = cov(m=y_value, y=x_value, ddof=dlta_degr_freedms) - beta = covariance[0, 1] / covariance[1, 1] - - return float(beta)
- - -
-[docs] - def ord_least_squares_fit( - self: Self, - y_column: tuple[str, ValueType] | int, - x_column: tuple[str, ValueType] | int, - *, - fitted_series: bool = True, - ) -> dict[str, float]: - """Ordinary Least Squares fit. - - Performs a linear regression and adds a new column with a fitted line - using Ordinary Least Squares fit. - - Args: - y_column: The column level values of the dependent variable y. - x_column: The column level values of the exogenous variable x. - fitted_series: If True the fit is added as a new column in the .tsdf - Pandas.DataFrame. Defaults to True. - - Returns: - A dictionary with the coefficient, intercept and rsquared outputs. - """ - msg = "y_column should be a tuple[str, ValueType] or an integer." - if isinstance(y_column, tuple): - y_value = self.tsdf[y_column].to_numpy() - y_label = cast( - "tuple[str, str]", - self.tsdf[y_column].name, - )[0] - elif isinstance(y_column, int): - y_value = self.tsdf.iloc[:, y_column].to_numpy() - y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0] - else: - raise TypeError(msg) - - msg = "x_column should be a tuple[str, ValueType] or an integer." - if isinstance(x_column, tuple): - x_value = self.tsdf[x_column].to_numpy().reshape(-1, 1) - x_label = cast( - "tuple[str, str]", - self.tsdf[x_column].name, - )[0] - elif isinstance(x_column, int): - x_value = self.tsdf.iloc[:, x_column].to_numpy().reshape(-1, 1) - x_label = cast("tuple[str, str]", self.tsdf.iloc[:, x_column].name)[0] - else: - raise TypeError(msg) - - model = LinearRegression(fit_intercept=True) - model.fit(x_value, y_value) - if fitted_series: - self.tsdf[y_label, x_label] = model.predict(x_value) - return { - "coefficient": float(model.coef_[0]), - "intercept": float(model.intercept_), - "rsquared": model.score(x_value, y_value), - }
- - -
-[docs] - def jensen_alpha( - self: Self, - asset: tuple[str, ValueType] | int, - market: tuple[str, ValueType] | int, - riskfree_rate: float = 0.0, - dlta_degr_freedms: int = 1, - ) -> float: - """Jensen's alpha. - - The Jensen's measure, or Jensen's alpha, is a risk-adjusted performance - measure that represents the average return on a portfolio or investment, - above or below that predicted by the capital asset pricing model (CAPM), - given the portfolio's or investment's beta and the average market return. - This metric is also commonly referred to as simply alpha. - - Reference: https://www.investopedia.com/terms/j/jensensmeasure.asp. - - Args: - asset: The column of the asset. - market: The column of the market against which Jensen's alpha is measured. - riskfree_rate: The return of the zero volatility riskfree asset. - Defaults to 0.0. - dlta_degr_freedms: Variance bias factor taking the value 0 or 1. - Defaults to 1. - - Returns: - Jensen's alpha. - """ - vtypes = self._value_types - if not (all(vtypes) or not any(vtypes)): - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - asset_rtn = self._extract_column_value(asset, vtypes, param_name="asset") - market_rtn = self._extract_column_value(market, vtypes, param_name="market") - - asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year) - market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year) - - covariance = cov(m=asset_rtn, y=market_rtn, ddof=dlta_degr_freedms) - beta = covariance[0, 1] / covariance[1, 1] - - return float( - asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate), - )
- - - def _prepare_returns_for_portfolio(self: Self) -> DataFrame: - """Prepare returns DataFrame for portfolio calculation. - - Returns: - Returns DataFrame. - - Raises: - MixedValuetypesError: If series types are mixed. - """ - vtypes = self._value_types - if not any(vtypes): - returns = self.tsdf.ffill().pct_change() - returns.iloc[0] = 0 - return returns - if all(vtypes): - return self.tsdf - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - def _calculate_eq_weights(self: Self) -> list[float]: - """Calculate equal weights. - - Returns: - List of equal weights. - """ - return [1.0 / self.item_count] * self.item_count - - def _calculate_inv_vol_weights(self: Self, returns: DataFrame) -> list[float]: - """Calculate inverse volatility weights. - - Args: - returns: Returns DataFrame. - - Returns: - List of inverse volatility weights. - """ - vol = divide(1.0, std(returns, axis=0, ddof=1)) - vol[isinf(vol)] = nan - return list(divide(vol, vol.sum())) - - def _calculate_max_div_weights(self: Self, returns: DataFrame) -> list[float]: - """Calculate maximum diversification weights. - - Args: - returns: Returns DataFrame. - - Returns: - List of maximum diversification weights. - - Raises: - MaxDiversificationNaNError: If correlation matrix has NaN values. - MaxDiversificationNegativeWeightsError: If weights are negative. - """ - corr_matrix = corrcoef(returns.T) - corr_matrix[isinf(corr_matrix)] = nan - corr_matrix[isnan(corr_matrix)] = nan - - msga = "max_div weight strategy failed: correlation matrix contains NaN values" - if isnan(corr_matrix).any(): - raise MaxDiversificationNaNError(msga) - - try: - inv_corr_sum = linalg.inv(corr_matrix).sum(axis=1) - - msgb = ( - "max_div weight strategy failed: " - "inverse correlation matrix sum contains NaN values" - ) - if isnan(inv_corr_sum).any(): - raise MaxDiversificationNaNError(msgb) - - weights = list(divide(inv_corr_sum, inv_corr_sum.sum())) - - msgc = "max_div weight strategy failed: final weights contain NaN values" - if any(isnan(weight) for weight in weights): # pragma: no cover - raise MaxDiversificationNaNError(msgc) - - msgd = ( - "max_div weight strategy failed: negative weights detected" - f" - weights: {[round(w, 6) for w in weights]}" - ) - if any(weight < 0 for weight in weights): - raise MaxDiversificationNegativeWeightsError(msgd) - - except linalg.LinAlgError as e: - msge = ( - "max_div weight strategy failed: " - f"correlation matrix is singular - {e!s}" - ) - raise MaxDiversificationNaNError(msge) from e - else: - return weights - - def _calculate_min_vol_overweight_weights( - self: Self, - returns: DataFrame, - ) -> list[float]: - """Calculate minimum volatility overweight weights. - - Args: - returns: Returns DataFrame. - - Returns: - List of minimum volatility overweight weights. - """ - vols = std(returns, axis=0, ddof=1) - min_vol_idx = vols.argmin() - min_vol_weight = 0.6 - remaining_weight = 0.4 - weights = [remaining_weight / (self.item_count - 1)] * self.item_count - weights[min_vol_idx] = min_vol_weight - return weights - - def _calculate_weights_from_strategy( - self: Self, - weight_strat: LiteralPortfolioWeightings, - returns: DataFrame, - ) -> list[float]: - """Calculate weights based on strategy. - - Args: - weight_strat: Weight calculation strategy. - returns: Returns DataFrame. - - Returns: - List of weights. - - Raises: - NotImplementedError: If strategy is not implemented. - """ - if weight_strat == "eq_weights": - return self._calculate_eq_weights() - if weight_strat == "inv_vol": - return self._calculate_inv_vol_weights(returns) - if weight_strat == "max_div": - return self._calculate_max_div_weights(returns) - if weight_strat == "min_vol_overweight": - return self._calculate_min_vol_overweight_weights(returns) - - msg = "Weight strategy not implemented" - raise NotImplementedError(msg) - -
-[docs] - def make_portfolio( - self: Self, - name: str, - weight_strat: LiteralPortfolioWeightings | None = None, - ) -> DataFrame: - """Calculate a basket timeseries based on the supplied weights. - - Args: - name: Name of the basket timeseries. - weight_strat: Weight calculation strategies. Optional. - - Returns: - A basket timeseries. - """ - if self.weights is None and weight_strat is None: - msg = ( - "OpenFrame weights property must be provided " - "to run the make_portfolio method." - ) - raise NoWeightsError(msg) - - returns = self._prepare_returns_for_portfolio() - - if weight_strat: - self.weights = self._calculate_weights_from_strategy( - weight_strat=weight_strat, - returns=returns, - ) - - return DataFrame( - data=(returns @ array(self.weights)).add(1.0).cumprod(), - index=self.tsdf.index, - columns=[[name], [ValueType.PRICE]], - dtype="float64", - )
- - -
-[docs] - def rolling_info_ratio( - self: Self, - long_column: int = 0, - short_column: int = 1, - observations: int = 21, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> DataFrame: - """Calculate rolling Information Ratio. - - The Information Ratio equals ( fund return less index return ) divided by - the Tracking Error. And the Tracking Error is the standard deviation of the - difference between the fund and its index returns. - - Args: - long_column: Column of timeseries that is the numerator in the ratio. - Defaults to 0. - short_column: Column of timeseries that is the denominator in the ratio. - Defaults to 1. - observations: The length of the rolling window to use is set as number of - observations. Defaults to 21. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Rolling Information Ratios. - """ - long_label = cast( - "tuple[str, str]", - self.tsdf.iloc[:, long_column].name, - )[0] - short_label = cast( - "tuple[str, str]", - self.tsdf.iloc[:, short_column].name, - )[0] - ratio_label = f"{long_label} / {short_label}" - if periods_in_a_year_fixed: - time_factor = float(periods_in_a_year_fixed) - else: - time_factor = self.periods_in_a_year - - relative = ( - 1.0 + self.tsdf.iloc[:, long_column] - self.tsdf.iloc[:, short_column] - ) - - retseries = ( - relative.ffill() - .pct_change() - .rolling(observations, min_periods=observations) - .sum() - ) - retdf = retseries.dropna().to_frame() - - voldf = relative.ffill().pct_change().rolling( - observations, - min_periods=observations, - ).std() * sqrt(time_factor) - voldf = voldf.dropna().to_frame() - - ratiodf = (retdf.iloc[:, 0] / voldf.iloc[:, 0]).to_frame() - ratiodf.columns = [[ratio_label], ["Information Ratio"]] - - return DataFrame(ratiodf)
- - -
-[docs] - def rolling_beta( - self: Self, - asset_column: int = 0, - market_column: int = 1, - observations: int = 21, - dlta_degr_freedms: int = 1, - ) -> DataFrame: - """Calculate rolling Market Beta. - - Calculates Beta as Co-variance of asset & market divided by Variance - of the market. - - Reference: https://www.investopedia.com/terms/b/beta.asp. - - Args: - asset_column: Column of timeseries that is the asset. Defaults to 0. - market_column: Column of timeseries that is the market. Defaults to 1. - observations: The length of the rolling window to use is set as number of - observations. Defaults to 21. - dlta_degr_freedms: Variance bias factor taking the value 0 or 1. - Defaults to 1. - - Returns: - Rolling Betas. - """ - market_label = cast("tuple[str, str]", self.tsdf.iloc[:, market_column].name)[ - 0 - ] - asset_label = cast("tuple[str, str]", self.tsdf.iloc[:, asset_column].name)[0] - beta_label = f"{asset_label} / {market_label}" - - rolling = ( - self.tsdf.ffill() - .pct_change() - .rolling( - observations, - min_periods=observations, - ) - ) - - rcov = rolling.cov(ddof=dlta_degr_freedms) - rcov = rcov.dropna() - - rollbetaseries = rcov.iloc[:, asset_column].xs( - market_label, - level=1, - ) / rcov.iloc[ - :, - market_column, - ].xs( - market_label, - level=1, - ) - rollbeta = rollbetaseries.to_frame() - rollbeta.index = rollbeta.index.get_level_values(0) - rollbeta.columns = MultiIndex.from_arrays([[beta_label], ["Beta"]]) - - return rollbeta
- - -
-[docs] - def rolling_corr( - self: Self, - first_column: int = 0, - second_column: int = 1, - observations: int = 21, - ) -> DataFrame: - """Calculate rolling Correlation. - - Calculates correlation between two series. The period with - at least the given number of observations is the first period calculated. - - Args: - first_column: The position as integer of the first timeseries to compare. - Defaults to 0. - second_column: The position as integer of the second timeseries to compare. - Defaults to 1. - observations: The length of the rolling window to use is set as number of - observations. Defaults to 21. - - Returns: - Rolling Correlations. - """ - corr_label = ( - cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0] - + "_VS_" - + cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0] - ) - first_series = ( - self.tsdf.iloc[:, first_column] - .ffill() - .pct_change()[1:] - .rolling(observations, min_periods=observations) - ) - second_series = self.tsdf.iloc[:, second_column].ffill().pct_change()[1:] - corrdf = first_series.corr(other=second_series).dropna().to_frame() - corrdf.columns = MultiIndex.from_arrays( - [ - [corr_label], - ["Rolling correlation"], - ], - ) - - return DataFrame(corrdf)
- - -
-[docs] - def multi_factor_linear_regression( - self: Self, - dependent_column: tuple[str, ValueType], - ) -> tuple[DataFrame, OpenTimeSeries]: - """Perform a multi-factor linear regression. - - This function treats one specified column in the DataFrame as the dependent - variable (y) and uses all remaining columns as independent variables (X). - It utilizes a scikit-learn LinearRegression model and returns a DataFrame - with summary output and an OpenTimeSeries of predicted values. - - Args: - dependent_column: A tuple key to select the column in the - OpenFrame.tsdf.columns to use as the dependent variable. - - Returns: - A tuple containing: - - A DataFrame with the R-squared, the intercept and the regression - coefficients - - An OpenTimeSeries of predicted values - - Raises: - KeyError: If the column tuple is not found in the OpenFrame.tsdf.columns. - ValueError: If not all series are returnseries (ValueType.RTRN). - """ - key_msg = ( - f"Tuple ({dependent_column[0]}, " - f"{dependent_column[1].value}) not found in data." - ) - if dependent_column not in self.tsdf.columns: - raise KeyError(key_msg) - - vtype_msg = "All series should be of ValueType.RTRN." - if not all(x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)): - raise MixedValuetypesError(vtype_msg) - - dependent = self.tsdf[dependent_column] - factors = self.tsdf.drop(columns=[dependent_column]) - indx = ["R-square", "Intercept", *factors.columns.get_level_values(0)] - - model = LinearRegression() - model.fit(factors, dependent) - - predictions = OpenTimeSeries.from_arrays( - name=f"Predicted {dependent_column[0]}", - dates=[date.strftime("%Y-%m-%d") for date in self.tsdf.index], - values=list(model.predict(factors)), - valuetype=ValueType.RTRN, - ) - - output = [model.score(factors, dependent), model.intercept_, *model.coef_] - - result = DataFrame(data=output, index=indx, columns=[dependent_column[0]]) - - return result, predictions.to_cumret()
- - - def _validate_and_prepare_rebalance_inputs( - self: Self, - items: list[str] | None, - bal_weights: list[float] | None, - *, - equal_weights: bool, - ) -> tuple[list[str], list[float]]: - """Validate and prepare inputs for rebalanced portfolio. - - Args: - items: List of items to include. If None, uses all items. - bal_weights: List of weights. If None, uses frame weights. - equal_weights: If True, use equal weights. - - Returns: - Tuple of (validated items, validated weights). - - Raises: - WeightsNotProvidedError: If weights are required but not provided. - TypeError: If items is not a list. - PortfolioItemsNotWithinFrameError: If items are invalid. - """ - if bal_weights is None and not equal_weights: - if self.weights is None: - msg = "Weights must be provided." - raise WeightsNotProvidedError(msg) - bal_weights = list(self.weights) - - if items is None: - items = list(self.columns_lvl_zero) - else: - msg = "Items must be passed as list." - if not isinstance(items, list): - raise TypeError(msg) - if not items: - msg = "Items for portfolio must be within SeriesFrame items." - raise PortfolioItemsNotWithinFrameError(msg) - if not set(items) <= set(self.columns_lvl_zero): - msg = "Items for portfolio must be within SeriesFrame items." - raise PortfolioItemsNotWithinFrameError(msg) - - if equal_weights: - bal_weights = [1 / len(items)] * len(items) - - return items, cast("list[float]", bal_weights) - - def _initialize_rebalance_output( - self: Self, - items: list[str], - name: str, - cash_values: list[float], - ) -> dict[str, dict[str, list[float]]]: - """Initialize output structure for rebalanced portfolio. - - Args: - items: List of items in portfolio. - name: Name of the portfolio. - cash_values: Cash index values. - - Returns: - Initialized output dictionary. - """ - output = { - item: { - ValueType.PRICE: [], - "buysell_qty": [0.0] * self.length, - "position": [0.0] * self.length, - "value": [0.0] * self.length, - "twr": [0.0] * self.length, - "settle": [0.0] * self.length, - } - for item in items - } - output.update( - { - "cash": { - ValueType.PRICE: cash_values, - "buysell_qty": [0.0] * self.length, - "position": [0.0] * self.length, - "value": [0.0] * self.length, - "twr": [0.0] * self.length, - "settle": [0.0] * self.length, - }, - name: { - ValueType.PRICE: [1.0] + [0.0] * (self.length - 1), - "buysell_qty": [-1.0] + [0.0] * (self.length - 1), - "position": [-1.0] + [0.0] * (self.length - 1), - "value": [-1.0] + [0.0] * (self.length - 1), - "twr": [1.0] + [0.0] * (self.length - 1), - "settle": [1.0] + [0.0] * (self.length - 1), - }, - }, - ) - return output - - def _initialize_first_day_positions( - self: Self, - items: list[str], - bal_weights: list[float], - output: dict[str, dict[str, list[float]]], - name: str, - ) -> None: - """Initialize positions for the first day. - - Args: - items: List of items in portfolio. - bal_weights: Weights for each item. - output: Output dictionary to update. - name: Name of the portfolio. - """ - for item, weight in zip(items, bal_weights, strict=False): - output[item][ValueType.PRICE] = cast( - "list[float]", - self.tsdf[(item, ValueType.PRICE)].to_numpy().tolist(), - ) - output[item]["buysell_qty"][0] = ( - weight / self.tsdf[(item, ValueType.PRICE)].iloc[0] - ) - output[item]["position"][0] = output[item]["buysell_qty"][0] - output[item]["value"][0] = ( - output[item]["position"][0] * output[item][ValueType.PRICE][0] - ) - output[item]["settle"][0] = ( - -output[item]["buysell_qty"][0] * output[item][ValueType.PRICE][0] - ) - output["cash"]["buysell_qty"][0] += output[item]["settle"][0] - output[item]["twr"][0] = ( - output[item]["value"][0] / -output[item]["settle"][0] - ) - - output["cash"]["position"][0] = ( - output["cash"]["buysell_qty"][0] + output[name]["settle"][0] - ) - output["cash"]["settle"][0] = -output["cash"]["position"][0] - - def _process_rebalancing_day( - self: Self, - day: int, - items: list[str], - bal_weights: list[float], - output: dict[str, dict[str, list[float]]], - name: str, - ) -> tuple[float, float]: - """Process a rebalancing day. - - Args: - day: Current day index. - items: List of items in portfolio. - bal_weights: Target weights for rebalancing. - output: Output dictionary to update. - name: Name of the portfolio. - - Returns: - Tuple of (portfolio_value, settle_value). - """ - portfolio_value = 0.0 - settle_value = 0.0 - - for item, weight in zip(items, bal_weights, strict=False): - output[item]["buysell_qty"][day] = ( - weight - - output[item]["value"][day - 1] / -output[name]["value"][day - 1] - ) / output[item][ValueType.PRICE][day] - output[item]["position"][day] = ( - output[item]["position"][day - 1] + output[item]["buysell_qty"][day] - ) - output[item]["value"][day] = ( - output[item]["position"][day] * output[item][ValueType.PRICE][day] - ) - portfolio_value += output[item]["value"][day] - output[item]["twr"][day] = ( - output[item]["value"][day] - / (output[item]["value"][day - 1] - output[item]["settle"][day]) - * output[item]["twr"][day - 1] - ) - output[item]["settle"][day] = ( - -output[item]["buysell_qty"][day] * output[item][ValueType.PRICE][day] - ) - settle_value += output[item]["settle"][day] - - return portfolio_value, settle_value - - def _process_non_rebalancing_day( - self: Self, - day: int, - items: list[str], - output: dict[str, dict[str, list[float]]], - ) -> float: - """Process a non-rebalancing day. - - Args: - day: Current day index. - items: List of items in portfolio. - output: Output dictionary to update. - - Returns: - Portfolio value. - """ - portfolio_value = 0.0 - - for item in items: - output[item]["position"][day] = output[item]["position"][day - 1] - output[item]["value"][day] = ( - output[item]["position"][day] * output[item][ValueType.PRICE][day] - ) - portfolio_value += output[item]["value"][day] - output[item]["twr"][day] = ( - output[item]["value"][day] - / (output[item]["value"][day - 1] - output[item]["settle"][day]) - * output[item]["twr"][day - 1] - ) - - return portfolio_value - - def _update_cash_and_portfolio( - self: Self, - day: int, - portfolio_value: float, - settle_value: float, - output: dict[str, dict[str, list[float]]], - name: str, - ) -> None: - """Update cash and portfolio values for a day. - - Args: - day: Current day index. - portfolio_value: Total portfolio value (before cash). - settle_value: Total settle value. - output: Output dictionary to update. - name: Name of the portfolio. - """ - output["cash"]["buysell_qty"][day] = settle_value - output["cash"]["position"][day] = ( - output["cash"]["position"][day - 1] - * output["cash"][ValueType.PRICE][day] - / output["cash"][ValueType.PRICE][day - 1] - + output["cash"]["buysell_qty"][day] - ) - output["cash"]["value"][day] = output["cash"]["position"][day] - total_portfolio_value = portfolio_value + output["cash"]["value"][day] - output[name]["position"][day] = output[name]["position"][day - 1] - output[name]["value"][day] = -total_portfolio_value - output[name]["twr"][day] = ( - output[name]["value"][day] / output[name]["position"][day] - ) - output[name][ValueType.PRICE][day] = output[name]["twr"][day] - - def _build_rebalance_result( - self: Self, - output: dict[str, dict[str, list[float]]], - instruments: list[str], - subheaders: list[str | ValueType], - ) -> DataFrame: - """Build result DataFrame from output dictionary. - - Args: - output: Output dictionary with all calculated values. - instruments: List of instrument names. - subheaders: List of subheader names. - - Returns: - DataFrame with MultiIndex columns. - """ - result = DataFrame() - for outvalue in output.values(): - result = concat( - [ - result, - DataFrame(data=outvalue, index=self.tsdf.index), - ], - axis="columns", - ) - lvlone, lvltwo = [], [] - for instr in instruments: - lvlone.extend([instr] * 6) - lvltwo.extend(subheaders) - result.columns = MultiIndex.from_arrays([lvlone, lvltwo]) - return result - -
-[docs] - def rebalanced_portfolio( - self: Self, - name: str, - items: list[str] | None = None, - bal_weights: list[float] | None = None, - frequency: int = 1, - cash_index: OpenTimeSeries | None = None, - *, - equal_weights: bool = False, - drop_extras: bool = True, - ) -> OpenFrame: - """Create a rebalanced portfolio from the OpenFrame constituents. - - Args: - name: Name of the portfolio. - items: List of items to include in the portfolio. If None, uses all items. - Optional. - bal_weights: List of weights for rebalancing. If None, uses frame weights. - Optional. - frequency: Rebalancing frequency. Defaults to 1. - cash_index: Cash index series for cash component. Optional. - equal_weights: If True, use equal weights for all items. Defaults to False. - drop_extras: If True, only return TWR series; if False, return all details. - Defaults to True. - - Returns: - OpenFrame containing the rebalanced portfolio. - """ - items, bal_weights = self._validate_and_prepare_rebalance_inputs( - items, - bal_weights, - equal_weights=equal_weights, - ) - - if cash_index: - cash_index.tsdf = cash_index.tsdf.reindex(self.tsdf.index) - cash_values: list[float] = cast( - "list[float]", cash_index.tsdf.iloc[:, 0].to_numpy().tolist() - ) - else: - cash_values = [1.0] * self.length - - if self.tsdf.isna().to_numpy().any(): - self.value_nan_handle() - - ccies = list({serie.currency for serie in self.constituents}) - if len(ccies) != 1: - msg = "Items for portfolio must be denominated in same currency." - raise MultipleCurrenciesError(msg) - currency = ccies[0] - - instruments = [*items, "cash", name] - subheaders = [ - ValueType.PRICE, - "buysell_qty", - "position", - "value", - "twr", - "settle", - ] - - output = self._initialize_rebalance_output( - items=items, - name=name, - cash_values=cash_values, - ) - - self._initialize_first_day_positions( - items=items, - bal_weights=bal_weights, - output=output, - name=name, - ) - - counter = 1 - for day in range(1, self.length): - if day == frequency * counter: - portfolio_value, settle_value = self._process_rebalancing_day( - day=day, - items=items, - bal_weights=bal_weights, - output=output, - name=name, - ) - counter += 1 - else: - portfolio_value = self._process_non_rebalancing_day( - day=day, - items=items, - output=output, - ) - settle_value = 0.0 - - self._update_cash_and_portfolio( - day=day, - portfolio_value=portfolio_value, - settle_value=settle_value, - output=output, - name=name, - ) - - result = self._build_rebalance_result( - output=output, - instruments=instruments, - subheaders=subheaders, - ) - - series = [] - if drop_extras: - used_constituents = [ - item for item in self.constituents if item.label in items - ] - series.extend( - [ - OpenTimeSeries.from_df( - dframe=result[(item.label, "twr")], - valuetype=ValueType.PRICE, - baseccy=item.currency, - local_ccy=item.local_ccy, - ) - for item in used_constituents - ] - ) - series.append( - OpenTimeSeries.from_df( - dframe=result[(name, "twr")], - valuetype=ValueType.PRICE, - baseccy=currency, - local_ccy=True, - ), - ) - else: - series.extend( - [ - OpenTimeSeries.from_df( - dframe=result.loc[:, col], - valuetype=ValueType.PRICE, - baseccy=currency, - local_ccy=True, - ).set_new_label(f"{col[0]}, {col[1]!s}") - for col in result.columns - ] - ) - - return OpenFrame(series)
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/load_plotly.html b/docs/build/html/_modules/openseries/load_plotly.html deleted file mode 100644 index 82ec6806..00000000 --- a/docs/build/html/_modules/openseries/load_plotly.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - openseries.load_plotly — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.load_plotly

-"""Function to load plotly layout and configuration from local json file."""
-
-from __future__ import annotations
-
-from json import load
-from logging import getLogger
-from pathlib import Path
-from typing import TYPE_CHECKING
-
-import requests
-from requests.exceptions import ConnectionError as RequestsConnectionError
-
-if TYPE_CHECKING:
-    from .owntypes import CaptorLogoType, PlotlyLayoutType  # pragma: no cover
-
-logger = getLogger(__name__)
-
-__all__ = ["load_plotly_dict"]
-
-
-def _check_remote_file_existence(url: str) -> bool:
-    """Check if remote file exists.
-
-    Args:
-        url: Path to remote file.
-
-    Returns:
-        True if url is valid and False otherwise.
-    """
-    ok_code = 200
-
-    try:
-        response = requests.head(url, timeout=30)
-        if response.status_code != ok_code:
-            return False
-    except RequestsConnectionError:
-        return False
-    return True
-
-
-
-[docs] -def load_plotly_dict( - *, - responsive: bool = True, -) -> tuple[PlotlyLayoutType, CaptorLogoType]: - """Load Plotly defaults. - - Args: - responsive: Flag whether to load as responsive. Defaults to True. - - Returns: - tuple[PlotlyLayoutType, CaptorLogoType]: A tuple (config_and_layout, logo) - where config_and_layout is the Plotly config and layout template dict, - and logo is the Captor logo dict (may be empty if the remote logo is - unavailable). - """ - package_dir = Path(__file__).parent - layoutfile = package_dir / "plotly_layouts.json" - logofile = package_dir / "plotly_captor_logo.json" - - with layoutfile.open(mode="r", encoding="utf-8") as layout_file: - fig = load(layout_file) - with logofile.open(mode="r", encoding="utf-8") as logo_file: - logo = load(logo_file) - - if not _check_remote_file_existence(url=logo["source"]): - msg = f"Failed to add logo image from URL {logo['source']}" - logger.warning(msg) - logo = {} - - fig["config"].update({"responsive": responsive}) - - return fig, logo
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/owntypes.html b/docs/build/html/_modules/openseries/owntypes.html deleted file mode 100644 index ef39a180..00000000 --- a/docs/build/html/_modules/openseries/owntypes.html +++ /dev/null @@ -1,619 +0,0 @@ - - - - - - - - openseries.owntypes — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.owntypes

-"""Declaring types used throughout the project."""
-
-from __future__ import annotations
-
-import datetime as dt
-from enum import StrEnum
-from pprint import pformat
-from typing import (
-    TYPE_CHECKING,
-    Annotated,
-    ClassVar,
-    Literal,
-    Self,
-    TypeAlias,
-    TypeVar,
-)
-
-from annotated_types import MinLen
-from numpy import datetime64
-from pandas import Series, Timestamp
-from pydantic import BaseModel, Field, StringConstraints
-
-if TYPE_CHECKING:
-    from pandas import Series as _Series
-
-    SeriesFloat = _Series[float]
-else:
-    SeriesFloat = Series
-
-__all__ = ["ValueType"]
-
-
-SeriesOrFloat_co = TypeVar("SeriesOrFloat_co", float, SeriesFloat, covariant=True)
-
-
-CountryStringType = Annotated[
-    str,
-    StringConstraints(
-        strip_whitespace=True,
-        pattern=r"^[A-Z]{2}$",
-        to_upper=True,
-        min_length=2,
-        max_length=2,
-        strict=True,
-    ),
-]
-CountrySetType: TypeAlias = Annotated[set[CountryStringType], MinLen(1)]
-CountriesType: TypeAlias = CountrySetType | CountryStringType
-
-
-
-[docs] -class Countries(BaseModel): - """Declare Countries.""" - - countryinput: CountriesType
- - - -CurrencyStringType = Annotated[ - str, - StringConstraints( - pattern=r"^[A-Z]{3}$", - to_upper=True, - min_length=3, - max_length=3, - strict=True, - strip_whitespace=True, - ), -] - - -
-[docs] -class Currency(BaseModel): - """Declare Currency.""" - - ccy: CurrencyStringType
- - - -DateStringType = Annotated[ - str, - StringConstraints( - pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$", - strip_whitespace=True, - strict=True, - min_length=10, - max_length=10, - ), -] -DateListType: TypeAlias = Annotated[list[DateStringType], MinLen(1)] - -ValueListType: TypeAlias = Annotated[list[float], MinLen(1)] - -DaysInYearType = Annotated[int, Field(strict=True, ge=1, le=366)] - -DateType = str | dt.date | dt.datetime | datetime64 | Timestamp - -PlotlyConfigType = ( - str - | int - | float - | bool - | list[str] - | dict[str, str | int | float | bool | list[str]] -) - -PlotlyLayoutType = dict[str, PlotlyConfigType] - -CaptorLogoType = dict[str, str | float] - -LiteralJsonOutput = Literal["values", "tsdf"] -LiteralTrunc = Literal["before", "after", "both"] -LiteralLinePlotMode = ( - Literal[ - "lines", - "markers", - "lines+markers", - "lines+text", - "markers+text", - "lines+markers+text", - ] - | None -) -LiteralHowMerge = Literal["outer", "inner"] -LiteralQuantileInterp = Literal["linear", "lower", "higher", "midpoint", "nearest"] -LiteralBizDayFreq = Literal["B", "BME", "BQE", "BYE"] -LiteralPandasReindexMethod = ( - Literal["pad", "ffill", "backfill", "bfill", "nearest"] | None -) -LiteralNanMethod = Literal["fill", "drop"] -LiteralCaptureRatio = Literal["up", "down", "both"] -LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"] -LiteralPlotlyOutput = Literal["file", "div"] -LiteralPlotlyJSlib = Literal[True, False, "cdn"] -LiteralPlotlyHistogramPlotType = Literal["bars", "lines"] -LiteralPlotlyHistogramBarMode = Literal["stack", "group", "overlay", "relative"] -LiteralPlotlyHistogramCurveType = Literal["normal", "kde"] -LiteralPlotlyHistogramHistNorm = Literal[ - "percent", - "probability", - "density", - "probability density", -] -LiteralPortfolioWeightings = Literal[ - "eq_weights", "inv_vol", "max_div", "min_vol_overweight" -] -LiteralMinimizeMethods = Literal[ - "SLSQP", - "Nelder-Mead", - "Powell", - "CG", - "BFGS", - "Newton-CG", - "L-BFGS-B", - "TNC", - "COBYLA", - "trust-constr", - "dogleg", - "trust-ncg", - "trust-exact", - "trust-krylov", -] - -LiteralSeriesProps = Literal[ - "value_ret", - "geo_ret", - "arithmetic_ret", - "vol", - "downside_deviation", - "ret_vol_ratio", - "sortino_ratio", - "kappa3_ratio", - "z_score", - "skew", - "kurtosis", - "positive_share", - "var_down", - "cvar_down", - "vol_from_var", - "worst", - "worst_month", - "max_drawdown_cal_year", - "max_drawdown", - "max_drawdown_date", - "first_idx", - "last_idx", - "length", - "span_of_days", - "yearfrac", - "periods_in_a_year", - "autocorr", - "partial_autocorr", -] -LiteralFrameProps = Literal[ - "value_ret", - "geo_ret", - "arithmetic_ret", - "autocorr", - "vol", - "downside_deviation", - "ret_vol_ratio", - "sortino_ratio", - "kappa3_ratio", - "z_score", - "skew", - "kurtosis", - "positive_share", - "var_down", - "cvar_down", - "vol_from_var", - "worst", - "worst_month", - "max_drawdown", - "max_drawdown_date", - "max_drawdown_cal_year", - "first_indices", - "last_indices", - "lengths_of_items", - "span_of_days_all", -] - - -
-[docs] -class PropertiesList(list[str]): - """Base class for allowed property arguments definition.""" - - allowed_strings: ClassVar[set[str]] = { - "value_ret", - "geo_ret", - "arithmetic_ret", - "vol", - "downside_deviation", - "ret_vol_ratio", - "sortino_ratio", - "kappa3_ratio", - "omega_ratio", - "z_score", - "skew", - "kurtosis", - "positive_share", - "var_down", - "cvar_down", - "vol_from_var", - "worst", - "worst_month", - "max_drawdown", - "max_drawdown_date", - "max_drawdown_cal_year", - } - - def _validate(self: Self) -> None: - """Validate the string input of the all_properties method.""" - seen = set() - invalids = set() - duplicates = set() - msg = "" - for item in self: - if item not in self.allowed_strings: - invalids.add(item) - if item in seen: - duplicates.add(item) - seen.add(item) - if len(invalids) != 0: - msg += ( - f"Invalid string(s): {list(invalids)}.\nAllowed strings are:" - f"\n{pformat(self.allowed_strings)}\n" - ) - if len(duplicates) != 0: - msg += f"Duplicate string(s): {list(duplicates)}." - if len(msg) != 0: - raise PropertiesInputValidationError(msg)
- - - -
-[docs] -class OpenTimeSeriesPropertiesList(PropertiesList): - """Allowed property arguments for the OpenTimeSeries class.""" - - allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | { - "first_idx", - "last_idx", - "length", - "span_of_days", - "yearfrac", - "periods_in_a_year", - "autocorr", - "partial_autocorr", - } - -
-[docs] - def __init__( - self: Self, - *args: LiteralSeriesProps, - ) -> None: - """Property arguments for the OpenTimeSeries class.""" - super().__init__(args) - self._validate()
-
- - - -
-[docs] -class OpenFramePropertiesList(PropertiesList): - """Allowed property arguments for the OpenFrame class.""" - - allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | { - "autocorr", - "first_indices", - "last_indices", - "lengths_of_items", - "span_of_days_all", - } - -
-[docs] - def __init__(self: Self, *args: LiteralFrameProps) -> None: - """Property arguments for the OpenFrame class.""" - super().__init__(args) - self._validate()
-
- - - -
-[docs] -class ValueType(StrEnum): - """Enum types of OpenTimeSeries to identify the output.""" - - EWMA_VOL = "EWMA volatility" - EWMA_VAR = "EWMA VaR" - PRICE = "Price(Close)" - RTRN = "Return(Total)" - RELRTRN = "Relative return" - ROLLBETA = "Beta" - ROLLCORR = "Rolling correlation" - ROLLCVAR = "Rolling CVaR" - ROLLINFORATIO = "Information Ratio" - ROLLRTRN = "Rolling returns" - ROLLVAR = "Rolling VaR" - ROLLVOL = "Rolling volatility"
- - - -
-[docs] -class MixedValuetypesError(Exception): - """Raised when provided timeseries valuetypes are not the same."""
- - - -
-[docs] -class AtLeastOneFrameError(Exception): - """Raised when none of the possible frame inputs is provided."""
- - - -
-[docs] -class DateAlignmentError(Exception): - """Raised when date input is not aligned with existing range."""
- - - -
-[docs] -class NumberOfItemsAndLabelsNotSameError(Exception): - """Raised when number of labels is not matching the number of timeseries."""
- - - -
-[docs] -class InitialValueZeroError(Exception): - """Raised when a calculation cannot be performed due to initial value(s) zero."""
- - - -
-[docs] -class CountriesNotStringNorListStrError(Exception): - """Raised when countries argument is not provided in correct format."""
- - - -
-[docs] -class MarketsNotStringNorListStrError(Exception): - """Raised when markets argument is not provided in correct format."""
- - - -
-[docs] -class TradingDaysNotAboveZeroError(Exception): - """Raised when trading days argument is not above zero."""
- - - -
-[docs] -class BothStartAndEndError(Exception): - """Raised when both start and end dates are provided."""
- - - -
-[docs] -class NoWeightsError(Exception): - """Raised when no weights are provided to function where necessary."""
- - - -
-[docs] -class LabelsNotUniqueError(Exception): - """Raised when provided label names are not unique."""
- - - -
-[docs] -class RatioInputError(Exception): - """Raised when ratio keyword not provided correctly."""
- - - -
-[docs] -class MergingResultedInEmptyError(Exception): - """Raised when a merge resulted in an empty DataFrame."""
- - - -
-[docs] -class IncorrectArgumentComboError(Exception): - """Raised when correct combination of arguments is not provided."""
- - - -
-[docs] -class PropertiesInputValidationError(Exception): - """Raised when duplicate strings are provided."""
- - - -
-[docs] -class ResampleDataLossError(Exception): - """Raised when user attempts to run resample_to_business_period_ends on returns."""
- - - -class WeightsNotProvidedError(Exception): - """Raised when weights are not provided.""" - - -class MultipleCurrenciesError(Exception): - """Raised when multiple currencies are provided.""" - - -class PortfolioItemsNotWithinFrameError(Exception): - """Raised when portfolio items are not within frame.""" - - -class MaxDiversificationNaNError(Exception): - """Raised when max_div weight strategy produces NaN values.""" - - -class MaxDiversificationNegativeWeightsError(Exception): - """Raised when max_div weight strategy produces negative weights.""" -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/portfoliotools.html b/docs/build/html/_modules/openseries/portfoliotools.html deleted file mode 100644 index af00cc40..00000000 --- a/docs/build/html/_modules/openseries/portfoliotools.html +++ /dev/null @@ -1,1042 +0,0 @@ - - - - - - - - openseries.portfoliotools — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.portfoliotools

-"""Defining the portfolio tools for the OpenFrame class."""
-
-from __future__ import annotations
-
-from inspect import stack
-from pathlib import Path
-from typing import TYPE_CHECKING, Any, cast
-
-from numpy import (
-    append,
-    array,
-    einsum,
-    float64,
-    inf,
-    isnan,
-    linspace,
-    nan,
-    sqrt,
-)
-from numpy import (
-    sum as npsum,
-)
-from pandas import (
-    DataFrame,
-    Series,
-    concat,
-)
-from plotly.graph_objs import Figure  # type: ignore[import-untyped]
-from plotly.io import to_html  # type: ignore[import-untyped]
-from plotly.offline import plot  # type: ignore[import-untyped]
-from scipy.optimize import minimize
-
-from .load_plotly import load_plotly_dict
-from .owntypes import (
-    AtLeastOneFrameError,
-    LiteralLinePlotMode,
-    LiteralMinimizeMethods,
-    LiteralPlotlyJSlib,
-    LiteralPlotlyOutput,
-    MixedValuetypesError,
-    ValueType,
-)
-from .series import OpenTimeSeries
-from .simulation import _random_generator
-
-if TYPE_CHECKING:  # pragma: no cover
-    from collections.abc import Callable
-
-    from numpy.typing import NDArray
-    from pydantic import DirectoryPath
-
-    from .frame import OpenFrame
-
-__all__ = [
-    "constrain_optimized_portfolios",
-    "efficient_frontier",
-    "prepare_plot_data",
-    "sharpeplot",
-    "simulate_portfolios",
-]
-
-
-
-[docs] -def simulate_portfolios( - simframe: OpenFrame, - num_ports: int, - seed: int, -) -> DataFrame: - """Generate random weights for simulated portfolios. - - Args: - simframe: Return data for portfolio constituents. - num_ports: Number of possible portfolios to simulate. - seed: The seed for the random process. - - Returns: - The resulting data. - """ - copi = simframe.from_deepcopy() - - vtypes = [x == ValueType.RTRN for x in copi.tsdf.columns.get_level_values(1)] - if not any(vtypes): - copi.value_to_ret() - log_ret = copi.tsdf.copy()[1:] - elif all(vtypes): - log_ret = copi.tsdf.copy() - else: - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - log_ret.columns = log_ret.columns.get_level_values(0) - - cov_matrix = log_ret.cov() * simframe.periods_in_a_year - mean_returns = log_ret.mean() * simframe.periods_in_a_year - - randomizer = _random_generator(seed=seed) - all_weights = randomizer.random((num_ports, simframe.item_count)) - all_weights = all_weights / all_weights.sum(axis=1, keepdims=True) - - ret_arr = all_weights @ mean_returns - vol_arr = sqrt(einsum("ij,jk,ik->i", all_weights, cov_matrix, all_weights)) - sharpe_arr = ret_arr / vol_arr - - simdf = concat( - [ - DataFrame({"stdev": vol_arr, "ret": ret_arr, "sharpe": sharpe_arr}), - DataFrame(all_weights, columns=simframe.columns_lvl_zero), - ], - axis="columns", - ) - simdf = simdf.replace([inf, -inf], nan) - return simdf.dropna()
- - - -def _prepare_returns_for_frontier(eframe: OpenFrame) -> tuple[DataFrame, OpenFrame]: - """Prepare returns DataFrame for frontier calculation. - - Args: - eframe: Portfolio data. - - Returns: - Tuple of (log_ret DataFrame, copied frame). - - Raises: - MixedValuetypesError: If series types are mixed. - """ - if eframe.weights is None: - eframe.weights = [1.0 / eframe.item_count] * eframe.item_count - - copi = eframe.from_deepcopy() - - vtypes = [x == ValueType.RTRN for x in copi.tsdf.columns.get_level_values(1)] - if not any(vtypes): - copi.value_to_ret() - log_ret = copi.tsdf.copy()[1:] - elif all(vtypes): - log_ret = copi.tsdf.copy() - else: - msg = "Mix of series types will give inconsistent results" - raise MixedValuetypesError(msg) - - log_ret.columns = log_ret.columns.get_level_values(0) - return log_ret, copi - - -def _calculate_frontier_bounds( - simulated: DataFrame, - log_ret: DataFrame, - periods_in_a_year: float, -) -> tuple[float, float]: - """Calculate frontier return bounds. - - Args: - simulated: Simulated portfolios DataFrame. - log_ret: Returns DataFrame. - periods_in_a_year: Periods in a year. - - Returns: - Tuple of (min_return, max_return). - """ - min_stdev_idx = simulated["stdev"].idxmin() - frontier_min = cast("float", simulated.loc[min_stdev_idx, "ret"]) - - arithmetic_means = array(log_ret.mean() * periods_in_a_year) - cleaned_arithmetic_means = arithmetic_means[~isnan(arithmetic_means)] - - frontier_max = float(cleaned_arithmetic_means.max()) - return frontier_min, frontier_max - - -def _build_frontier_line( - log_ret: DataFrame, - frontier_min: float, - frontier_max: float, - frontier_points: int, - periods_in_a_year: float, - init_guess: NDArray[float64], - bounds: tuple[tuple[float, float], ...], - minimize_method: LiteralMinimizeMethods, -) -> tuple[list[float], list[NDArray[float64]]]: - """Build frontier line points. - - Args: - log_ret: Returns DataFrame. - frontier_min: Minimum return. - frontier_max: Maximum return. - frontier_points: Number of points. - periods_in_a_year: Periods in a year. - init_guess: Initial guess for optimization. - bounds: Optimization bounds. - minimize_method: Minimization method. - - Returns: - Tuple of (frontier_x, frontier_weights). - """ - - def _check_sum(weights: NDArray[float64]) -> float: - return cast("float", npsum(weights) - 1) - - def _get_ret_vol_sr( - lg_ret: DataFrame, - weights: NDArray[float64], - per_in_yr: float, - ) -> NDArray[float64]: - ret = npsum(lg_ret.mean() * weights) * per_in_yr - volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights)) - sr = ret / volatility - return cast("NDArray[float64]", array([ret, volatility, sr])) - - def _diff_return( - lg_ret: DataFrame, - weights: NDArray[float64], - per_in_yr: float, - poss_return: float, - ) -> float64: - return cast( - "float64", - _get_ret_vol_sr(lg_ret=lg_ret, weights=weights, per_in_yr=per_in_yr)[0] - - poss_return, - ) - - def _minimize_volatility( - weights: NDArray[float64], - ) -> float64: - return cast( - "float64", - _get_ret_vol_sr( - lg_ret=log_ret, - weights=weights, - per_in_yr=periods_in_a_year, - )[1], - ) - - frontier_y = linspace(start=frontier_min, stop=frontier_max, num=frontier_points) - frontier_x = [] - frontier_weights = [] - - for possible_return in frontier_y: - cons = cast( - "Any", - [ - {"type": "eq", "fun": _check_sum}, - { - "type": "eq", - "fun": lambda w, poss_return=possible_return: _diff_return( - lg_ret=log_ret, - weights=w, - per_in_yr=periods_in_a_year, - poss_return=poss_return, - ), - }, - ], - ) - - result = minimize( - fun=_minimize_volatility, - x0=init_guess, - method=minimize_method, - bounds=bounds, - constraints=cons, - ) - - frontier_x.append(result["fun"]) - frontier_weights.append(result["x"]) - - return frontier_x, frontier_weights - - -def _build_frontier_dataframe( - frontier_x: list[float], - frontier_y: NDArray[float64], - frontier_weights: list[NDArray[float64]], - columns_lvl_zero: list[str], -) -> DataFrame: - """Build frontier DataFrame. - - Args: - frontier_x: Frontier volatility values. - frontier_y: Frontier return values. - frontier_weights: Frontier weight arrays. - columns_lvl_zero: Column names. - - Returns: - Frontier DataFrame. - """ - line_df = concat( - [ - DataFrame(data=frontier_weights, columns=columns_lvl_zero), - DataFrame({"stdev": frontier_x, "ret": frontier_y}), - ], - axis="columns", - ) - line_df["sharpe"] = line_df.ret / line_df.stdev - - limit_small = 0.0001 - line_df = line_df.mask(line_df.abs() < limit_small, 0.0) - - weight_cols = columns_lvl_zero - weight_header = "<br><br>Weights:<br>" - line_df["text"] = line_df[weight_cols].apply( - lambda row: ( - weight_header - + "<br>".join([f"{row[col]:.1%} {col}" for col in weight_cols]) - ), - axis=1, - ) - - return line_df - - -def _apply_tweak(line_df: DataFrame) -> DataFrame: - """Apply tweak to frontier DataFrame. - - Args: - line_df: Frontier DataFrame. - - Returns: - Tweaked DataFrame. - """ - limit_tweak = 0.001 - line_df["stdev_diff"] = line_df.stdev.ffill().pct_change() - line_df = line_df.loc[line_df.stdev_diff.abs() > limit_tweak] - return line_df.drop(columns="stdev_diff") - - -def _create_optimization_functions( - log_ret: DataFrame, - periods_in_a_year: float, -) -> tuple[ - Callable[[NDArray[float64]], float], - Callable[[NDArray[float64]], NDArray[float64]], - Callable[[NDArray[float64]], float64], -]: - """Create optimization helper functions. - - Args: - log_ret: Returns DataFrame. - periods_in_a_year: Periods in a year. - - Returns: - Tuple of (_check_sum, _get_ret_vol_sr, _neg_sharpe) functions. - """ - - def _check_sum(weights: NDArray[float64]) -> float: - return cast("float", npsum(weights) - 1) - - def _get_ret_vol_sr(weights: NDArray[float64]) -> NDArray[float64]: - ret = npsum(log_ret.mean() * weights) * periods_in_a_year - volatility = sqrt(weights.T @ (log_ret.cov() * periods_in_a_year @ weights)) - sr = ret / volatility - return cast("NDArray[float64]", array([ret, volatility, sr])) - - def _neg_sharpe(weights: NDArray[float64]) -> float64: - return cast("float64", _get_ret_vol_sr(weights)[2] * -1) - - return _check_sum, _get_ret_vol_sr, _neg_sharpe - - -def _optimize_max_sharpe_portfolio( - init_guess: NDArray[float64], - bounds: tuple[tuple[float, float], ...], - minimize_method: LiteralMinimizeMethods, - _check_sum: Callable[[NDArray[float64]], float], - _get_ret_vol_sr: Callable[[NDArray[float64]], NDArray[float64]], - _neg_sharpe: Callable[[NDArray[float64]], float64], -) -> tuple[NDArray[float64], NDArray[float64]]: - """Optimize maximum Sharpe ratio portfolio. - - Args: - init_guess: Initial guess for optimization. - bounds: Optimization bounds. - minimize_method: Minimization method. - _check_sum: Check sum constraint function. - _get_ret_vol_sr: Get return, volatility, Sharpe ratio function. - _neg_sharpe: Negative Sharpe ratio function. - - Returns: - Tuple of (optimal metrics, optimal weights). - """ - constraints = cast("Any", [{"type": "eq", "fun": _check_sum}]) - opt_results = minimize( - fun=_neg_sharpe, - x0=init_guess, - method=minimize_method, - bounds=bounds, - constraints=constraints, - ) - - optimal = _get_ret_vol_sr(opt_results.x) - - return optimal, opt_results.x - - -
-[docs] -def efficient_frontier( - eframe: OpenFrame, - num_ports: int = 5000, - seed: int = 71, - bounds: tuple[tuple[float, float], ...] | None = None, - frontier_points: int = 200, - minimize_method: LiteralMinimizeMethods = "SLSQP", - *, - tweak: bool = True, -) -> tuple[DataFrame, DataFrame, NDArray[float64]]: - """Identify an efficient frontier. - - Args: - eframe: Portfolio data. - num_ports: Number of possible portfolios to simulate. Defaults to 5000. - seed: The seed for the random process. Defaults to 71. - bounds: The range of minimum and maximum allowed allocations for each asset. - frontier_points: Number of points along frontier to optimize. Defaults to 200. - minimize_method: The method passed into the scipy.minimize function. - Defaults to SLSQP. - tweak: Cutting the frontier to exclude multiple points with almost the - same risk. - Defaults to True. - - Returns: - The efficient frontier data, simulation data and optimal portfolio. - """ - log_ret, copi = _prepare_returns_for_frontier(eframe) - - simulated = simulate_portfolios(simframe=copi, num_ports=num_ports, seed=seed) - - frontier_min, frontier_max = _calculate_frontier_bounds( - simulated=simulated, - log_ret=log_ret, - periods_in_a_year=copi.periods_in_a_year, - ) - - if not bounds: - bounds = tuple((0.0, 1.0) for _ in range(eframe.item_count)) - init_guess = array(eframe.weights) - - _check_sum, _get_ret_vol_sr, _neg_sharpe = _create_optimization_functions( - log_ret=log_ret, - periods_in_a_year=copi.periods_in_a_year, - ) - - optimal, opt_weights = _optimize_max_sharpe_portfolio( - init_guess=init_guess, - bounds=bounds, - minimize_method=minimize_method, - _check_sum=_check_sum, - _get_ret_vol_sr=_get_ret_vol_sr, - _neg_sharpe=_neg_sharpe, - ) - - frontier_y = linspace(start=frontier_min, stop=frontier_max, num=frontier_points) - frontier_x, frontier_weights = _build_frontier_line( - log_ret=log_ret, - frontier_min=frontier_min, - frontier_max=frontier_max, - frontier_points=frontier_points, - periods_in_a_year=copi.periods_in_a_year, - init_guess=init_guess, - bounds=bounds, - minimize_method=minimize_method, - ) - - line_df = _build_frontier_dataframe( - frontier_x=frontier_x, - frontier_y=frontier_y, - frontier_weights=frontier_weights, - columns_lvl_zero=eframe.columns_lvl_zero, - ) - - if tweak: - line_df = _apply_tweak(line_df) - - return line_df, simulated, append(optimal, opt_weights)
- - - -
-[docs] -def constrain_optimized_portfolios( - data: OpenFrame, - serie: OpenTimeSeries, - portfolioname: str = "Current Portfolio", - simulations: int = 10000, - curve_points: int = 200, - bounds: tuple[tuple[float, float], ...] | None = None, - minimize_method: LiteralMinimizeMethods = "SLSQP", -) -> tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]: - """Constrain optimized portfolios to those that improve on the current one. - - Args: - data: Portfolio data. - serie: A timeseries representing the current portfolio. - portfolioname: Name of the portfolio. Defaults to "Current Portfolio". - simulations: Number of possible portfolios to simulate. Defaults to 10000. - curve_points: Number of optimal portfolios on the efficient frontier. - Defaults to 200. - bounds: The range of minimum and maximum allowed allocations for each asset. - minimize_method: The method passed into the scipy.minimize function. - Defaults to SLSQP. - - Returns: - The constrained optimal portfolio data. - - """ - lr_frame = data.from_deepcopy() - mv_frame = data.from_deepcopy() - - if not bounds: - bounds = tuple((0.0, 1.0) for _ in range(data.item_count)) - - front_frame, _, _ = efficient_frontier( - eframe=data, - num_ports=simulations, - frontier_points=curve_points, - bounds=bounds, - minimize_method=minimize_method, - ) - - condition_least_ret = front_frame.ret > serie.arithmetic_ret - least_ret_frame = front_frame[condition_least_ret].sort_values(by="stdev") - least_ret_port: Series[float] = least_ret_frame.iloc[0] - least_ret_port_name = f"Minimize vol & target return of {portfolioname}" - least_ret_weights: list[float] = [ - least_ret_port.loc[c] for c in lr_frame.columns_lvl_zero - ] - lr_frame.weights = least_ret_weights - resleast = OpenTimeSeries.from_df(lr_frame.make_portfolio(least_ret_port_name)) - - condition_most_vol = front_frame.stdev < serie.vol - most_vol_frame = front_frame[condition_most_vol].sort_values( - by="ret", - ascending=False, - ) - most_vol_port: Series[float] = most_vol_frame.iloc[0] - most_vol_port_name = f"Maximize return & target risk of {portfolioname}" - most_vol_weights: list[float] = [ - most_vol_port.loc[c] for c in mv_frame.columns_lvl_zero - ] - mv_frame.weights = most_vol_weights - resmost = OpenTimeSeries.from_df(mv_frame.make_portfolio(most_vol_port_name)) - - return lr_frame, resleast, mv_frame, resmost
- - - -
-[docs] -def prepare_plot_data( - assets: OpenFrame, - current: OpenTimeSeries, - optimized: NDArray[float64], -) -> DataFrame: - """Prepare data to be used as point_frame in the sharpeplot function. - - Args: - assets: Portfolio data with individual assets and a weighted portfolio. - current: The current or initial portfolio based on given weights. - optimized: Data optimized with the efficient_frontier method. - - Returns: - The data prepared with mean returns, volatility and weights. - """ - txt = "<br><br>Weights:<br>" + "<br>".join( - [ - f"{wgt:.1%} {nm}" - for wgt, nm in zip( - cast("list[float]", assets.weights), - assets.columns_lvl_zero, - strict=True, - ) - ], - ) - - opt_text_list = [ - f"{wgt:.1%} {nm}" - for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True) - ] - opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list) - plotframe = DataFrame( - data=[ - assets.arithmetic_ret, - assets.vol, - Series( - data=[""] * assets.item_count, - index=assets.vol.index, - ), - ], - index=["ret", "stdev", "text"], - ) - plotframe.columns = plotframe.columns.get_level_values(0) - plotframe["Max Sharpe Portfolio"] = Series( - data=[optimized[0], optimized[1], opt_text], - index=plotframe.index, - dtype=object, - ) - if current.label is not None: - label = current.label - plotframe[label] = Series( - data=[current.arithmetic_ret, current.vol, txt], - index=plotframe.index, - dtype=object, - ) - - return plotframe
- - - -def _determine_output_directory(directory: DirectoryPath | None) -> Path: - """Determine output directory for plot file. - - Args: - directory: Optional directory path. - - Returns: - Path to output directory. - """ - if directory: - return Path(directory).resolve() - if Path.home().joinpath("Documents").exists(): - return Path.home().joinpath("Documents") - return Path(stack()[2].filename).parent - - -def _add_simulated_portfolios_trace( - figure: Figure, - sim_frame: DataFrame, - returns: list[float], - risk: list[float], -) -> None: - """Add simulated portfolios trace to figure. - - Args: - figure: Plotly figure. - sim_frame: Simulated portfolios DataFrame. - returns: List to extend with returns. - risk: List to extend with risk values. - """ - returns.extend(list(sim_frame.loc[:, "ret"])) - risk.extend(list(sim_frame.loc[:, "stdev"])) - figure.add_scatter( - x=sim_frame.loc[:, "stdev"], - y=sim_frame.loc[:, "ret"], - hoverinfo="skip", - marker={ - "size": 10, - "opacity": 0.5, - "color": sim_frame.loc[:, "sharpe"], - "colorscale": "Jet", - "reversescale": True, - "colorbar": {"thickness": 20, "title": "Ratio<br>ret / vol"}, - }, - mode="markers", - name="simulated portfolios", - ) - - -def _add_efficient_frontier_trace( - figure: Figure, - line_frame: DataFrame, - returns: list[float], - risk: list[float], -) -> None: - """Add efficient frontier trace to figure. - - Args: - figure: Plotly figure. - line_frame: Efficient frontier DataFrame. - returns: List to extend with returns. - risk: List to extend with risk values. - """ - returns.extend(list(line_frame.loc[:, "ret"])) - risk.extend(list(line_frame.loc[:, "stdev"])) - figure.add_scatter( - x=line_frame.loc[:, "stdev"], - y=line_frame.loc[:, "ret"], - text=line_frame.loc[:, "text"], - xhoverformat=".2%", - yhoverformat=".2%", - hovertemplate="Return %{y}<br>Vol %{x}%{text}", - hoverlabel_align="right", - line={"width": 2.5, "dash": "solid"}, - mode="lines", - name="Efficient frontier", - ) - - -def _add_point_frame_traces( - figure: Figure, - point_frame: DataFrame, - point_frame_mode: LiteralLinePlotMode, - fig: dict[str, Any], - returns: list[float], - risk: list[float], -) -> None: - """Add point frame traces to figure. - - Args: - figure: Plotly figure. - point_frame: Point frame DataFrame. - point_frame_mode: Mode for point frame traces. - fig: Plotly figure dictionary. - returns: List to extend with returns. - risk: List to extend with risk values. - """ - layout_dict = cast( - "dict[str, str | int | float | bool | list[str]]", - fig["layout"], - ) - base_colorway = cast("list[str]", layout_dict.get("colorway", [])) - if len(base_colorway) < len(point_frame.columns) and base_colorway: - repeats = (len(point_frame.columns) + len(base_colorway) - 1) // len( - base_colorway - ) - colorway = (base_colorway * repeats)[: len(point_frame.columns)] - else: - colorway = base_colorway[: len(point_frame.columns)] - for col, clr in zip(point_frame.columns, colorway, strict=True): - returns.extend([cast("float", point_frame.loc["ret", col])]) - risk.extend([cast("float", point_frame.loc["stdev", col])]) - figure.add_scatter( - x=[point_frame.loc["stdev", col]], - y=[point_frame.loc["ret", col]], - xhoverformat=".2%", - yhoverformat=".2%", - hovertext=[point_frame.loc["text", col]], - hovertemplate="Return %{y}<br>Vol %{x}%{hovertext}", - hoverlabel_align="right", - marker={"size": 20, "color": clr}, - mode=point_frame_mode, - name=col, - text=col, - textfont={"size": 14}, - textposition="bottom center", - ) - - -def _configure_figure_layout( - figure: Figure, - titletext: str | None, - logo: dict[str, Any], - *, - title: bool = True, - add_logo: bool = True, -) -> None: - """Configure figure layout. - - Args: - figure: Plotly figure. - title: Whether to add title. - titletext: Optional title text. - add_logo: Whether to add logo. - logo: Logo dictionary. - """ - figure.update_layout( - xaxis={"tickformat": ".1%"}, - xaxis_title="volatility", - yaxis={ - "tickformat": ".1%", - "scaleanchor": "x", - "scaleratio": 1, - }, - yaxis_title="annual return", - showlegend=False, - ) - if title: - if titletext is None: - titletext = "<b>Risk and Return</b><br>" - figure.update_layout(title={"text": titletext, "font": {"size": 36}}) - - if add_logo: - figure.add_layout_image(logo) - - -def _generate_sharpeplot_output( - figure: Figure, - plotfile: Path, - filename: str, - output_type: LiteralPlotlyOutput, - include_plotlyjs: LiteralPlotlyJSlib, - fig: dict[str, Any], - *, - auto_open: bool = True, -) -> str: - """Generate output for sharpeplot. - - Args: - figure: Plotly figure. - plotfile: Path to plot file. - filename: Filename. - output_type: Output type. - include_plotlyjs: How to include plotly.js. - fig: Plotly figure dictionary. - auto_open: Whether to auto-open. - - Returns: - Output string. - """ - if output_type == "file": - plot( - figure_or_data=figure, - filename=str(plotfile), - auto_open=auto_open, - auto_play=False, - include_plotlyjs=include_plotlyjs, - config=fig["config"], - output_type=output_type, - ) - return str(plotfile) - - div_id = filename.split(maxsplit=1, sep=".")[0] - return cast( - "str", - to_html( - fig=figure, - config=fig["config"], - auto_play=False, - include_plotlyjs=include_plotlyjs, - full_html=False, - div_id=div_id, - ), - ) - - -
-[docs] -def sharpeplot( - sim_frame: DataFrame | None = None, - line_frame: DataFrame | None = None, - point_frame: DataFrame | None = None, - point_frame_mode: LiteralLinePlotMode = "markers", - filename: str | None = None, - directory: DirectoryPath | None = None, - titletext: str | None = None, - output_type: LiteralPlotlyOutput = "file", - include_plotlyjs: LiteralPlotlyJSlib = "cdn", - *, - title: bool = True, - add_logo: bool = True, - auto_open: bool = True, -) -> tuple[Figure, str]: - """Create scatter plot coloured by Sharpe Ratio. - - Args: - sim_frame: Data from the simulate_portfolios method. - line_frame: Data from the efficient_frontier method. - point_frame: Data to highlight current and efficient portfolios. - point_frame_mode: Which type of scatter to use. Defaults to markers. - filename: Name of the Plotly html file. - directory: Directory where Plotly html file is saved. - titletext: Text for the plot title. - output_type: Determines output type. Defaults to "file". - include_plotlyjs: Determines how the plotly.js library is included - in the output. - Defaults to "cdn". - title: Whether to add standard plot title. Defaults to True. - add_logo: Whether to add Captor logo. Defaults to True. - auto_open: Determines whether to open a browser window with the plot. - Defaults to True. - - Returns: - The scatter plot with simulated and optimized results. - """ - if sim_frame is None and line_frame is None and point_frame is None: - msg = "One of sim_frame, line_frame or point_frame must be provided." - raise AtLeastOneFrameError(msg) - - returns: list[float] = [] - risk: list[float] = [] - - dirpath = _determine_output_directory(directory) - if not filename: - filename = "sharpeplot.html" - plotfile = dirpath.joinpath(filename) - - fig, logo = load_plotly_dict() - figure = Figure(fig) - - if sim_frame is not None: - _add_simulated_portfolios_trace(figure, sim_frame, returns, risk) - if line_frame is not None: - _add_efficient_frontier_trace(figure, line_frame, returns, risk) - if point_frame is not None: - _add_point_frame_traces( - figure, point_frame, point_frame_mode, fig, returns, risk - ) - - _configure_figure_layout(figure, titletext, logo, title=title, add_logo=add_logo) - - string_output = _generate_sharpeplot_output( - figure, - plotfile, - filename, - output_type, - include_plotlyjs, - fig, - auto_open=auto_open, - ) - - return figure, string_output
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/report.html b/docs/build/html/_modules/openseries/report.html deleted file mode 100644 index 744e2bb2..00000000 --- a/docs/build/html/_modules/openseries/report.html +++ /dev/null @@ -1,816 +0,0 @@ - - - - - - - - openseries.report — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.report

-"""Functions related to HTML reports."""
-
-from __future__ import annotations
-
-from inspect import stack
-from itertools import cycle
-from json import dumps as json_dumps
-from logging import getLogger
-from pathlib import Path
-from secrets import choice
-from string import ascii_letters
-from typing import TYPE_CHECKING, Any, cast
-from warnings import catch_warnings, simplefilter
-from webbrowser import open as webbrowser_open
-
-from pandas import DataFrame, Index, Series, Timestamp, concat, isna
-from plotly.graph_objs import Bar, Figure, Scatter  # type: ignore[import-untyped]
-from plotly.utils import PlotlyJSONEncoder  # type: ignore[import-untyped]
-
-from .html_utils import _get_base_css, _get_plotly_script
-from .load_plotly import load_plotly_dict
-from .owntypes import (
-    CaptorLogoType,
-    LiteralBizDayFreq,
-    LiteralFrameProps,
-    LiteralPlotlyJSlib,
-    LiteralPlotlyOutput,
-    ValueType,
-)
-
-if TYPE_CHECKING:  # pragma: no cover
-    from .frame import OpenFrame
-
-logger = getLogger(__name__)
-
-__all__ = ["report_html"]
-
-
-def calendar_period_returns(
-    data: OpenFrame,
-    freq: LiteralBizDayFreq = "BYE",
-    *,
-    relabel: bool = True,
-) -> DataFrame:
-    """Generate a table of returns with appropriate table labels."""
-    copied = data.from_deepcopy()
-    copied.resample_to_business_period_ends(freq=freq)
-    copied.value_to_ret()
-    cldr = copied.tsdf.iloc[1:].copy()
-    if relabel:
-        if freq.upper() == "BYE":
-            cldr.index = Index([d.year for d in cldr.index])
-        elif freq.upper() == "BQE":
-            cldr.index = Index(
-                [Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index],
-            )
-        else:
-            cldr.index = Index([d.strftime("%b %y") for d in cldr.index])
-    return cldr
-
-
-def _dumps_plotly(obj: object) -> str:
-    return json_dumps(obj, cls=PlotlyJSONEncoder)
-
-
-def _fmt_dates(idx: Index) -> list[str]:
-    return [Timestamp(d).strftime("%Y-%m-%d") for d in idx]
-
-
-def _metrics_table_html(df: DataFrame) -> str:
-    return df.to_html(index=False, escape=False, classes=["metrics"], border=0)
-
-
-def _get_report_properties_and_labels(
-    yearfrac: float,
-) -> tuple[list[str], list[str], list[str]]:
-    """Get properties and labels based on yearfrac."""
-    if yearfrac > 1.0:
-        properties = [
-            "geo_ret",
-            "vol",
-            "ret_vol_ratio",
-            "sortino_ratio",
-            "worst_month",
-            "first_indices",
-            "last_indices",
-        ]
-        labels_init = [
-            "Return (CAGR)",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Worst Month",
-            "Comparison Start",
-            "Comparison End",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Capture Ratio (monthly)",
-            "Index Beta (weekly)",
-        ]
-        labels_final = [
-            "Return (CAGR)",
-            "Year-to-Date",
-            "Month-to-Date",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Index Beta (weekly)",
-            "Capture Ratio (monthly)",
-            "Worst Month",
-            "Comparison Start",
-            "Comparison End",
-        ]
-    else:
-        properties = [
-            "value_ret",
-            "vol",
-            "ret_vol_ratio",
-            "sortino_ratio",
-            "worst",
-            "first_indices",
-            "last_indices",
-        ]
-        labels_init = [
-            "Return (simple)",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Worst Day",
-            "Comparison Start",
-            "Comparison End",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Index Beta (weekly)",
-        ]
-        labels_final = [
-            "Return (simple)",
-            "Year-to-Date",
-            "Month-to-Date",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Index Beta (weekly)",
-            "Worst Day",
-            "Comparison Start",
-            "Comparison End",
-        ]
-    return properties, labels_init, labels_final
-
-
-def _create_line_traces(data: OpenFrame) -> list[Scatter]:
-    """Create line traces for the plot."""
-    x_line = _fmt_dates(data.tsdf.index)
-    line_traces: list[Scatter] = []
-    for item, lbl in enumerate(data.columns_lvl_zero):
-        line_traces.append(
-            Scatter(
-                x=x_line,
-                y=data.tsdf.iloc[:, item].tolist(),
-                hovertemplate=f"{lbl}<br>%{{y:.2%}}<br>%{{x}}<extra></extra>",
-                line={"width": 2.5, "dash": "solid"},
-                mode="lines",
-                name=lbl,
-                showlegend=True,
-            ),
-        )
-    return line_traces
-
-
-def _create_bar_traces(
-    data: OpenFrame,
-    bar_freq: LiteralBizDayFreq,
-) -> list[Bar]:
-    """Create bar traces for the plot."""
-    quarter_of_year = 0.25
-    if data.yearfrac < quarter_of_year:
-        tmp = data.from_deepcopy()
-        bdf = tmp.value_to_ret().tsdf.iloc[1:]
-    else:
-        bdf = calendar_period_returns(data=data, freq=bar_freq)
-
-    x_bar = [str(x) for x in bdf.index]
-    bar_traces: list[Bar] = []
-    for item in range(data.item_count):
-        col_name = cast("tuple[str, ValueType]", bdf.iloc[:, item].name)
-        bar_traces.append(
-            Bar(
-                x=x_bar,
-                y=bdf.iloc[:, item].tolist(),
-                hovertemplate=f"{col_name[0]}<br>%{{y:.2%}}<br>%{{x}}<extra></extra>",
-                name=col_name[0],
-                showlegend=False,
-            ),
-        )
-    return bar_traces
-
-
-def _add_jensen_alpha(
-    rpt_df: DataFrame,
-    data: OpenFrame,
-) -> DataFrame:
-    """Add Jensen's Alpha to the report dataframe."""
-    alpha_frame = data.from_deepcopy()
-    alpha_frame.to_cumret()
-    with catch_warnings():
-        simplefilter("ignore")
-        alphas: list[str | float] = [
-            alpha_frame.jensen_alpha(
-                asset=(aname, ValueType.PRICE),
-                market=(alpha_frame.columns_lvl_zero[-1], ValueType.PRICE),
-                riskfree_rate=0.0,
-            )
-            for aname in alpha_frame.columns_lvl_zero[:-1]
-        ]
-    alphas.append("")
-    ar = DataFrame(
-        data=alphas,
-        index=data.tsdf.columns,
-        columns=["Jensen's Alpha"],
-    ).T
-    return concat([rpt_df, ar])
-
-
-def _add_information_ratio(
-    rpt_df: DataFrame,
-    data: OpenFrame,
-) -> DataFrame:
-    """Add Information Ratio to the report dataframe."""
-    ir = data.info_ratio_func()
-    ir.name = "Information Ratio"
-    ir.iloc[-1] = None
-    ir_df = ir.to_frame().T
-    return concat([rpt_df, ir_df])
-
-
-def _add_tracking_error(
-    rpt_df: DataFrame,
-    data: OpenFrame,
-) -> DataFrame:
-    """Add Tracking Error to the report dataframe."""
-    te_frame = data.from_deepcopy()
-    te_frame.resample("7D")
-    with catch_warnings():
-        simplefilter("ignore")
-        te: Series[float] | Series[str] = te_frame.tracking_error_func()
-    if te.hasnans:
-        te = Series(
-            data=[""] * te_frame.item_count,
-            index=te_frame.tsdf.columns,
-            name="Tracking Error (weekly)",
-        )
-    else:
-        te.iloc[-1] = None
-        te.name = "Tracking Error (weekly)"
-    te_df = te.to_frame().T
-    return concat([rpt_df, te_df])
-
-
-def _add_capture_ratio(
-    rpt_df: DataFrame,
-    data: OpenFrame,
-    formats: list[str],
-) -> tuple[DataFrame, list[str]]:
-    """Add Capture Ratio to the report dataframe."""
-    crm = data.from_deepcopy()
-    crm.resample("ME")
-    cru_save = Series(
-        data=[""] * crm.item_count,
-        index=crm.tsdf.columns,
-        name="Capture Ratio (monthly)",
-    )
-    with catch_warnings():
-        simplefilter("ignore")
-        try:
-            cru: Series[float] | Series[str] = crm.capture_ratio_func(ratio="both")
-        except ZeroDivisionError as exc:  # pragma: no cover
-            msg = f"Capture ratio calculation error: {exc!s}"  # pragma: no cover
-            logger.warning(msg)  # pragma: no cover
-            cru = cru_save  # pragma: no cover
-    if cru.hasnans:
-        cru = cru_save
-    else:
-        cru.iloc[-1] = None
-        cru.name = "Capture Ratio (monthly)"
-    cru_df = cru.to_frame().T
-    return concat([rpt_df, cru_df]), formats
-
-
-def _add_beta(
-    rpt_df: DataFrame,
-    data: OpenFrame,
-) -> DataFrame:
-    """Add Index Beta to the report dataframe."""
-    beta_frame = data.from_deepcopy()
-    beta_frame.resample("7D").value_nan_handle("drop")
-    beta_frame.to_cumret()
-    betas: list[str | float] = [
-        beta_frame.beta(
-            asset=(bname, ValueType.PRICE),
-            market=(beta_frame.columns_lvl_zero[-1], ValueType.PRICE),
-        )
-        for bname in beta_frame.columns_lvl_zero[:-1]
-    ]
-    betas.append("")
-    br = DataFrame(
-        data=betas,
-        index=data.tsdf.columns,
-        columns=["Index Beta (weekly)"],
-    ).T
-    return concat([rpt_df, br])
-
-
-def _add_ytd_mtd(
-    rpt_df: DataFrame,
-    data: OpenFrame,
-) -> DataFrame:
-    """Add Year-to-Date and Month-to-Date to the report dataframe."""
-    this_year = data.last_idx.year
-    this_month = data.last_idx.month
-    ytd = data.value_ret_calendar_period(year=this_year).map("{:.2%}".format)
-    ytd.name = "Year-to-Date"
-    mtd = data.value_ret_calendar_period(year=this_year, month=this_month).map(
-        "{:.2%}".format,
-    )
-    mtd.name = "Month-to-Date"
-    ytd_df = ytd.to_frame().T
-    mtd_df = mtd.to_frame().T
-    return concat([rpt_df, ytd_df, mtd_df])
-
-
-def _get_output_directory(directory: Path | None) -> Path:
-    """Determine the output directory."""
-    if directory:
-        return Path(directory).resolve()
-    if Path.home().joinpath("Documents").exists():
-        return Path.home() / "Documents"
-    return Path(stack()[1].filename).parent
-
-
-def _get_plotly_layouts(
-    layout_theme: dict[str, Any],
-    colorway: list[str],
-    item_count: int,
-) -> tuple[dict[str, Any], dict[str, Any]]:
-    """Get line and bar layouts for plotly."""
-    line_layout = dict(layout_theme)
-    line_layout.update(
-        {
-            "colorway": colorway[:item_count] if colorway else None,
-            "margin": {"l": 50, "r": 20, "t": 20, "b": 40},
-            "xaxis": {"gridcolor": "#EEEEEE", "automargin": True, "tickangle": -45},
-            "yaxis": {"tickformat": ".2%", "gridcolor": "#EEEEEE", "automargin": True},
-            "showlegend": False,
-        },
-    )
-
-    bar_layout = dict(layout_theme)
-    bar_layout.update(
-        {
-            "barmode": "group",
-            "margin": {"l": 50, "r": 20, "t": 10, "b": 80},
-            "xaxis": {"gridcolor": "#EEEEEE", "automargin": True, "tickangle": -45},
-            "yaxis": {"tickformat": ".2%", "gridcolor": "#EEEEEE", "automargin": True},
-            "showlegend": False,
-        },
-    )
-
-    return line_layout, bar_layout
-
-
-def _get_logo_html(logo: CaptorLogoType, *, add_logo: bool) -> str:
-    """Get logo HTML."""
-    if not add_logo:
-        return ""
-    try:
-        src = cast("dict[str, Any]", logo).get("source", "")
-    except (KeyError, AttributeError, TypeError):
-        src = ""
-    if src:
-        return f'<img src="{src}" alt="Captor" style="height:68px;" />'
-    return "CAPTOR"
-
-
-def _get_legend_html(line_traces: list[Scatter], colorway: list[str]) -> str:
-    """Generate HTML for the legend at the bottom of the page."""
-    legend_items = []
-    color_cycle = cycle(colorway or ["#66725B"])
-    for trace in line_traces:
-        name = trace.name or ""
-        color = next(color_cycle)
-        legend_items.append(
-            f'<div class="legend-item">'
-            f'<div class="legend-color" style="background-color:{color};"></div>'
-            f"<span>{name}</span>"
-            f"</div>",
-        )
-    if legend_items:
-        return f'<div class="legend-container">{"".join(legend_items)}</div>'
-    return ""
-
-
-def _get_css() -> str:
-    """Get CSS styles for the HTML report."""
-    base_css = _get_base_css()
-    return (
-        base_css
-        + """
-    .header{display:grid;grid-template-columns:140px 1fr 140px;gap:12px;
-    align-items:start;}
-    h1{margin:0;text-align:center;font-size:45px;font-weight:800;}
-    .layout{display:grid;grid-template-columns:1.2fr .9fr;
-    grid-template-areas:"charts table";gap:22px;align-items:start;margin-top:12px;}
-    .charts{grid-area:charts;display:grid;grid-template-rows:auto auto;gap:18px;}
-    .table{grid-area:table;}
-    .plot{width:100%;height:380px;}
-    .plot.bar{height:300px;}
-    @media (max-width:980px){
-      .page{padding:24px;padding-bottom:24px;}
-      .header{grid-template-columns:120px 1fr;}
-      h1{font-size:36px;}
-      .layout{grid-template-columns:1fr;grid-template-areas:"table" "charts";gap:16px;}
-      .plot{height:380px;}
-      .plot.bar{height:300px;}
-      table.metrics{table-layout:fixed;width:auto;}
-      table.metrics thead th{min-width:120px;width:120px;white-space:nowrap;}
-      table.metrics thead th:first-child{width:180px;}
-      table.metrics tbody td{min-width:120px;width:120px;}
-      table.metrics tbody td:first-child{width:180px;}
-    }
-    table.metrics{width:100%;border-collapse:separate;border-spacing:0;font-size:12px;
-    border-radius:4px;overflow:hidden;table-layout:fixed;}
-    table.metrics thead th{background:var(--header);color:white;padding:8px 10px;
-    font-weight:700;text-align:center;word-wrap:break-word;word-break:break-word;}
-    table.metrics thead th:first-child{background:var(--header2);text-align:left;
-    width:180px;}
-    table.metrics tbody td{padding:7px 10px;border-bottom:1px solid white;
-    border-right:1px solid white;text-align:center;background:var(--paper);}
-    table.metrics tbody td:first-child{text-align:left;font-weight:600;color:white;
-    background:var(--header);width:180px;}
-    table.metrics tbody td:last-child{background:var(--cell2);}
-    .legend-container{margin-top:24px;padding-top:20px;padding-bottom:16px;
-    display:flex;justify-content:center;flex-wrap:wrap;gap:24px;flex-shrink:0;}
-    .legend-item{display:flex;align-items:center;gap:8px;font-size:14px;}
-    .legend-color{width:20px;height:3px;border-radius:2px;}
-    @media (min-width:981px){
-      html,body{overflow-y:auto;}
-    }
-    """
-    )
-
-
-def _write_html_file(
-    plotfile: Path,
-    html: str,
-    *,
-    auto_open: bool,
-) -> str:
-    """Write HTML file and optionally open it."""
-    plotfile.parent.mkdir(parents=True, exist_ok=True)
-    plotfile.write_text(html, encoding="utf-8")
-    if auto_open:
-        try:
-            webbrowser_open(plotfile.as_uri())
-        except OSError as exc:
-            logger.warning("Failed to open browser: %s", exc)
-    return str(plotfile)
-
-
-def _generate_html(
-    title: str | None,
-    css: str,
-    plotly_script: str,
-    logo_html: str,
-    table_html: str,
-    line_payload: dict[str, Any],
-    bar_payload: dict[str, Any],
-    legend_html: str,
-) -> str:
-    """Generate the HTML string."""
-    return f"""<!doctype html>
-<html lang="sv">
-<head>
-<meta charset="utf-8" />
-<meta name="viewport" content="width=device-width,initial-scale=1" />
-<title>{title or ""}</title>
-<style>{css}</style>
-{plotly_script}
-</head>
-<body>
-<div class="page">
-  <div class="header">
-    <div>{logo_html}</div>
-    <div><h1>{title or ""}</h1></div>
-    <div></div>
-  </div>
-  <div class="layout">
-    <div class="charts">
-      <div id="lineplot" class="plot"></div>
-      <div id="barplot" class="plot bar"></div>
-    </div>
-    <div class="table">{table_html}</div>
-  </div>
-  {legend_html}
-</div>
-<script>
-const line = {_dumps_plotly(line_payload)};
-const bar = {_dumps_plotly(bar_payload)};
-Plotly.newPlot("lineplot", line.data, line.layout, line.config);
-Plotly.newPlot("barplot", bar.data, bar.layout, bar.config);
-window.addEventListener("resize", () => {{
-  Plotly.Plots.resize("lineplot");
-  Plotly.Plots.resize("barplot");
-}});
-</script>
-</body>
-</html>
-"""
-
-
-
-[docs] -def report_html( - data: OpenFrame, - bar_freq: LiteralBizDayFreq = "BYE", - filename: str | None = None, - title: str | None = None, - directory: Path | None = None, - output_type: LiteralPlotlyOutput = "file", - include_plotlyjs: LiteralPlotlyJSlib = "cdn", - *, - auto_open: bool = False, - add_logo: bool = True, - vertical_legend: bool = True, -) -> tuple[Figure, str]: - """Generate a responsive HTML report page with line and bar plots and a table.""" - copied = data.from_deepcopy() - copied.trunc_frame().value_nan_handle().to_cumret() - - properties, labels_init, labels_final = _get_report_properties_and_labels( - copied.yearfrac, - ) - - line_traces = _create_line_traces(copied) - bar_traces = _create_bar_traces(copied, bar_freq) - - rpt_df = copied.all_properties( - properties=cast("list[LiteralFrameProps]", properties), - ) - rpt_df = _add_jensen_alpha(rpt_df, copied) - rpt_df = _add_information_ratio(rpt_df, copied) - rpt_df = _add_tracking_error(rpt_df, copied) - - if copied.yearfrac > 1.0: - rpt_df, _ = _add_capture_ratio(rpt_df, copied, []) - - rpt_df = _add_beta(rpt_df, copied) - rpt_df.index = Index(labels_init) - rpt_df = _add_ytd_mtd(rpt_df, copied) - rpt_df = rpt_df.reindex(labels_final) - - format_map = { - "Return (CAGR)": "{:.2%}", - "Return (simple)": "{:.2%}", - "Year-to-Date": "{:.2%}", - "Month-to-Date": "{:.2%}", - "Volatility": "{:.2%}", - "Sharpe Ratio": "{:.2f}", - "Sortino Ratio": "{:.2f}", - "Jensen's Alpha": "{:.2%}", - "Information Ratio": "{:.2f}", - "Tracking Error (weekly)": "{:.2%}", - "Index Beta (weekly)": "{:.2f}", - "Capture Ratio (monthly)": "{:.2f}", - "Worst Month": "{:.2%}", - "Worst Day": "{:.2%}", - "Comparison Start": "{:%Y-%m-%d}", - "Comparison End": "{:%Y-%m-%d}", - } - formats = [format_map.get(label, "{:.2f}") for label in labels_final] - - for item, f in zip(rpt_df.index, formats, strict=False): - rpt_df.loc[item] = rpt_df.loc[item].apply( - lambda x, fmt=f: ( - "" - if ( - x is None - or (not isinstance(x, str) and isna(x)) - or (isinstance(x, str) and x.lower() in ("nan", "nan%", "")) - ) - else ( - str(x) - if isinstance(x, str) - else ( - Timestamp(x).strftime("%Y-%m-%d") - if "%Y-%m-%d" in fmt and not isinstance(x, str) - else fmt.format(x) - ) - ) - ), - ) - - rpt_df.index = Index([f"<b>{x}</b>" for x in rpt_df.index]) - rpt_df = rpt_df.reset_index() - - colmns = ["", *copied.columns_lvl_zero] - rpt_df.columns = colmns - table_html = _metrics_table_html(rpt_df) - - dirpath = _get_output_directory(directory=directory) - - if not filename: - filename = "".join(choice(ascii_letters) for _ in range(6)) + ".html" - - plotfile = dirpath / filename - - fig_theme, logo = load_plotly_dict() - layout_theme = cast("dict[str, Any]", fig_theme.get("layout", {})) - colorway: list[str] = cast("dict[str, list[str]]", layout_theme).get( - "colorway", [] - ) - - line_layout, bar_layout = _get_plotly_layouts( - layout_theme=layout_theme, - colorway=colorway, - item_count=copied.item_count, - ) - - config = cast("dict[str, Any]", fig_theme.get("config", {})) or {} - config = {**config, "responsive": True, "displayModeBar": False} - - plotly_script = _get_plotly_script(include_plotlyjs=include_plotlyjs) - logo_html = _get_logo_html(logo=logo, add_logo=add_logo) - css = _get_css() - - line_payload = { - "data": [t.to_plotly_json() for t in line_traces], - "layout": line_layout, - "config": config, - } - bar_payload = { - "data": [t.to_plotly_json() for t in bar_traces], - "layout": bar_layout, - "config": config, - } - - if not vertical_legend: - logger.debug("Horizontal legend layout requested.") - legend_html = _get_legend_html(line_traces=line_traces, colorway=colorway) - - html = _generate_html( - title=title, - css=css, - plotly_script=plotly_script, - logo_html=logo_html, - table_html=table_html, - line_payload=line_payload, - bar_payload=bar_payload, - legend_html=legend_html, - ) - - if output_type == "file": - output = _write_html_file(plotfile=plotfile, html=html, auto_open=auto_open) - else: - output = html - - fig_return = Figure(data=line_traces) - return fig_return, output
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/series.html b/docs/build/html/_modules/openseries/series.html deleted file mode 100644 index ccdace82..00000000 --- a/docs/build/html/_modules/openseries/series.html +++ /dev/null @@ -1,1278 +0,0 @@ - - - - - - - - openseries.series — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.series

-"""The OpenTimeSeries class."""
-
-from __future__ import annotations
-
-import datetime as dt
-from copy import deepcopy
-from logging import getLogger
-from typing import TYPE_CHECKING, Any, Self, TypeVar, cast
-
-if TYPE_CHECKING:  # pragma: no cover
-    from numpy.typing import NDArray
-    from pandas import Timestamp
-
-from numpy import (
-    append,
-    array,
-    asarray,
-    cumprod,
-    diff,
-    float64,
-    insert,
-    isnan,
-    log,
-    sqrt,
-    square,
-)
-from pandas import (
-    DataFrame,
-    DatetimeIndex,
-    Index,
-    MultiIndex,
-    Series,
-    date_range,
-)
-from pydantic import field_validator, model_validator
-from scipy.stats import chi2, norm
-
-from ._common_model import (
-    _calculate_time_factor,
-    _CommonModel,
-    _demeaned_returns_for_autocorr,
-)
-from .datefixer import _do_resample_to_business_period_ends, date_fix
-from .owntypes import (
-    Countries,
-    CountriesType,
-    Currency,
-    CurrencyStringType,
-    DateAlignmentError,
-    DateListType,
-    DaysInYearType,
-    IncorrectArgumentComboError,
-    LiteralBizDayFreq,
-    LiteralPandasReindexMethod,
-    LiteralSeriesProps,
-    MarketsNotStringNorListStrError,
-    OpenTimeSeriesPropertiesList,
-    ResampleDataLossError,
-    ValueListType,
-    ValueType,
-)
-
-logger = getLogger(__name__)
-
-__all__ = ["OpenTimeSeries", "timeseries_chain"]
-
-TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
-
-
-
-[docs] -class OpenTimeSeries(_CommonModel[float]): - """OpenTimeSeries objects are at the core of the openseries package. - - The intended use is to allow analyses of financial timeseries. - It is only intended for daily or less frequent data samples. - - Args: - timeseries_id: Database identifier of the timeseries. - instrument_id: Database identifier of the instrument associated with - the timeseries. - name: String identifier of the timeseries and/or instrument. - valuetype: Identifies if the series is a series of values or returns. - dates: Dates of the individual timeseries items. - These dates will not be altered by methods. - values: The value or return values of the timeseries items. - These values will not be altered by methods. - local_ccy: Boolean flag indicating if timeseries is in local currency. - tsdf: Pandas object holding dates and values that can be altered via - methods. - currency: ISO 4217 currency code of the timeseries. - domestic: ISO 4217 currency code of the user's home currency. - Defaults to "SEK". - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - Optional. - isin: ISO 6166 identifier code of the associated instrument. Optional. - label: Placeholder for a name of the timeseries. Optional. - """ - - timeseries_id: str - instrument_id: str - name: str - valuetype: ValueType - dates: DateListType - values: ValueListType - local_ccy: bool - tsdf: DataFrame - currency: CurrencyStringType - domestic: CurrencyStringType = "SEK" - countries: CountriesType = "SE" - isin: str | None = None - label: str | None = None - - @field_validator("domestic", mode="before") - @classmethod - def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType: - """Pydantic validator to ensure domestic field is validated.""" - Currency(ccy=value) - return value - - @field_validator("countries", mode="before") - @classmethod - def _validate_countries(cls, value: CountriesType) -> CountriesType: - """Pydantic validator to ensure countries field is validated.""" - Countries(countryinput=value) - return value - - @field_validator("markets", mode="before") - @classmethod - def _validate_markets( - cls, - value: list[str] | str | None, - ) -> list[str] | str | None: - """Pydantic validator to ensure markets field is validated. - - Raises: - MarketsNotStringNorListStrError: If ``markets`` is neither a string - nor a non-empty list of strings. - """ - msg = ( - "'markets' must be a string or list of strings, " - f"got {type(value).__name__!r}" - ) - if value is None or isinstance(value, str): - return value - if isinstance(value, list): - if all(isinstance(item, str) for item in value) and len(value) != 0: - return value - item_msg = "All items in 'markets' must be strings." - raise MarketsNotStringNorListStrError(item_msg) - raise MarketsNotStringNorListStrError(msg) - - @model_validator(mode="after") - def _dates_and_values_validate(self: Self) -> Self: - """Pydantic validator to ensure dates and values are validated. - - Raises: - ValueError: If dates are not unique or if numbers of dates and values - do not match the shape of ``tsdf``. - """ - values_list_length = len(self.values) - dates_list_length = len(self.dates) - dates_set_length = len(set(self.dates)) - if dates_list_length != dates_set_length: - msg = "Dates are not unique" - raise ValueError(msg) - if ( - (dates_list_length != values_list_length) - or (len(self.tsdf.index) != self.tsdf.shape[0]) - or (self.tsdf.shape[1] != 1) - ): - msg = "Number of dates and values passed do not match" - raise ValueError(msg) - return self - - def _coerce_result( - self: Self, - result: Series[float], - name: str, - ) -> float: - _ = name - return float(asarray(a=result, dtype=float64).squeeze()) - -
-[docs] - @classmethod - def from_arrays( - cls, - name: str, - dates: DateListType, - values: ValueListType, - valuetype: ValueType = ValueType.PRICE, - timeseries_id: str = "", - instrument_id: str = "", - isin: str | None = None, - baseccy: CurrencyStringType = "SEK", - *, - local_ccy: bool = True, - ) -> Self: - """Create series from a list of dates and a list of values. - - Args: - name: String identifier of the timeseries and/or instrument. - dates: List of date strings as ISO 8601 YYYY-MM-DD. - values: Array of float values. - valuetype: Identifies if the series is a series of values or returns. - Defaults to ValueType.PRICE. - timeseries_id: Database identifier of the timeseries. Optional. - instrument_id: Database identifier of the instrument associated - with the timeseries. Optional. - isin: ISO 6166 identifier code of the associated instrument. Optional. - baseccy: ISO 4217 currency code of the timeseries. Defaults to "SEK". - local_ccy: Boolean flag indicating if timeseries is in local currency. - Defaults to True. - - Returns: - An OpenTimeSeries object. - """ - return cls( - name=name, - label=name, - dates=dates, - values=values, - valuetype=valuetype, - timeseries_id=timeseries_id, - instrument_id=instrument_id, - isin=isin, - currency=baseccy, - local_ccy=local_ccy, - tsdf=DataFrame( - data=values, - index=[deyt.date() for deyt in DatetimeIndex(dates)], - columns=[[name], [valuetype]], - dtype="float64", - ), - )
- - -
-[docs] - @classmethod - def from_df( - cls, - dframe: Series | DataFrame | object, - column_nmbr: int = 0, - valuetype: ValueType = ValueType.PRICE, - baseccy: CurrencyStringType = "SEK", - *, - local_ccy: bool = True, - ) -> Self: - """Create series from a Pandas DataFrame or Series. - - Args: - dframe: Pandas DataFrame or Series. - column_nmbr: Using iloc[:, column_nmbr] to pick column. Defaults to 0. - valuetype: Identifies if the series is a series of values or returns. - Defaults to ValueType.PRICE. - baseccy: ISO 4217 currency code of the timeseries. Defaults to "SEK". - local_ccy: Boolean flag indicating if timeseries is in local currency. - Defaults to True. - - Returns: - An OpenTimeSeries object. - - Raises: - TypeError: If ``dframe`` is not a ``pandas.Series`` or a - ``pandas.DataFrame``. - """ - msg = "Argument dframe must be pandas Series or DataFrame." - values: list[float] - pandas_obj: Series | DataFrame - if isinstance(dframe, Series): - pandas_obj = dframe - if isinstance(dframe.name, tuple): - label, _ = dframe.name - else: - label = dframe.name - values = dframe.to_numpy().tolist() - elif isinstance(dframe, DataFrame): - pandas_obj = dframe - values = dframe.iloc[:, column_nmbr].to_list() - if isinstance(dframe.columns, MultiIndex): - if _check_if_none( - dframe.columns.get_level_values(0).to_numpy()[column_nmbr], - ): - label = "Series" - msg = f"Label missing. Adding: {label}" - logger.warning(msg) - else: - label = dframe.columns.get_level_values(0).to_numpy()[column_nmbr] - if _check_if_none( - dframe.columns.get_level_values(1).to_numpy()[column_nmbr], - ): - valuetype = ValueType.PRICE - msg = f"valuetype missing. Adding: {valuetype.value}" - logger.warning(msg) - else: - valuetype = dframe.columns.get_level_values(1).to_numpy()[ - column_nmbr - ] - else: - label = dframe.columns.to_numpy()[column_nmbr] - else: - raise TypeError(msg) - - dates = [date_fix(d).strftime("%Y-%m-%d") for d in pandas_obj.index] - - return cls( - timeseries_id="", - instrument_id="", - currency=baseccy, - dates=dates, - name=label, - label=label, - valuetype=valuetype, - values=values, - local_ccy=local_ccy, - tsdf=DataFrame( - data=values, - index=[deyt.date() for deyt in DatetimeIndex(dates)], - columns=[[label], [valuetype]], - dtype="float64", - ), - )
- - -
-[docs] - @classmethod - def from_fixed_rate( - cls, - rate: float, - d_range: DatetimeIndex | None = None, - days: int | None = None, - end_dt: dt.date | None = None, - label: str = "Series", - valuetype: ValueType = ValueType.PRICE, - baseccy: CurrencyStringType = "SEK", - *, - local_ccy: bool = True, - ) -> Self: - """Create series from values accruing with a given fixed rate return. - - Providing a date_range of type Pandas DatetimeIndex takes priority over - providing a combination of days and an end date. - - Args: - rate: The accrual rate. - d_range: A given range of dates. Optional. - days: Number of days to generate when date_range not provided. Must be - combined with end_dt. Optional. - end_dt: End date of date range to generate when date_range not provided. - Must be combined with days. Optional. - label: Placeholder for a name of the timeseries. - valuetype: Identifies if the series is a series of values or returns. - Defaults to ValueType.PRICE. - baseccy: The currency of the timeseries. Defaults to "SEK". - local_ccy: Boolean flag indicating if timeseries is in local currency. - Defaults to True. - - Returns: - An OpenTimeSeries object. - - Raises: - IncorrectArgumentComboError: If ``d_range`` is not provided and the - combination of ``days`` and ``end_dt`` is incomplete. - """ - if d_range is None: - if days is not None and end_dt is not None: - d_range = DatetimeIndex( - [d.date() for d in date_range(periods=days, end=end_dt, freq="D")], - ) - else: - msg = "If d_range is not provided both days and end_dt must be." - raise IncorrectArgumentComboError(msg) - deltas = array([i.days for i in d_range[1:] - d_range[:-1]]) - arr: list[float] = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0))) - dates = [d.strftime("%Y-%m-%d") for d in d_range] - - return cls( - timeseries_id="", - instrument_id="", - currency=baseccy, - dates=dates, - name=label, - label=label, - valuetype=valuetype, - values=arr, - local_ccy=local_ccy, - tsdf=DataFrame( - data=arr, - index=[d.date() for d in DatetimeIndex(dates)], - columns=[[label], [valuetype]], - dtype="float64", - ), - )
- - -
-[docs] - def from_deepcopy(self: Self) -> Self: - """Create copy of OpenTimeSeries object. - - Returns: - An OpenTimeSeries object. - """ - return deepcopy(self)
- - -
-[docs] - def pandas_df(self: Self) -> Self: - """Populate .tsdf Pandas DataFrame from the .dates and .values lists. - - Returns: - An OpenTimeSeries object. - """ - dframe = DataFrame( - data=self.values, - index=[d.date() for d in DatetimeIndex(self.dates)], - columns=[[self.label], [self.valuetype]], - dtype="float64", - ) - self.tsdf = dframe - - return self
- - -
-[docs] - def all_properties( - self: Self, - properties: list[LiteralSeriesProps] | None = None, - ) -> DataFrame: - """Calculate chosen properties. - - Args: - properties: The properties to calculate. Defaults to calculating all - available. Optional. - - Returns: - Properties of the OpenTimeSeries. - """ - if not properties: - properties = cast( - "list[LiteralSeriesProps]", - OpenTimeSeriesPropertiesList.allowed_strings, - ) - - props = OpenTimeSeriesPropertiesList(*properties) - - def _prop_value(name: str) -> float | int | dt.date | Series[float]: - attr = getattr(self, name) - return cast( - "float | int | dt.date | Series[float]", - attr() if callable(attr) else attr, - ) - - pdf = DataFrame.from_dict( - {x: _prop_value(x) for x in props}, - orient="index", - ) - pdf.columns = self.tsdf.columns - return pdf
- - -
-[docs] - def value_to_ret(self: Self) -> Self: - """Convert series of values into series of returns. - - Returns: - The returns of the values in the series. - """ - returns = self.tsdf.ffill().pct_change() - returns.iloc[0] = 0 - self.valuetype = ValueType.RTRN - arrays = cast("Any", [[self.label], [self.valuetype]]) - returns.columns = MultiIndex.from_arrays(arrays) - self.tsdf = returns.copy() - return self
- - -
-[docs] - def value_to_diff(self: Self, periods: int = 1) -> Self: - """Convert series of values to series of their period differences. - - Args: - periods: The number of periods between observations over which difference - is calculated. Defaults to 1. - - Returns: - An OpenTimeSeries object. - """ - self.tsdf = self.tsdf.diff(periods=periods) - self.tsdf.iloc[0] = 0 - self.valuetype = ValueType.RTRN - self.tsdf.columns = MultiIndex.from_arrays( - [ - [self.label], - [self.valuetype], - ], - ) - return self
- - -
-[docs] - def to_cumret(self: Self) -> Self: - """Convert series of returns into cumulative series of values. - - Returns: - An OpenTimeSeries object. - """ - if self.valuetype == ValueType.PRICE: - self.value_to_ret() - - self.tsdf = self.tsdf.add(1.0) - self.tsdf = self.tsdf.cumprod(axis=0) / self.tsdf.iloc[0] - - self.valuetype = ValueType.PRICE - self.tsdf.columns = MultiIndex.from_arrays( - [ - [self.label], - [self.valuetype], - ], - ) - return self
- - -
-[docs] - def from_1d_rate_to_cumret( - self: Self, - days_in_year: int = 365, - divider: float = 1.0, - ) -> Self: - """Convert series of 1-day rates into series of cumulative values. - - Args: - days_in_year: Calendar days per year used as divisor. Defaults to 365. - divider: Convenience divider for when the 1-day rate is not scaled - correctly. Defaults to 1.0. - - Returns: - An OpenTimeSeries object. - """ - arr: NDArray[float64] = array(self.values) / divider - - deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]]) - arr = cast( - "NDArray[float64]", - cumprod( - a=insert( - arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0 - ), - ), - ) - - self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index] - self.values = list(arr) - self.valuetype = ValueType.PRICE - self.tsdf = DataFrame( - data=self.values, - index=[d.date() for d in DatetimeIndex(self.dates)], - columns=[[self.label], [self.valuetype]], - dtype="float64", - ) - - return self
- - -
-[docs] - def resample( - self: Self, - freq: LiteralBizDayFreq | str = "BME", - ) -> Self: - """Resamples the timeseries frequency. - - Args: - freq: The date offset string that sets the resampled frequency. - Defaults to "BME". - - Returns: - An OpenTimeSeries object. - """ - self.tsdf.index = DatetimeIndex(self.tsdf.index) - if self.valuetype == ValueType.RTRN: - self.tsdf = self.tsdf.resample(freq).sum() - else: - self.tsdf = self.tsdf.resample(freq).last() - self.tsdf.index = Index(DatetimeIndex(self.tsdf.index).date) - return self
- - -
-[docs] - def resample_to_business_period_ends( - self: Self, - freq: LiteralBizDayFreq = "BME", - method: LiteralPandasReindexMethod = "nearest", - ) -> Self: - """Resamples timeseries frequency to the business calendar month end dates. - - Stubs left in place. Stubs will be aligned to the shortest stub. - - Args: - freq: The date offset string that sets the resampled frequency. - Defaults to BME. - method: Controls the method used to align values across columns. - Defaults to nearest. - - Returns: - An OpenTimeSeries object. - - Raises: - ResampleDataLossError: If called on a return series (``valuetype`` is - ``ValueType.RTRN``), since summation across sparser frequency would - be required to avoid data loss. - """ - if self.valuetype == ValueType.RTRN: - msg = ( - "Do not run resample_to_business_period_ends on return series. " - "The operation will pick the last data point in the sparser series. " - "It will not sum returns and therefore data will be lost." - ) - raise ResampleDataLossError(msg) - - dates = _do_resample_to_business_period_ends( - data=self.tsdf, - freq=freq, - countries=self.countries, - markets=self.markets, - ) - self.tsdf = self.tsdf.reindex([deyt.date() for deyt in dates], method=method) - return self
- - -
-[docs] - def ewma_vol_func( - self: Self, - lmbda: float = 0.94, - day_chunk: int = 11, - dlta_degr_freedms: int = 0, - months_from_last: int | None = None, - from_date: dt.date | None = None, - to_date: dt.date | None = None, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> Series[float]: - """Exponentially Weighted Moving Average Model for Volatility. - - Reference: https://www.investopedia.com/articles/07/ewma.asp. - - Args: - lmbda: Scaling factor to determine weighting. Defaults to 0.94. - day_chunk: Sampling the data which is assumed to be daily. - Defaults to 11. - dlta_degr_freedms: Variance bias factor taking the value 0 or 1. - Defaults to 0. - months_from_last: Number of months offset as positive integer. - Overrides use of from_date and to_date. Optional. - from_date: Specific from date. Optional. - to_date: Specific to date. Optional. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Series EWMA volatility. - """ - earlier, later = self.calc_range( - months_offset=months_from_last, - from_dt=from_date, - to_dt=to_date, - ) - time_factor = _calculate_time_factor( - data=self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ].iloc[:, 0], - earlier=earlier, - later=later, - periods_in_a_year_fixed=periods_in_a_year_fixed, - ) - - data = self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ].copy() - - data.loc[:, (self.label, ValueType.RTRN)] = log( - data.loc[:, self.tsdf.columns.to_numpy()[0]], - ).diff() - - rawdata = [ - data[(self.label, ValueType.RTRN)] - .iloc[1:day_chunk] - .std(ddof=dlta_degr_freedms) - * sqrt(time_factor), - ] - - for item in data[(self.label, ValueType.RTRN)].iloc[1:]: - prev = rawdata[-1] - rawdata.append( - sqrt( - square(item) * time_factor * (1 - lmbda) + square(prev) * lmbda, - ), - ) - - return Series( - data=rawdata, - index=data.index, - name=(self.label, ValueType.EWMA_VOL), - dtype="float64", - )
- - -
-[docs] - def ewma_var_func( - self: Self, - lmbda: float = 0.94, - day_chunk: int = 11, - level: float = 0.95, - dlta_degr_freedms: int = 0, - months_from_last: int | None = None, - from_date: dt.date | None = None, - to_date: dt.date | None = None, - periods_in_a_year_fixed: DaysInYearType | None = None, - ) -> Series[float]: - """Exponentially Weighted Moving Average Model for Value At Risk (VaR). - - Reference: https://www.investopedia.com/articles/07/ewma.asp. - - Args: - lmbda: Scaling factor to determine weighting. Defaults to 0.94. - day_chunk: Sampling the data which is assumed to be daily. - Defaults to 11. - level: The sought VaR level. Defaults to 0.95. - dlta_degr_freedms: Variance bias factor taking the value 0 or 1. - Defaults to 0. - months_from_last: Number of months offset as positive integer. - Overrides use of from_date and to_date. Optional. - from_date: Specific from date. Optional. - to_date: Specific to date. Optional. - periods_in_a_year_fixed: Allows locking the periods-in-a-year to simplify - test cases and comparisons. Optional. - - Returns: - Series EWMA VaR. - """ - earlier, later = self.calc_range( - months_offset=months_from_last, - from_dt=from_date, - to_dt=to_date, - ) - time_factor = _calculate_time_factor( - data=self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ].iloc[:, 0], - earlier=earlier, - later=later, - periods_in_a_year_fixed=periods_in_a_year_fixed, - ) - - data = self.tsdf.loc[ - cast("Timestamp", earlier) : cast("Timestamp", later) - ].copy() - - data.loc[:, (self.label, ValueType.RTRN)] = log( - data.loc[:, self.tsdf.columns.to_numpy()[0]], - ).diff() - - rawdata = [ - data[(self.label, ValueType.RTRN)] - .iloc[1:day_chunk] - .std(ddof=dlta_degr_freedms) - * sqrt(time_factor), - ] - - for item in data[(self.label, ValueType.RTRN)].iloc[1:]: - prev = rawdata[-1] - rawdata.append( - sqrt( - square(item) * time_factor * (1 - lmbda) + square(prev) * lmbda, - ), - ) - - return Series( - data=array(rawdata) * norm.ppf(1 - level), - index=data.index, - name=(self.label, ValueType.EWMA_VAR), - dtype="float64", - )
- - -
-[docs] - def running_adjustment( - self: Self, - adjustment: float, - days_in_year: int = 365, - ) -> Self: - """Add or subtract a fee from the timeseries return. - - Args: - adjustment: Fee to add or subtract. - days_in_year: The calculation divisor and assumed number of days in a - calendar year. Defaults to 365. - - Returns: - An OpenTimeSeries object. - """ - if self.valuetype == ValueType.RTRN: - ra_df = self.tsdf.copy() - initial_value = 1.0 - returns_input = True - else: - initial_value = cast("float", self.tsdf.iloc[0, 0]) - ra_df = self.tsdf.ffill().pct_change() - returns_input = False - ra_df = ra_df.dropna() - - dates_index = DatetimeIndex(ra_df.index) - dates_list = [self.first_idx] + [d.date() for d in dates_index] - - dates_np = array( - [dt.datetime.combine(d, dt.time()) for d in dates_list], - dtype="datetime64[D]", - ) - date_diffs = cast( - "NDArray[float64]", - diff(dates_np).astype("timedelta64[D]").astype(float64), - ) - - returns_array = cast( - "NDArray[float64]", - ra_df.iloc[:, 0].to_numpy(), - ) - - adjustment_factors = ( - 1.0 + returns_array + adjustment * date_diffs / days_in_year - ) - - values_array = cumprod(insert(adjustment_factors, 0, initial_value)) - values = list(values_array) - - self.tsdf = DataFrame(data=values, index=dates_list) - self.valuetype = ValueType.PRICE - self.tsdf.columns = MultiIndex.from_arrays( - [ - [self.label], - [self.valuetype], - ], - ) - self.tsdf.index = Index(DatetimeIndex(self.tsdf.index).date) - if returns_input: - self.value_to_ret() - return self
- - -
-[docs] - def set_new_label( - self: Self, - lvl_zero: str | None = None, - lvl_one: ValueType | None = None, - *, - delete_lvl_one: bool = False, - ) -> Self: - """Set the column labels of the .tsdf Pandas Dataframe. - - Args: - lvl_zero: New level zero label. Optional. - lvl_one: New level one label. Optional. - delete_lvl_one: If True the level one label is deleted. Defaults to False. - - Returns: - An OpenTimeSeries object. - """ - if lvl_zero is None and lvl_one is None: - self.tsdf.columns = MultiIndex.from_arrays( - [[self.label], [self.valuetype]], - ) - elif lvl_zero is not None and lvl_one is None: - self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [self.valuetype]]) - self.label = lvl_zero - elif lvl_zero is None and lvl_one is not None: - self.tsdf.columns = MultiIndex.from_arrays([[self.label], [lvl_one]]) - self.valuetype = lvl_one - else: - self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [lvl_one]]) - self.label, self.valuetype = lvl_zero, cast("ValueType", lvl_one) - if delete_lvl_one: - self.tsdf.columns = self.tsdf.columns.get_level_values(0) - return self
- - - def _returns_series(self: Self, *, squared: bool = False) -> Series[float]: - """Return demeaned return series for autocorrelation analysis.""" - data: Series[float] = self.tsdf.iloc[:, 0] - return _demeaned_returns_for_autocorr( - series=data, valuetype=self.valuetype, squared=squared - ) - -
-[docs] - def acf( - self: Self, - lags: int | list[int], - *, - squared: bool = False, - ) -> Series[float]: - """Calculate autocorrelation function for specified lags. - - Args: - lags: If int, compute ACF from lag 0 to this value (inclusive). - If list, compute ACF at lag 0 plus each lag in the list. - squared: If True, compute ACF of squared returns. Defaults to False. - - Returns: - Series of autocorrelations indexed by lag. - """ - rets = self._returns_series(squared=squared) - if isinstance(lags, int): - lag_list = list(range(lags + 1)) - else: - lag_list = sorted({0} | set(lags)) - values: list[float] = [] - for lag in lag_list: - if lag == 0: - values.append(1.0) - else: - values.append(float(rets.autocorr(lag=lag))) - return Series( - data=values, - index=lag_list, - name="ACF", - dtype="float64", - )
- - -
-[docs] - def partial_autocorr(self: Self, lag: int = 1, *, squared: bool = False) -> float: - """Calculate partial autocorrelation at a given lag. - - Args: - lag: The lag at which to compute partial autocorrelation. Defaults to 1. - squared: If True, compute partial autocorrelation of squared returns. - Defaults to False. - - Returns: - Partial autocorrelation at the specified lag. - """ - pacf_series = self.pacf(lags=lag, squared=squared) - return float(pacf_series.loc[lag])
- - -
-[docs] - def pacf( - self: Self, - lags: int | list[int], - *, - squared: bool = False, - ) -> Series[float]: - """Calculate partial autocorrelation function for specified lags. - - Args: - lags: If int, compute PACF from lag 0 to this value (inclusive). - If list, compute PACF at lag 0 plus each lag in the list. - squared: If True, compute PACF of squared returns. Defaults to False. - - Returns: - Series of partial autocorrelations indexed by lag. - """ - if isinstance(lags, int): - lag_list = list(range(lags + 1)) - else: - lag_list = sorted({0} | set(lags)) - max_lag = max(lag_list) if lag_list else 0 - acf_vals = self.acf(lags=max_lag, squared=squared) - acf_arr = array([acf_vals.loc[k] for k in range(max_lag + 1)]) - pacf_values: list[float] = [1.0] - phi: list[list[float]] = [] - for k in range(1, max_lag + 1): - if k == 1: - phi_kk = acf_arr[1] - else: - numer = acf_arr[k] - denom = 1.0 - for j in range(k - 1): - numer -= phi[k - 2][j] * acf_arr[k - 1 - j] - denom -= phi[k - 2][j] * acf_arr[j + 1] - phi_kk = numer / denom - phi_row = [0.0] * k - for j in range(k - 1): - phi_row[j] = phi[k - 2][j] - phi_kk * phi[k - 2][k - 2 - j] - phi_row[k - 1] = phi_kk - phi.append(phi_row) - pacf_values.append(phi_kk) - result = {lag: pacf_values[lag] for lag in lag_list} - return Series( - data=[result[lag] for lag in lag_list], - index=lag_list, - name="PACF", - dtype="float64", - )
- - -
-[docs] - def ljung_box( - self: Self, - lags: int | list[int], - *, - squared: bool = False, - ) -> tuple[float, float, list[int]]: - """Compute Ljung-Box test for autocorrelation. - - Args: - lags: If int, use lags 1 through this value. If list, use the given - lags (lag 0 excluded from test). - squared: If True, test autocorrelation of squared returns. - Defaults to False. - - Returns: - Tuple of (statistic, pvalue, lags) where statistic is the Ljung-Box - Q statistic, pvalue is the chi-squared p-value, and lags is the - list of lags used. - """ - rets = self._returns_series(squared=squared) - n = len(rets) - if isinstance(lags, int): - lag_list = list(range(1, lags + 1)) - else: - lag_list = sorted({k for k in lags if k > 0}) - if not lag_list: - return 0.0, 1.0, [] - r_k_sq_sum = 0.0 - for k in lag_list: - if k < n: - r_k = float(rets.autocorr(lag=k)) - r_k_sq_sum += r_k**2 / (n - k) - q_stat = n * (n + 2) * r_k_sq_sum - df = len(lag_list) - pval = float(1.0 - chi2.cdf(q_stat, df)) - return q_stat, pval, lag_list
-
- - - -
-[docs] -def timeseries_chain( - front: TypeOpenTimeSeries, - back: TypeOpenTimeSeries, - old_fee: float = 0.0, -) -> TypeOpenTimeSeries: - """Chain two timeseries together. - - The function assumes that the two series have at least one date in common. - - Args: - front: Earlier series to chain with. - back: Later series to chain with. - old_fee: Fee to apply to earlier series. Defaults to 0.0. - - Returns: - An OpenTimeSeries object or a subclass thereof. - """ - old = front.from_deepcopy() - old.running_adjustment(old_fee) - new = back.from_deepcopy() - idx = 0 - first = new.tsdf.index[idx] - - if old.last_idx < first: - msg = "Timeseries dates must overlap to allow them to be chained." - raise DateAlignmentError(msg) - - while first not in old.tsdf.index: - idx += 1 - first = new.tsdf.index[idx] - if first > old.tsdf.index[-1]: - msg = "Failed to find a matching date between series" - raise DateAlignmentError(msg) - - dates: list[str] = [x.strftime("%Y-%m-%d") for x in old.tsdf.index if x < first] - - old_values = Series(old.tsdf.iloc[: len(dates), 0]) - old_values = old_values.mul( - Series(new.tsdf.iloc[:, 0]).loc[first] - / Series(old.tsdf.iloc[:, 0]).loc[first], - ) - values = append(old_values, new.tsdf.iloc[:, 0]) - - dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index]) - - return back.__class__( - timeseries_id=new.timeseries_id, - instrument_id=new.instrument_id, - currency=new.currency, - dates=dates, - name=new.name, - label=new.name, - valuetype=new.valuetype, - values=list(values), - local_ccy=new.local_ccy, - tsdf=DataFrame( - data=values, - index=[d.date() for d in DatetimeIndex(dates)], - columns=[[new.label], [new.valuetype]], - dtype="float64", - ), - )
- - - -def _check_if_none(item: object) -> bool: - """Check if a variable is None or equivalent. - - Args: - item: Variable to be checked. - - Returns: - Answer to whether the variable is None or equivalent. - """ - if item is None: - return True - - try: - return cast("bool", isnan(cast("float", item))) - except (TypeError, ValueError): - return len(str(item)) == 0 -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_modules/openseries/simulation.html b/docs/build/html/_modules/openseries/simulation.html deleted file mode 100644 index 3e75c106..00000000 --- a/docs/build/html/_modules/openseries/simulation.html +++ /dev/null @@ -1,673 +0,0 @@ - - - - - - - - openseries.simulation — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for openseries.simulation

-"""The ReturnSimulation class."""
-
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, Self, TypedDict, cast
-
-try:
-    from typing import Unpack
-except ImportError:  # pragma: no cover
-    from typing_extensions import Unpack
-
-if TYPE_CHECKING:
-    import datetime as dt  # pragma: no cover
-
-from numpy import multiply, sqrt
-from numpy.random import PCG64, Generator, SeedSequence
-from pandas import (
-    DataFrame,
-    Index,
-    MultiIndex,
-    concat,
-)
-from pydantic import (
-    BaseModel,
-    ConfigDict,
-    NonNegativeFloat,
-    PositiveFloat,
-    PositiveInt,
-)
-
-from .datefixer import generate_calendar_date_range
-from .owntypes import (
-    CountriesType,
-    DaysInYearType,
-    ValueType,
-)
-
-__all__ = ["ReturnSimulation"]
-
-
-class _JumpParams(TypedDict, total=False):
-    """TypedDict for jump diffusion parameters."""
-
-    jumps_lamda: NonNegativeFloat
-    jumps_sigma: NonNegativeFloat
-    jumps_mu: float
-
-
-def _validate_ar1_coef(ar1_coef: float) -> None:
-    """Validate ar1_coef is in (-1, 1) for stationarity."""
-    if not -1.0 < ar1_coef < 1.0:
-        msg = f"ar1_coef must be in (-1, 1) for stationarity, got {ar1_coef}"
-        raise ValueError(msg)
-
-
-def _apply_ar1_filter(returns: DataFrame, ar1_coef: float) -> DataFrame:
-    """Apply AR(1) filter to returns to introduce lag-1 autocorrelation.
-
-    r_t = ar1_coef * r_{t-1} + sqrt(1 - ar1_coef**2) * innovation_t
-    Preserves mean and variance of the base process.
-
-    Args:
-        returns: DataFrame of shape (number_of_sims, trading_days).
-        ar1_coef: Lag-1 autocorrelation coefficient in (-1, 1).
-
-    Returns:
-        Filtered returns.
-    """
-    if ar1_coef == 0.0:
-        return returns
-    arr = returns.to_numpy(copy=True)
-    scale = sqrt(1.0 - ar1_coef * ar1_coef)
-    for t in range(1, arr.shape[1]):
-        arr[:, t] = ar1_coef * arr[:, t - 1] + scale * arr[:, t]
-    return DataFrame(data=arr, dtype="float64")
-
-
-def _random_generator(seed: int | None) -> Generator:
-    """Make a Numpy Random Generator object.
-
-    Args:
-        seed: Random seed.
-
-    Returns:
-        Numpy random process generator.
-    """
-    ss = SeedSequence(entropy=seed)
-    bg = PCG64(seed=cast("int | None", ss))
-    return Generator(bit_generator=bg)
-
-
-def _create_base_simulation(
-    cls: type[ReturnSimulation],
-    returns: DataFrame,
-    number_of_sims: PositiveInt,
-    trading_days: PositiveInt,
-    trading_days_in_year: DaysInYearType,
-    mean_annual_return: float,
-    mean_annual_vol: PositiveFloat,
-    seed: int | None = None,
-    **kwargs: Unpack[_JumpParams],
-) -> ReturnSimulation:
-    """Common logic for creating simulations.
-
-    Args:
-        cls: The ReturnSimulation class.
-        returns: The calculated returns data.
-        number_of_sims: Number of simulations to generate.
-        trading_days: Number of trading days to simulate.
-        trading_days_in_year: Number of trading days used to annualize.
-        mean_annual_return: Mean annual return.
-        mean_annual_vol: Mean annual volatility.
-        seed: Seed for random process initiation.
-        **kwargs: Additional keyword arguments for jump parameters.
-
-    Returns:
-        A ReturnSimulation instance.
-    """
-    return cls(
-        number_of_sims=number_of_sims,
-        trading_days=trading_days,
-        trading_days_in_year=trading_days_in_year,
-        mean_annual_return=mean_annual_return,
-        mean_annual_vol=mean_annual_vol,
-        dframe=returns,
-        seed=seed,
-        **kwargs,
-    )
-
-
-
-[docs] -class ReturnSimulation(BaseModel): - """The class ReturnSimulation allows for simulating financial timeseries. - - Args: - number_of_sims: Number of simulations to generate. - trading_days: Total number of days to simulate. - trading_days_in_year: Number of trading days used to annualize. - mean_annual_return: Mean annual return of the distribution. - mean_annual_vol: Mean annual standard deviation of the distribution. - dframe: Pandas DataFrame object holding the resulting values. - jumps_lamda: This is the probability of a jump happening at each point in time. - Defaults to 0.0. - jumps_sigma: This is the volatility of the jump size. Defaults to 0.0. - jumps_mu: This is the average jump size. Defaults to 0.0. - seed: Seed for random process initiation. - - """ - - number_of_sims: PositiveInt - trading_days: PositiveInt - trading_days_in_year: DaysInYearType - mean_annual_return: float - mean_annual_vol: PositiveFloat - dframe: DataFrame - jumps_lamda: NonNegativeFloat = 0.0 - jumps_sigma: NonNegativeFloat = 0.0 - jumps_mu: float = 0.0 - seed: int | None = None - - model_config = ConfigDict( - arbitrary_types_allowed=True, - validate_assignment=True, - revalidate_instances="always", - ) - -
-[docs] - @cached_property - def results(self: Self) -> DataFrame: - """Simulation data. - - Returns: - Simulation data. - """ - return self.dframe.add(1.0).cumprod(axis="columns").T
- - - @property - def realized_mean_return(self: Self) -> float: - """Annualized arithmetic mean of returns. - - Returns: - Annualized arithmetic mean of returns. - """ - return cast( - "float", - ( - self.results.ffill().pct_change().mean() * self.trading_days_in_year - ).iloc[0], - ) - - @property - def realized_vol(self: Self) -> float: - """Annualized volatility. - - Returns: - Annualized volatility. - """ - return cast( - "float", - ( - self.results.ffill().pct_change().std() - * sqrt(self.trading_days_in_year) - ).iloc[0], - ) - -
-[docs] - @classmethod - def from_normal( - cls: type[ReturnSimulation], - number_of_sims: PositiveInt, - mean_annual_return: float, - mean_annual_vol: PositiveFloat, - trading_days: PositiveInt, - trading_days_in_year: DaysInYearType = 252, - seed: int | None = None, - randomizer: Generator | None = None, - ar1_coef: float = 0.0, - ) -> ReturnSimulation: - """Create a Normal distribution simulation. - - Args: - number_of_sims: Number of simulations to generate. - trading_days: Number of trading days to simulate. - mean_annual_return: Mean return. - mean_annual_vol: Mean standard deviation. - trading_days_in_year: Number of trading days used to annualize. - Defaults to 252. - seed: Seed for random process initiation. - randomizer: Random process generator. - ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce - autocorrelation. Defaults to 0.0 (i.i.d. returns). - - Returns: - Normal distribution simulation. - """ - _validate_ar1_coef(ar1_coef) - if not randomizer: - randomizer = _random_generator(seed=seed) - - returns_df = DataFrame( - data=randomizer.normal( - loc=mean_annual_return / trading_days_in_year, - scale=mean_annual_vol / sqrt(trading_days_in_year), - size=(number_of_sims, trading_days), - ), - dtype="float64", - ) - returns = _apply_ar1_filter(returns_df, ar1_coef) - - return _create_base_simulation( - cls=cls, - returns=returns, - number_of_sims=number_of_sims, - trading_days=trading_days, - trading_days_in_year=trading_days_in_year, - mean_annual_return=mean_annual_return, - mean_annual_vol=mean_annual_vol, - seed=seed, - )
- - -
-[docs] - @classmethod - def from_lognormal( - cls: type[ReturnSimulation], - number_of_sims: PositiveInt, - mean_annual_return: float, - mean_annual_vol: PositiveFloat, - trading_days: PositiveInt, - trading_days_in_year: DaysInYearType = 252, - seed: int | None = None, - randomizer: Generator | None = None, - ar1_coef: float = 0.0, - ) -> ReturnSimulation: - """Create a Lognormal distribution simulation. - - Args: - number_of_sims: Number of simulations to generate. - trading_days: Number of trading days to simulate. - mean_annual_return: Mean return. - mean_annual_vol: Mean standard deviation. - trading_days_in_year: Number of trading days used to annualize. - Defaults to 252. - seed: Seed for random process initiation. - randomizer: Random process generator. - ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce - autocorrelation. Defaults to 0.0 (i.i.d. returns). - - Returns: - Lognormal distribution simulation. - """ - _validate_ar1_coef(ar1_coef) - if not randomizer: - randomizer = _random_generator(seed=seed) - - returns_df = DataFrame( - data=( - randomizer.lognormal( - mean=mean_annual_return / trading_days_in_year, - sigma=mean_annual_vol / sqrt(trading_days_in_year), - size=(number_of_sims, trading_days), - ) - - 1 - ), - dtype="float64", - ) - returns = _apply_ar1_filter(returns_df, ar1_coef) - - return _create_base_simulation( - cls=cls, - returns=returns, - number_of_sims=number_of_sims, - trading_days=trading_days, - trading_days_in_year=trading_days_in_year, - mean_annual_return=mean_annual_return, - mean_annual_vol=mean_annual_vol, - seed=seed, - )
- - -
-[docs] - @classmethod - def from_gbm( - cls: type[ReturnSimulation], - number_of_sims: PositiveInt, - mean_annual_return: float, - mean_annual_vol: PositiveFloat, - trading_days: PositiveInt, - trading_days_in_year: DaysInYearType = 252, - seed: int | None = None, - randomizer: Generator | None = None, - ar1_coef: float = 0.0, - ) -> ReturnSimulation: - """Create a Geometric Brownian Motion simulation. - - Args: - number_of_sims: Number of simulations to generate. - trading_days: Number of trading days to simulate. - mean_annual_return: Mean return. - mean_annual_vol: Mean standard deviation. - trading_days_in_year: Number of trading days used to annualize. - Defaults to 252. - seed: Seed for random process initiation. - randomizer: Random process generator. - ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce - autocorrelation. Defaults to 0.0 (i.i.d. returns). - - Returns: - Geometric Brownian Motion simulation. - """ - _validate_ar1_coef(ar1_coef) - if not randomizer: - randomizer = _random_generator(seed=seed) - - drift = (mean_annual_return - 0.5 * mean_annual_vol**2.0) * ( - 1.0 / trading_days_in_year - ) - - normal_mean = 0.0 - wiener = randomizer.normal( - loc=normal_mean, - scale=sqrt(1.0 / trading_days_in_year) * mean_annual_vol, - size=(number_of_sims, trading_days), - ) - - returns_df = DataFrame(data=drift + wiener, dtype="float64") - returns = _apply_ar1_filter(returns_df, ar1_coef) - - return _create_base_simulation( - cls=cls, - returns=returns, - number_of_sims=number_of_sims, - trading_days=trading_days, - trading_days_in_year=trading_days_in_year, - mean_annual_return=mean_annual_return, - mean_annual_vol=mean_annual_vol, - seed=seed, - )
- - -
-[docs] - @classmethod - def from_merton_jump_gbm( - cls: type[ReturnSimulation], - number_of_sims: PositiveInt, - trading_days: PositiveInt, - mean_annual_return: float, - mean_annual_vol: PositiveFloat, - jumps_lamda: NonNegativeFloat, - jumps_sigma: NonNegativeFloat = 0.0, - jumps_mu: float = 0.0, - trading_days_in_year: DaysInYearType = 252, - seed: int | None = None, - randomizer: Generator | None = None, - ar1_coef: float = 0.0, - ) -> ReturnSimulation: - """Create a Merton Jump-Diffusion model simulation. - - Args: - number_of_sims: Number of simulations to generate. - trading_days: Number of trading days to simulate. - mean_annual_return: Mean return. - mean_annual_vol: Mean standard deviation. - jumps_lamda: This is the probability of a jump happening at each point - in time. - jumps_sigma: This is the volatility of the jump size. Defaults to 0.0. - jumps_mu: This is the average jump size. Defaults to 0.0. - trading_days_in_year: Number of trading days used to annualize. - Defaults to 252. - seed: Seed for random process initiation. - randomizer: Random process generator. - ar1_coef: Lag-1 autoregressive coefficient in (-1, 1) to induce - autocorrelation. Defaults to 0.0 (i.i.d. returns). - - Returns: - Merton Jump-Diffusion model simulation. - """ - _validate_ar1_coef(ar1_coef) - if not randomizer: - randomizer = _random_generator(seed=seed) - - normal_mean = 0.0 - wiener = randomizer.normal( - loc=normal_mean, - scale=sqrt(1.0 / trading_days_in_year) * mean_annual_vol, - size=(number_of_sims, trading_days), - ) - - poisson_jumps = multiply( - randomizer.poisson( - lam=jumps_lamda * (1.0 / trading_days_in_year), - size=(number_of_sims, trading_days), - ), - randomizer.normal( - loc=jumps_mu, - scale=jumps_sigma, - size=(number_of_sims, trading_days), - ), - ) - - drift = ( - mean_annual_return - - 0.5 * mean_annual_vol**2.0 - - jumps_lamda * (jumps_mu + jumps_sigma**2.0) - ) * (1.0 / trading_days_in_year) - - raw_returns = poisson_jumps + drift + wiener - raw_returns[:, 0] = 0.0 - - returns_df = DataFrame(data=raw_returns, dtype="float64") - returns = _apply_ar1_filter(returns_df, ar1_coef) - - return _create_base_simulation( - cls=cls, - returns=returns, - number_of_sims=number_of_sims, - trading_days=trading_days, - trading_days_in_year=trading_days_in_year, - mean_annual_return=mean_annual_return, - mean_annual_vol=mean_annual_vol, - seed=seed, - jumps_lamda=jumps_lamda, - jumps_sigma=jumps_sigma, - jumps_mu=jumps_mu, - )
- - -
-[docs] - def to_dataframe( - self: Self, - name: str, - start: dt.date | None = None, - end: dt.date | None = None, - countries: CountriesType = "SE", - markets: list[str] | str | None = None, - ) -> DataFrame: - """Create a pandas.DataFrame from simulation(s). - - Args: - name: Name label of the serie(s). - start: Date when the simulation starts. - end: Date when the simulation ends. - countries: (List of) country code(s) according to ISO 3166-1 alpha-2. - Defaults to "SE". - markets: (List of) markets code(s) supported by exchange_calendars. - - Returns: - The simulation(s) data. - """ - d_range = generate_calendar_date_range( - trading_days=self.trading_days, - start=start, - end=end, - countries=countries, - markets=markets, - ) - - if self.number_of_sims == 1: - sdf = self.dframe.iloc[0].T.to_frame() - sdf.index = Index(d_range) - sdf.columns = MultiIndex.from_arrays( - [ - [name], - [ValueType.RTRN], - ], - ) - return sdf - - df_list = [ - DataFrame( - data=self.dframe.iloc[item].values, - index=Index(d_range), - columns=MultiIndex.from_arrays( - [ - [f"{name}_{item}"], - [ValueType.RTRN], - ], - ), - ) - for item in range(self.number_of_sims) - ] - return concat(df_list, axis="columns", sort=True)
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/_sources/api/datefixer.rst.txt b/docs/build/html/_sources/api/datefixer.rst.txt deleted file mode 100644 index 53e8ae02..00000000 --- a/docs/build/html/_sources/api/datefixer.rst.txt +++ /dev/null @@ -1,22 +0,0 @@ -Date Utilities -============== - -.. currentmodule:: openseries.datefixer - -Date Handling Functions ------------------------ - -.. autofunction:: date_fix - :no-index: -.. autofunction:: date_offset_foll - :no-index: -.. autofunction:: generate_calendar_date_range - :no-index: -.. autofunction:: get_previous_business_day_before_today - :no-index: -.. autofunction:: holiday_calendar - :no-index: -.. autofunction:: offset_business_days - :no-index: - -The datefixer module provides utilities for handling business days, holidays, and date calculations commonly needed in financial analysis. diff --git a/docs/build/html/_sources/api/frame.rst.txt b/docs/build/html/_sources/api/frame.rst.txt deleted file mode 100644 index a644139c..00000000 --- a/docs/build/html/_sources/api/frame.rst.txt +++ /dev/null @@ -1,289 +0,0 @@ -OpenFrame -========= - -.. currentmodule:: openseries - -.. autoclass:: OpenFrame - :undoc-members: - :show-inheritance: - :no-index: - -The OpenFrame class manages collections of OpenTimeSeries objects and provides functionality for: - -- Multi-asset analysis and comparison -- Portfolio construction and optimization -- Correlation and regression analysis -- Risk attribution and factor analysis -- Batch processing of multiple time series - -Class Methods for Construction ------------------------------- - -.. automethod:: OpenFrame.from_deepcopy - :no-index: - -Properties ----------- - -Frame-specific Properties -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenFrame.constituents - :no-index: -.. autoattribute:: OpenFrame.columns_lvl_zero - :no-index: -.. autoattribute:: OpenFrame.columns_lvl_one - :no-index: -.. autoattribute:: OpenFrame.item_count - :no-index: -.. autoattribute:: OpenFrame.weights - :no-index: -.. autoattribute:: OpenFrame.first_indices - :no-index: -.. autoattribute:: OpenFrame.last_indices - :no-index: -.. autoattribute:: OpenFrame.lengths_of_items - :no-index: -.. autoattribute:: OpenFrame.span_of_days_all - :no-index: - -Common Properties -~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenFrame.first_idx - :no-index: -.. autoattribute:: OpenFrame.last_idx - :no-index: -.. autoattribute:: OpenFrame.length - :no-index: -.. autoattribute:: OpenFrame.span_of_days - :no-index: -.. autoattribute:: OpenFrame.tsdf - :no-index: -.. autoattribute:: OpenFrame.max_drawdown_date - :no-index: -.. autoattribute:: OpenFrame.periods_in_a_year - :no-index: -.. autoattribute:: OpenFrame.yearfrac - :no-index: - -Financial Metrics -~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenFrame.all_properties - :no-index: -.. autoattribute:: OpenFrame.arithmetic_ret - :no-index: -.. autoattribute:: OpenFrame.geo_ret - :no-index: -.. autoattribute:: OpenFrame.value_ret - :no-index: -.. autoattribute:: OpenFrame.vol - :no-index: -.. autoattribute:: OpenFrame.downside_deviation - :no-index: -.. autoattribute:: OpenFrame.ret_vol_ratio - :no-index: -.. autoattribute:: OpenFrame.sortino_ratio - :no-index: -.. autoattribute:: OpenFrame.kappa3_ratio - :no-index: -.. autoattribute:: OpenFrame.omega_ratio - :no-index: -.. autoattribute:: OpenFrame.var_down - :no-index: -.. autoattribute:: OpenFrame.cvar_down - :no-index: -.. autoattribute:: OpenFrame.worst - :no-index: -.. autoattribute:: OpenFrame.worst_month - :no-index: -.. autoattribute:: OpenFrame.max_drawdown - :no-index: -.. autoattribute:: OpenFrame.max_drawdown_cal_year - :no-index: -.. autoattribute:: OpenFrame.positive_share - :no-index: -.. autoattribute:: OpenFrame.vol_from_var - :no-index: -.. autoattribute:: OpenFrame.autocorr - :no-index: -.. autoattribute:: OpenFrame.skew - :no-index: -.. autoattribute:: OpenFrame.kurtosis - :no-index: -.. autoattribute:: OpenFrame.z_score - :no-index: - -Methods -------- - -Frame Management -~~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.merge_series - :no-index: -.. automethod:: OpenFrame.trunc_frame - :no-index: -.. automethod:: OpenFrame.add_timeseries - :no-index: -.. automethod:: OpenFrame.delete_timeseries - :no-index: - -Portfolio Analysis -~~~~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.relative - :no-index: -.. automethod:: OpenFrame.make_portfolio - :no-index: -.. automethod:: OpenFrame.rebalanced_portfolio - :no-index: - -Statistical Analysis -~~~~~~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.ord_least_squares_fit - :no-index: -.. automethod:: OpenFrame.beta - :no-index: -.. automethod:: OpenFrame.jensen_alpha - :no-index: -.. automethod:: OpenFrame.tracking_error_func - :no-index: -.. automethod:: OpenFrame.info_ratio_func - :no-index: -.. automethod:: OpenFrame.capture_ratio_func - :no-index: -.. automethod:: OpenFrame.multi_factor_linear_regression - :no-index: - -Rolling Analysis -~~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.rolling_info_ratio - :no-index: -.. automethod:: OpenFrame.rolling_beta - :no-index: -.. automethod:: OpenFrame.rolling_corr - :no-index: -.. automethod:: OpenFrame.rolling_return - :no-index: -.. automethod:: OpenFrame.rolling_vol - :no-index: -.. automethod:: OpenFrame.rolling_var_down - :no-index: -.. automethod:: OpenFrame.rolling_cvar_down - :no-index: - -Correlation and Risk -~~~~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenFrame.correl_matrix - :no-index: -.. automethod:: OpenFrame.ewma_risk - :no-index: - -Data Manipulation -~~~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.align_index_to_local_cdays - :no-index: -.. automethod:: OpenFrame.resample - :no-index: -.. automethod:: OpenFrame.resample_to_business_period_ends - :no-index: -.. automethod:: OpenFrame.value_nan_handle - :no-index: -.. automethod:: OpenFrame.return_nan_handle - :no-index: - -Transformations -~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.to_cumret - :no-index: -.. automethod:: OpenFrame.value_to_ret - :no-index: -.. automethod:: OpenFrame.value_to_diff - :no-index: -.. automethod:: OpenFrame.value_to_log - :no-index: -.. automethod:: OpenFrame.to_drawdown_series - :no-index: -.. automethod:: OpenFrame.value_ret_calendar_period - :no-index: - -Analysis Methods -~~~~~~~~~~~~~~~~ - -Autocorrelation analysis: ``autocorr`` (property) and ``autocorr_func`` return -lag-N autocorrelation per column. For ACF, PACF, and Ljung-Box tests, use the -constituent OpenTimeSeries objects. - -.. automethod:: OpenFrame.autocorr_func - :no-index: -.. automethod:: OpenFrame.calc_range - :no-index: -.. automethod:: OpenFrame.outliers - :no-index: - -Financial Metrics Methods -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.arithmetic_ret_func - :no-index: -.. automethod:: OpenFrame.geo_ret_func - :no-index: -.. automethod:: OpenFrame.value_ret_func - :no-index: -.. automethod:: OpenFrame.vol_func - :no-index: -.. automethod:: OpenFrame.lower_partial_moment_func - :no-index: -.. automethod:: OpenFrame.ret_vol_ratio_func - :no-index: -.. automethod:: OpenFrame.sortino_ratio_func - :no-index: -.. automethod:: OpenFrame.omega_ratio_func - :no-index: -.. automethod:: OpenFrame.var_down_func - :no-index: -.. automethod:: OpenFrame.cvar_down_func - :no-index: -.. automethod:: OpenFrame.worst_func - :no-index: -.. automethod:: OpenFrame.max_drawdown_func - :no-index: -.. automethod:: OpenFrame.positive_share_func - :no-index: -.. automethod:: OpenFrame.vol_from_var_func - :no-index: -.. automethod:: OpenFrame.skew_func - :no-index: -.. automethod:: OpenFrame.kurtosis_func - :no-index: -.. automethod:: OpenFrame.z_score_func - :no-index: -.. automethod:: OpenFrame.target_weight_from_var - :no-index: - -Visualization -~~~~~~~~~~~~~ - -The plotting methods generate fully responsive HTML output that automatically adapts to different screen sizes and device orientations. Plots are optimized for both desktop and mobile viewing with separate title containers and responsive CSS styling. - -.. automethod:: OpenFrame.plot_series - :no-index: -.. automethod:: OpenFrame.plot_bars - :no-index: -.. automethod:: OpenFrame.plot_histogram - :no-index: - -Export Methods -~~~~~~~~~~~~~~ - -.. automethod:: OpenFrame.to_json - :no-index: -.. automethod:: OpenFrame.to_xlsx - :no-index: diff --git a/docs/build/html/_sources/api/generated/openseries.OpenFrame.rst.txt b/docs/build/html/_sources/api/generated/openseries.OpenFrame.rst.txt deleted file mode 100644 index 2639c005..00000000 --- a/docs/build/html/_sources/api/generated/openseries.OpenFrame.rst.txt +++ /dev/null @@ -1,156 +0,0 @@ -openseries.OpenFrame -==================== - -.. currentmodule:: openseries - -.. autoclass:: OpenFrame - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~OpenFrame.__init__ - ~OpenFrame.add_timeseries - ~OpenFrame.align_index_to_local_cdays - ~OpenFrame.all_properties - ~OpenFrame.arithmetic_ret_func - ~OpenFrame.autocorr_func - ~OpenFrame.beta - ~OpenFrame.calc_range - ~OpenFrame.capture_ratio_func - ~OpenFrame.construct - ~OpenFrame.copy - ~OpenFrame.cvar_down_func - ~OpenFrame.delete_timeseries - ~OpenFrame.dict - ~OpenFrame.ewma_risk - ~OpenFrame.from_deepcopy - ~OpenFrame.from_orm - ~OpenFrame.geo_ret_func - ~OpenFrame.info_ratio_func - ~OpenFrame.jensen_alpha - ~OpenFrame.json - ~OpenFrame.kurtosis_func - ~OpenFrame.lower_partial_moment_func - ~OpenFrame.make_portfolio - ~OpenFrame.max_drawdown_func - ~OpenFrame.merge_series - ~OpenFrame.model_construct - ~OpenFrame.model_copy - ~OpenFrame.model_dump - ~OpenFrame.model_dump_json - ~OpenFrame.model_json_schema - ~OpenFrame.model_parametrized_name - ~OpenFrame.model_post_init - ~OpenFrame.model_rebuild - ~OpenFrame.model_validate - ~OpenFrame.model_validate_json - ~OpenFrame.model_validate_strings - ~OpenFrame.multi_factor_linear_regression - ~OpenFrame.omega_ratio_func - ~OpenFrame.ord_least_squares_fit - ~OpenFrame.outliers - ~OpenFrame.parse_file - ~OpenFrame.parse_obj - ~OpenFrame.parse_raw - ~OpenFrame.plot_bars - ~OpenFrame.plot_histogram - ~OpenFrame.plot_series - ~OpenFrame.positive_share_func - ~OpenFrame.rebalanced_portfolio - ~OpenFrame.relative - ~OpenFrame.resample - ~OpenFrame.resample_to_business_period_ends - ~OpenFrame.ret_vol_ratio_func - ~OpenFrame.return_nan_handle - ~OpenFrame.rolling_beta - ~OpenFrame.rolling_corr - ~OpenFrame.rolling_cvar_down - ~OpenFrame.rolling_info_ratio - ~OpenFrame.rolling_return - ~OpenFrame.rolling_var_down - ~OpenFrame.rolling_vol - ~OpenFrame.schema - ~OpenFrame.schema_json - ~OpenFrame.skew_func - ~OpenFrame.sortino_ratio_func - ~OpenFrame.target_weight_from_var - ~OpenFrame.to_cumret - ~OpenFrame.to_drawdown_series - ~OpenFrame.to_json - ~OpenFrame.to_xlsx - ~OpenFrame.tracking_error_func - ~OpenFrame.trunc_frame - ~OpenFrame.update_forward_refs - ~OpenFrame.validate - ~OpenFrame.value_nan_handle - ~OpenFrame.value_ret_calendar_period - ~OpenFrame.value_ret_func - ~OpenFrame.value_to_diff - ~OpenFrame.value_to_log - ~OpenFrame.value_to_ret - ~OpenFrame.var_down_func - ~OpenFrame.vol_from_var_func - ~OpenFrame.vol_func - ~OpenFrame.worst_func - ~OpenFrame.z_score_func - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~OpenFrame.arithmetic_ret - ~OpenFrame.autocorr - ~OpenFrame.columns_lvl_one - ~OpenFrame.columns_lvl_zero - ~OpenFrame.correl_matrix - ~OpenFrame.cvar_down - ~OpenFrame.downside_deviation - ~OpenFrame.first_idx - ~OpenFrame.first_indices - ~OpenFrame.geo_ret - ~OpenFrame.item_count - ~OpenFrame.kappa3_ratio - ~OpenFrame.kurtosis - ~OpenFrame.last_idx - ~OpenFrame.last_indices - ~OpenFrame.length - ~OpenFrame.lengths_of_items - ~OpenFrame.max_drawdown - ~OpenFrame.max_drawdown_cal_year - ~OpenFrame.max_drawdown_date - ~OpenFrame.model_computed_fields - ~OpenFrame.model_config - ~OpenFrame.model_extra - ~OpenFrame.model_fields - ~OpenFrame.model_fields_set - ~OpenFrame.omega_ratio - ~OpenFrame.periods_in_a_year - ~OpenFrame.positive_share - ~OpenFrame.ret_vol_ratio - ~OpenFrame.skew - ~OpenFrame.sortino_ratio - ~OpenFrame.span_of_days - ~OpenFrame.span_of_days_all - ~OpenFrame.value_ret - ~OpenFrame.var_down - ~OpenFrame.vol - ~OpenFrame.vol_from_var - ~OpenFrame.worst - ~OpenFrame.worst_month - ~OpenFrame.yearfrac - ~OpenFrame.z_score - ~OpenFrame.constituents - ~OpenFrame.weights - ~OpenFrame.markets - ~OpenFrame.tsdf - - \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.OpenTimeSeries.rst.txt b/docs/build/html/_sources/api/generated/openseries.OpenTimeSeries.rst.txt deleted file mode 100644 index 1c3dbc26..00000000 --- a/docs/build/html/_sources/api/generated/openseries.OpenTimeSeries.rst.txt +++ /dev/null @@ -1,155 +0,0 @@ -openseries.OpenTimeSeries -========================= - -.. currentmodule:: openseries - -.. autoclass:: OpenTimeSeries - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~OpenTimeSeries.__init__ - ~OpenTimeSeries.acf - ~OpenTimeSeries.align_index_to_local_cdays - ~OpenTimeSeries.all_properties - ~OpenTimeSeries.arithmetic_ret_func - ~OpenTimeSeries.autocorr_func - ~OpenTimeSeries.calc_range - ~OpenTimeSeries.construct - ~OpenTimeSeries.copy - ~OpenTimeSeries.cvar_down_func - ~OpenTimeSeries.dict - ~OpenTimeSeries.ewma_var_func - ~OpenTimeSeries.ewma_vol_func - ~OpenTimeSeries.from_1d_rate_to_cumret - ~OpenTimeSeries.from_arrays - ~OpenTimeSeries.from_deepcopy - ~OpenTimeSeries.from_df - ~OpenTimeSeries.from_fixed_rate - ~OpenTimeSeries.from_orm - ~OpenTimeSeries.geo_ret_func - ~OpenTimeSeries.json - ~OpenTimeSeries.kurtosis_func - ~OpenTimeSeries.ljung_box - ~OpenTimeSeries.lower_partial_moment_func - ~OpenTimeSeries.max_drawdown_func - ~OpenTimeSeries.model_construct - ~OpenTimeSeries.model_copy - ~OpenTimeSeries.model_dump - ~OpenTimeSeries.model_dump_json - ~OpenTimeSeries.model_json_schema - ~OpenTimeSeries.model_parametrized_name - ~OpenTimeSeries.model_post_init - ~OpenTimeSeries.model_rebuild - ~OpenTimeSeries.model_validate - ~OpenTimeSeries.model_validate_json - ~OpenTimeSeries.model_validate_strings - ~OpenTimeSeries.omega_ratio_func - ~OpenTimeSeries.outliers - ~OpenTimeSeries.pacf - ~OpenTimeSeries.pandas_df - ~OpenTimeSeries.parse_file - ~OpenTimeSeries.parse_obj - ~OpenTimeSeries.parse_raw - ~OpenTimeSeries.partial_autocorr - ~OpenTimeSeries.plot_bars - ~OpenTimeSeries.plot_histogram - ~OpenTimeSeries.plot_series - ~OpenTimeSeries.positive_share_func - ~OpenTimeSeries.resample - ~OpenTimeSeries.resample_to_business_period_ends - ~OpenTimeSeries.ret_vol_ratio_func - ~OpenTimeSeries.return_nan_handle - ~OpenTimeSeries.rolling_cvar_down - ~OpenTimeSeries.rolling_return - ~OpenTimeSeries.rolling_var_down - ~OpenTimeSeries.rolling_vol - ~OpenTimeSeries.running_adjustment - ~OpenTimeSeries.schema - ~OpenTimeSeries.schema_json - ~OpenTimeSeries.set_new_label - ~OpenTimeSeries.skew_func - ~OpenTimeSeries.sortino_ratio_func - ~OpenTimeSeries.target_weight_from_var - ~OpenTimeSeries.to_cumret - ~OpenTimeSeries.to_drawdown_series - ~OpenTimeSeries.to_json - ~OpenTimeSeries.to_xlsx - ~OpenTimeSeries.update_forward_refs - ~OpenTimeSeries.validate - ~OpenTimeSeries.value_nan_handle - ~OpenTimeSeries.value_ret_calendar_period - ~OpenTimeSeries.value_ret_func - ~OpenTimeSeries.value_to_diff - ~OpenTimeSeries.value_to_log - ~OpenTimeSeries.value_to_ret - ~OpenTimeSeries.var_down_func - ~OpenTimeSeries.vol_from_var_func - ~OpenTimeSeries.vol_func - ~OpenTimeSeries.worst_func - ~OpenTimeSeries.z_score_func - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~OpenTimeSeries.arithmetic_ret - ~OpenTimeSeries.autocorr - ~OpenTimeSeries.cvar_down - ~OpenTimeSeries.downside_deviation - ~OpenTimeSeries.first_idx - ~OpenTimeSeries.geo_ret - ~OpenTimeSeries.kappa3_ratio - ~OpenTimeSeries.kurtosis - ~OpenTimeSeries.last_idx - ~OpenTimeSeries.length - ~OpenTimeSeries.max_drawdown - ~OpenTimeSeries.max_drawdown_cal_year - ~OpenTimeSeries.max_drawdown_date - ~OpenTimeSeries.model_computed_fields - ~OpenTimeSeries.model_config - ~OpenTimeSeries.model_extra - ~OpenTimeSeries.model_fields - ~OpenTimeSeries.model_fields_set - ~OpenTimeSeries.omega_ratio - ~OpenTimeSeries.periods_in_a_year - ~OpenTimeSeries.positive_share - ~OpenTimeSeries.ret_vol_ratio - ~OpenTimeSeries.skew - ~OpenTimeSeries.sortino_ratio - ~OpenTimeSeries.span_of_days - ~OpenTimeSeries.value_ret - ~OpenTimeSeries.var_down - ~OpenTimeSeries.vol - ~OpenTimeSeries.vol_from_var - ~OpenTimeSeries.worst - ~OpenTimeSeries.worst_month - ~OpenTimeSeries.yearfrac - ~OpenTimeSeries.z_score - ~OpenTimeSeries.timeseries_id - ~OpenTimeSeries.instrument_id - ~OpenTimeSeries.name - ~OpenTimeSeries.valuetype - ~OpenTimeSeries.dates - ~OpenTimeSeries.values - ~OpenTimeSeries.local_ccy - ~OpenTimeSeries.tsdf - ~OpenTimeSeries.currency - ~OpenTimeSeries.domestic - ~OpenTimeSeries.countries - ~OpenTimeSeries.isin - ~OpenTimeSeries.label - ~OpenTimeSeries.constituents - ~OpenTimeSeries.weights - ~OpenTimeSeries.markets - - \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.ReturnSimulation.rst.txt b/docs/build/html/_sources/api/generated/openseries.ReturnSimulation.rst.txt deleted file mode 100644 index e616fb7e..00000000 --- a/docs/build/html/_sources/api/generated/openseries.ReturnSimulation.rst.txt +++ /dev/null @@ -1,73 +0,0 @@ -openseries.ReturnSimulation -=========================== - -.. currentmodule:: openseries - -.. autoclass:: ReturnSimulation - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~ReturnSimulation.__init__ - ~ReturnSimulation.construct - ~ReturnSimulation.copy - ~ReturnSimulation.dict - ~ReturnSimulation.from_gbm - ~ReturnSimulation.from_lognormal - ~ReturnSimulation.from_merton_jump_gbm - ~ReturnSimulation.from_normal - ~ReturnSimulation.from_orm - ~ReturnSimulation.json - ~ReturnSimulation.model_construct - ~ReturnSimulation.model_copy - ~ReturnSimulation.model_dump - ~ReturnSimulation.model_dump_json - ~ReturnSimulation.model_json_schema - ~ReturnSimulation.model_parametrized_name - ~ReturnSimulation.model_post_init - ~ReturnSimulation.model_rebuild - ~ReturnSimulation.model_validate - ~ReturnSimulation.model_validate_json - ~ReturnSimulation.model_validate_strings - ~ReturnSimulation.parse_file - ~ReturnSimulation.parse_obj - ~ReturnSimulation.parse_raw - ~ReturnSimulation.schema - ~ReturnSimulation.schema_json - ~ReturnSimulation.to_dataframe - ~ReturnSimulation.update_forward_refs - ~ReturnSimulation.validate - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~ReturnSimulation.model_computed_fields - ~ReturnSimulation.model_config - ~ReturnSimulation.model_extra - ~ReturnSimulation.model_fields - ~ReturnSimulation.model_fields_set - ~ReturnSimulation.realized_mean_return - ~ReturnSimulation.realized_vol - ~ReturnSimulation.results - ~ReturnSimulation.number_of_sims - ~ReturnSimulation.trading_days - ~ReturnSimulation.trading_days_in_year - ~ReturnSimulation.mean_annual_return - ~ReturnSimulation.mean_annual_vol - ~ReturnSimulation.dframe - ~ReturnSimulation.jumps_lamda - ~ReturnSimulation.jumps_sigma - ~ReturnSimulation.jumps_mu - ~ReturnSimulation.seed - - \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.ValueType.rst.txt b/docs/build/html/_sources/api/generated/openseries.ValueType.rst.txt deleted file mode 100644 index fd1cfe87..00000000 --- a/docs/build/html/_sources/api/generated/openseries.ValueType.rst.txt +++ /dev/null @@ -1,86 +0,0 @@ -openseries.ValueType -==================== - -.. currentmodule:: openseries - -.. autoclass:: ValueType - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~ValueType.encode - ~ValueType.replace - ~ValueType.split - ~ValueType.rsplit - ~ValueType.join - ~ValueType.capitalize - ~ValueType.casefold - ~ValueType.title - ~ValueType.center - ~ValueType.count - ~ValueType.expandtabs - ~ValueType.find - ~ValueType.partition - ~ValueType.index - ~ValueType.ljust - ~ValueType.lower - ~ValueType.lstrip - ~ValueType.rfind - ~ValueType.rindex - ~ValueType.rjust - ~ValueType.rstrip - ~ValueType.rpartition - ~ValueType.splitlines - ~ValueType.strip - ~ValueType.swapcase - ~ValueType.translate - ~ValueType.upper - ~ValueType.startswith - ~ValueType.endswith - ~ValueType.removeprefix - ~ValueType.removesuffix - ~ValueType.isascii - ~ValueType.islower - ~ValueType.isupper - ~ValueType.istitle - ~ValueType.isspace - ~ValueType.isdecimal - ~ValueType.isdigit - ~ValueType.isnumeric - ~ValueType.isalpha - ~ValueType.isalnum - ~ValueType.isidentifier - ~ValueType.isprintable - ~ValueType.zfill - ~ValueType.format - ~ValueType.format_map - ~ValueType.maketrans - ~ValueType.__init__ - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~ValueType.EWMA_VOL - ~ValueType.EWMA_VAR - ~ValueType.PRICE - ~ValueType.RTRN - ~ValueType.RELRTRN - ~ValueType.ROLLBETA - ~ValueType.ROLLCORR - ~ValueType.ROLLCVAR - ~ValueType.ROLLINFORATIO - ~ValueType.ROLLRTRN - ~ValueType.ROLLVAR - ~ValueType.ROLLVOL - - \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.constrain_optimized_portfolios.rst.txt b/docs/build/html/_sources/api/generated/openseries.constrain_optimized_portfolios.rst.txt deleted file mode 100644 index 8ac01906..00000000 --- a/docs/build/html/_sources/api/generated/openseries.constrain_optimized_portfolios.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.constrain\_optimized\_portfolios -=========================================== - -.. currentmodule:: openseries - -.. autofunction:: constrain_optimized_portfolios \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.date_fix.rst.txt b/docs/build/html/_sources/api/generated/openseries.date_fix.rst.txt deleted file mode 100644 index 997c00c9..00000000 --- a/docs/build/html/_sources/api/generated/openseries.date_fix.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.date\_fix -==================== - -.. currentmodule:: openseries - -.. autofunction:: date_fix \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.date_offset_foll.rst.txt b/docs/build/html/_sources/api/generated/openseries.date_offset_foll.rst.txt deleted file mode 100644 index e69616f9..00000000 --- a/docs/build/html/_sources/api/generated/openseries.date_offset_foll.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.date\_offset\_foll -============================= - -.. currentmodule:: openseries - -.. autofunction:: date_offset_foll \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.efficient_frontier.rst.txt b/docs/build/html/_sources/api/generated/openseries.efficient_frontier.rst.txt deleted file mode 100644 index 8983301e..00000000 --- a/docs/build/html/_sources/api/generated/openseries.efficient_frontier.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.efficient\_frontier -============================== - -.. currentmodule:: openseries - -.. autofunction:: efficient_frontier \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.generate_calendar_date_range.rst.txt b/docs/build/html/_sources/api/generated/openseries.generate_calendar_date_range.rst.txt deleted file mode 100644 index cba4c6a9..00000000 --- a/docs/build/html/_sources/api/generated/openseries.generate_calendar_date_range.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.generate\_calendar\_date\_range -========================================== - -.. currentmodule:: openseries - -.. autofunction:: generate_calendar_date_range \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.get_previous_business_day_before_today.rst.txt b/docs/build/html/_sources/api/generated/openseries.get_previous_business_day_before_today.rst.txt deleted file mode 100644 index 38e2e8a6..00000000 --- a/docs/build/html/_sources/api/generated/openseries.get_previous_business_day_before_today.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.get\_previous\_business\_day\_before\_today -====================================================== - -.. currentmodule:: openseries - -.. autofunction:: get_previous_business_day_before_today \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.holiday_calendar.rst.txt b/docs/build/html/_sources/api/generated/openseries.holiday_calendar.rst.txt deleted file mode 100644 index 49edd9e7..00000000 --- a/docs/build/html/_sources/api/generated/openseries.holiday_calendar.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.holiday\_calendar -============================ - -.. currentmodule:: openseries - -.. autofunction:: holiday_calendar \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.load_plotly_dict.rst.txt b/docs/build/html/_sources/api/generated/openseries.load_plotly_dict.rst.txt deleted file mode 100644 index e52afff4..00000000 --- a/docs/build/html/_sources/api/generated/openseries.load_plotly_dict.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.load\_plotly\_dict -============================= - -.. currentmodule:: openseries - -.. autofunction:: load_plotly_dict \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.offset_business_days.rst.txt b/docs/build/html/_sources/api/generated/openseries.offset_business_days.rst.txt deleted file mode 100644 index 46c7f971..00000000 --- a/docs/build/html/_sources/api/generated/openseries.offset_business_days.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.offset\_business\_days -================================= - -.. currentmodule:: openseries - -.. autofunction:: offset_business_days \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.prepare_plot_data.rst.txt b/docs/build/html/_sources/api/generated/openseries.prepare_plot_data.rst.txt deleted file mode 100644 index ecfbb4c2..00000000 --- a/docs/build/html/_sources/api/generated/openseries.prepare_plot_data.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.prepare\_plot\_data -============================== - -.. currentmodule:: openseries - -.. autofunction:: prepare_plot_data \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.report_html.rst.txt b/docs/build/html/_sources/api/generated/openseries.report_html.rst.txt deleted file mode 100644 index a9d7292a..00000000 --- a/docs/build/html/_sources/api/generated/openseries.report_html.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.report\_html -======================= - -.. currentmodule:: openseries - -.. autofunction:: report_html \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.sharpeplot.rst.txt b/docs/build/html/_sources/api/generated/openseries.sharpeplot.rst.txt deleted file mode 100644 index 7e75303f..00000000 --- a/docs/build/html/_sources/api/generated/openseries.sharpeplot.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.sharpeplot -===================== - -.. currentmodule:: openseries - -.. autofunction:: sharpeplot \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.simulate_portfolios.rst.txt b/docs/build/html/_sources/api/generated/openseries.simulate_portfolios.rst.txt deleted file mode 100644 index 915fef82..00000000 --- a/docs/build/html/_sources/api/generated/openseries.simulate_portfolios.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.simulate\_portfolios -=============================== - -.. currentmodule:: openseries - -.. autofunction:: simulate_portfolios \ No newline at end of file diff --git a/docs/build/html/_sources/api/generated/openseries.timeseries_chain.rst.txt b/docs/build/html/_sources/api/generated/openseries.timeseries_chain.rst.txt deleted file mode 100644 index 1a4902c1..00000000 --- a/docs/build/html/_sources/api/generated/openseries.timeseries_chain.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -openseries.timeseries\_chain -============================ - -.. currentmodule:: openseries - -.. autofunction:: timeseries_chain \ No newline at end of file diff --git a/docs/build/html/_sources/api/openseries.rst.txt b/docs/build/html/_sources/api/openseries.rst.txt deleted file mode 100644 index 2761c330..00000000 --- a/docs/build/html/_sources/api/openseries.rst.txt +++ /dev/null @@ -1,90 +0,0 @@ -openseries package -================== - -The openseries package provides two main classes for financial time series analysis: - -- **OpenTimeSeries**: For single financial time series analysis -- **OpenFrame**: For multi-asset portfolio analysis and comparison - -Both classes inherit from the private ``_CommonModel`` class, which in turn inherits from Pydantic's ``BaseModel``. This inheritance structure provides: - -- **Data validation** through Pydantic's validation system -- **Common functionality** shared between both classes (risk metrics, plotting, data handling) -- **Type safety** and consistent API design - -The ``_CommonModel`` class contains all the shared methods and properties that both ``OpenTimeSeries`` and ``OpenFrame`` use, including risk calculations, plotting capabilities, and data manipulation functions. - -Main Classes ------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.OpenTimeSeries - openseries.OpenFrame - -Utility Functions ------------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.timeseries_chain - openseries.report_html - -Portfolio Tools ---------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.efficient_frontier - openseries.simulate_portfolios - openseries.constrain_optimized_portfolios - openseries.prepare_plot_data - openseries.sharpeplot - -Date Utilities --------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.date_fix - openseries.date_offset_foll - openseries.generate_calendar_date_range - openseries.get_previous_business_day_before_today - openseries.holiday_calendar - openseries.offset_business_days - -Simulation ----------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.ReturnSimulation - -Types and Enums ---------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.ValueType - -Other Utilities ---------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - openseries.load_plotly_dict - openseries.export_plotly_figure diff --git a/docs/build/html/_sources/api/portfoliotools.rst.txt b/docs/build/html/_sources/api/portfoliotools.rst.txt deleted file mode 100644 index d41b4812..00000000 --- a/docs/build/html/_sources/api/portfoliotools.rst.txt +++ /dev/null @@ -1,32 +0,0 @@ -Portfolio Tools -=============== - -.. currentmodule:: openseries.portfoliotools - -The portfoliotools module provides functions for portfolio optimization, simulation, and analysis. - -Portfolio Optimization ------------------------ - -.. autofunction:: efficient_frontier - :no-index: - -Portfolio Simulation ---------------------- - -.. autofunction:: simulate_portfolios - :no-index: - -Portfolio Constraints ---------------------- - -.. autofunction:: constrain_optimized_portfolios - :no-index: - -Visualization -------------- - -.. autofunction:: prepare_plot_data - :no-index: -.. autofunction:: sharpeplot - :no-index: diff --git a/docs/build/html/_sources/api/report.rst.txt b/docs/build/html/_sources/api/report.rst.txt deleted file mode 100644 index b80ac6e1..00000000 --- a/docs/build/html/_sources/api/report.rst.txt +++ /dev/null @@ -1,67 +0,0 @@ -Report Generation -================= - -.. currentmodule:: openseries.report - -HTML Report Function --------------------- - -.. autofunction:: report_html - :no-index: - -The ``report_html`` function creates comprehensive HTML reports for financial analysis, comparing multiple assets and providing detailed performance metrics, charts, and risk analysis. - -The generated report includes: - -- **Interactive line charts** showing cumulative returns over time for all assets -- **Bar charts** displaying period returns (annual, quarterly, or monthly depending on data length) -- **Performance metrics table** including: - - Return metrics (CAGR or simple return, Year-to-Date, Month-to-Date) - - Risk metrics (Volatility, Sharpe Ratio, Sortino Ratio) - - Relative performance metrics (Jensen's Alpha, Information Ratio, Tracking Error, Index Beta) - - Capture Ratio (for periods longer than one year) - - Worst period returns - - Comparison period dates - -**Important Notes:** - -- The last asset in the ``OpenFrame`` is used as the benchmark for relative performance metrics - (Jensen's Alpha, Information Ratio, Tracking Error, Index Beta, and Capture Ratio) -- For periods shorter than one year, the report uses simple returns instead of CAGR -- For periods shorter than a quarter, bar charts show daily returns instead of period returns -- Capture Ratio is only included for periods longer than one year - -Responsive Design ------------------ - -The report features **responsive design** with separate layouts optimized for desktop and mobile devices: - -- **Desktop layout**: Charts and tables are displayed side-by-side in a 2x2 grid layout. The table - is rendered as an interactive Plotly table integrated with the charts. - -- **Mobile layout**: Content is stacked vertically for better viewing on smaller screens. The table - is rendered as a standard HTML table below the charts for better mobile compatibility. - -The HTML output automatically adapts to screen size and device capabilities using CSS media queries -and JavaScript detection. The layout switches at a breakpoint of 960px width or when touch capabilities -are detected. - -Return Values -------------- - -The function returns a tuple containing: - -- **Plotly Figure**: The desktop version of the figure object (can be used for further customization - or interactive display) - -- **String output**: The type depends on the ``output_type`` parameter: - - - When ``output_type="file"`` (default): Returns the file path string to the saved HTML file. - The file contains a complete HTML document (with DOCTYPE, html, head, and body tags) - that can be opened directly in a web browser. If ``auto_open=True``, the file will - automatically open in the default web browser. - - - When ``output_type="div"``: Returns a string containing the responsive HTML div section - (includes both desktop and mobile layouts with CSS and JavaScript) that can be embedded - in an existing HTML page. The ``filename`` parameter is optional when using this mode and - is only used to generate unique div IDs for the embedded content. diff --git a/docs/build/html/_sources/api/series.rst.txt b/docs/build/html/_sources/api/series.rst.txt deleted file mode 100644 index b899915a..00000000 --- a/docs/build/html/_sources/api/series.rst.txt +++ /dev/null @@ -1,272 +0,0 @@ -OpenTimeSeries -============== - -.. currentmodule:: openseries - -.. autoclass:: OpenTimeSeries - :undoc-members: - :show-inheritance: - :special-members: __init__ - :no-index: - -The OpenTimeSeries class is the core component for analyzing individual financial time series. It provides comprehensive functionality for: - -- Loading data from various sources (arrays, DataFrames, fixed rates) -- Calculating financial metrics and risk measures -- Performing time series transformations -- Creating visualizations -- Exporting results - -Class Methods for Construction ------------------------------- - -.. automethod:: OpenTimeSeries.from_arrays - :no-index: -.. automethod:: OpenTimeSeries.from_df - :no-index: -.. automethod:: OpenTimeSeries.from_fixed_rate - :no-index: -.. automethod:: OpenTimeSeries.from_deepcopy - :no-index: - -Properties ----------- - -Non-numerical Properties -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenTimeSeries.timeseries_id - :no-index: -.. autoattribute:: OpenTimeSeries.instrument_id - :no-index: -.. autoattribute:: OpenTimeSeries.dates - :no-index: -.. autoattribute:: OpenTimeSeries.values - :no-index: -.. autoattribute:: OpenTimeSeries.currency - :no-index: -.. autoattribute:: OpenTimeSeries.domestic - :no-index: -.. autoattribute:: OpenTimeSeries.local_ccy - :no-index: -.. autoattribute:: OpenTimeSeries.name - :no-index: -.. autoattribute:: OpenTimeSeries.isin - :no-index: -.. autoattribute:: OpenTimeSeries.label - :no-index: -.. autoattribute:: OpenTimeSeries.countries - :no-index: -.. autoattribute:: OpenTimeSeries.markets - :no-index: -.. autoattribute:: OpenTimeSeries.valuetype - :no-index: - -Common Properties -~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenTimeSeries.first_idx - :no-index: -.. autoattribute:: OpenTimeSeries.last_idx - :no-index: -.. autoattribute:: OpenTimeSeries.length - :no-index: -.. autoattribute:: OpenTimeSeries.span_of_days - :no-index: -.. autoattribute:: OpenTimeSeries.tsdf - :no-index: -.. autoattribute:: OpenTimeSeries.max_drawdown_date - :no-index: -.. autoattribute:: OpenTimeSeries.periods_in_a_year - :no-index: -.. autoattribute:: OpenTimeSeries.yearfrac - :no-index: - -Financial Metrics -~~~~~~~~~~~~~~~~~ - -.. autoattribute:: OpenTimeSeries.all_properties - :no-index: -.. autoattribute:: OpenTimeSeries.arithmetic_ret - :no-index: -.. autoattribute:: OpenTimeSeries.geo_ret - :no-index: -.. autoattribute:: OpenTimeSeries.value_ret - :no-index: -.. autoattribute:: OpenTimeSeries.vol - :no-index: -.. autoattribute:: OpenTimeSeries.downside_deviation - :no-index: -.. autoattribute:: OpenTimeSeries.ret_vol_ratio - :no-index: -.. autoattribute:: OpenTimeSeries.sortino_ratio - :no-index: -.. autoattribute:: OpenTimeSeries.kappa3_ratio - :no-index: -.. autoattribute:: OpenTimeSeries.omega_ratio - :no-index: -.. autoattribute:: OpenTimeSeries.var_down - :no-index: -.. autoattribute:: OpenTimeSeries.cvar_down - :no-index: -.. autoattribute:: OpenTimeSeries.worst - :no-index: -.. autoattribute:: OpenTimeSeries.worst_month - :no-index: -.. autoattribute:: OpenTimeSeries.max_drawdown - :no-index: -.. autoattribute:: OpenTimeSeries.max_drawdown_cal_year - :no-index: -.. autoattribute:: OpenTimeSeries.positive_share - :no-index: -.. autoattribute:: OpenTimeSeries.vol_from_var - :no-index: -.. autoattribute:: OpenTimeSeries.autocorr - :no-index: -.. autoattribute:: OpenTimeSeries.skew - :no-index: -.. autoattribute:: OpenTimeSeries.kurtosis - :no-index: -.. autoattribute:: OpenTimeSeries.z_score - :no-index: - -Methods -------- - -Data Manipulation -~~~~~~~~~~~~~~~~~ - -.. automethod:: OpenTimeSeries.pandas_df - :no-index: -.. automethod:: OpenTimeSeries.set_new_label - :no-index: -.. automethod:: OpenTimeSeries.running_adjustment - :no-index: -.. automethod:: OpenTimeSeries.from_1d_rate_to_cumret - :no-index: -.. automethod:: OpenTimeSeries.align_index_to_local_cdays - :no-index: -.. automethod:: OpenTimeSeries.resample - :no-index: -.. automethod:: OpenTimeSeries.resample_to_business_period_ends - :no-index: -.. automethod:: OpenTimeSeries.value_nan_handle - :no-index: -.. automethod:: OpenTimeSeries.return_nan_handle - :no-index: - -Transformations -~~~~~~~~~~~~~~~ - -.. automethod:: OpenTimeSeries.to_cumret - :no-index: -.. automethod:: OpenTimeSeries.value_to_ret - :no-index: -.. automethod:: OpenTimeSeries.value_to_diff - :no-index: -.. automethod:: OpenTimeSeries.value_to_log - :no-index: -.. automethod:: OpenTimeSeries.to_drawdown_series - :no-index: - -Analysis Methods -~~~~~~~~~~~~~~~~ - -Autocorrelation analysis: ``autocorr`` (property) and ``autocorr_func`` provide -lag-N autocorrelation; ``acf``, ``pacf``, ``partial_autocorr``, and ``ljung_box`` -support full autocorrelation diagnostics. Available on both OpenTimeSeries and -OpenFrame (except acf, pacf, partial_autocorr, ljung_box which are -OpenTimeSeries-only). - -.. automethod:: OpenTimeSeries.autocorr_func - :no-index: -.. automethod:: OpenTimeSeries.acf - :no-index: -.. automethod:: OpenTimeSeries.partial_autocorr - :no-index: -.. automethod:: OpenTimeSeries.pacf - :no-index: -.. automethod:: OpenTimeSeries.ljung_box - :no-index: -.. automethod:: OpenTimeSeries.ewma_vol_func - :no-index: -.. automethod:: OpenTimeSeries.value_ret_calendar_period - :no-index: -.. automethod:: OpenTimeSeries.rolling_return - :no-index: -.. automethod:: OpenTimeSeries.rolling_vol - :no-index: -.. automethod:: OpenTimeSeries.rolling_var_down - :no-index: -.. automethod:: OpenTimeSeries.rolling_cvar_down - :no-index: -.. automethod:: OpenTimeSeries.calc_range - :no-index: -.. automethod:: OpenTimeSeries.outliers - :no-index: - -Financial Metrics Methods -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automethod:: OpenTimeSeries.arithmetic_ret_func - :no-index: -.. automethod:: OpenTimeSeries.geo_ret_func - :no-index: -.. automethod:: OpenTimeSeries.value_ret_func - :no-index: -.. automethod:: OpenTimeSeries.vol_func - :no-index: -.. automethod:: OpenTimeSeries.lower_partial_moment_func - :no-index: -.. automethod:: OpenTimeSeries.ret_vol_ratio_func - :no-index: -.. automethod:: OpenTimeSeries.sortino_ratio_func - :no-index: -.. automethod:: OpenTimeSeries.omega_ratio_func - :no-index: -.. automethod:: OpenTimeSeries.var_down_func - :no-index: -.. automethod:: OpenTimeSeries.cvar_down_func - :no-index: -.. automethod:: OpenTimeSeries.worst_func - :no-index: -.. automethod:: OpenTimeSeries.max_drawdown_func - :no-index: -.. automethod:: OpenTimeSeries.positive_share_func - :no-index: -.. automethod:: OpenTimeSeries.vol_from_var_func - :no-index: -.. automethod:: OpenTimeSeries.skew_func - :no-index: -.. automethod:: OpenTimeSeries.kurtosis_func - :no-index: -.. automethod:: OpenTimeSeries.z_score_func - :no-index: -.. automethod:: OpenTimeSeries.target_weight_from_var - :no-index: - -Visualization -~~~~~~~~~~~~~ - -The plotting methods generate fully responsive HTML output that automatically adapts to different screen sizes and device orientations. Plots are optimized for both desktop and mobile viewing with separate title containers and responsive CSS styling. - -.. automethod:: OpenTimeSeries.plot_series - :no-index: -.. automethod:: OpenTimeSeries.plot_bars - :no-index: -.. automethod:: OpenTimeSeries.plot_histogram - :no-index: - -Export Methods -~~~~~~~~~~~~~~ - -.. automethod:: OpenTimeSeries.to_json - :no-index: -.. automethod:: OpenTimeSeries.to_xlsx - :no-index: - -Utility Functions ------------------ - -.. autofunction:: timeseries_chain - :no-index: diff --git a/docs/build/html/_sources/api/simulation.rst.txt b/docs/build/html/_sources/api/simulation.rst.txt deleted file mode 100644 index 59ee70fc..00000000 --- a/docs/build/html/_sources/api/simulation.rst.txt +++ /dev/null @@ -1,22 +0,0 @@ -Simulation -========== - -.. currentmodule:: openseries.simulation - -.. automodule:: openseries.simulation - :members: - :undoc-members: - :show-inheritance: - :no-index: - -ReturnSimulation Class ----------------------- - -.. autoclass:: ReturnSimulation - :members: - :undoc-members: - :show-inheritance: - :no-index: - :special-members: __init__ - -The ReturnSimulation class is used to create simulated financial time series for testing and analysis purposes. It provides methods to generate realistic return patterns based on statistical distributions. diff --git a/docs/build/html/_sources/api/types.rst.txt b/docs/build/html/_sources/api/types.rst.txt deleted file mode 100644 index f0d22e58..00000000 --- a/docs/build/html/_sources/api/types.rst.txt +++ /dev/null @@ -1,107 +0,0 @@ -Types and Enums -=============== - -.. currentmodule:: openseries.owntypes - -.. automodule:: openseries.owntypes - :members: - :undoc-members: - :show-inheritance: - :no-index: - -Value Types ------------ - -.. autoclass:: ValueType - :members: - :undoc-members: - :show-inheritance: - :no-index: - -The ValueType enum identifies the type of values in a time series (prices, returns, etc.). - -Type Aliases ------------- - -.. autodata:: SeriesOrFloat_co -.. autodata:: CountryStringType -.. autodata:: CountrySetType -.. autodata:: CountriesType -.. autodata:: CurrencyStringType -.. autodata:: DateStringType -.. autodata:: DateListType -.. autodata:: ValueListType -.. autodata:: DaysInYearType -.. autodata:: DateType - -Literal Types -------------- - -.. autodata:: LiteralJsonOutput -.. autodata:: LiteralTrunc -.. autodata:: LiteralLinePlotMode -.. autodata:: LiteralHowMerge -.. autodata:: LiteralQuantileInterp -.. autodata:: LiteralBizDayFreq -.. autodata:: LiteralPandasReindexMethod -.. autodata:: LiteralNanMethod -.. autodata:: LiteralCaptureRatio -.. autodata:: LiteralBarPlotMode -.. autodata:: LiteralPlotlyOutput -.. autodata:: LiteralPlotlyJSlib -.. autodata:: LiteralPlotlyHistogramPlotType -.. autodata:: LiteralPlotlyHistogramBarMode -.. autodata:: LiteralPlotlyHistogramCurveType -.. autodata:: LiteralPlotlyHistogramHistNorm -.. autodata:: LiteralPortfolioWeightings -.. autodata:: LiteralMinimizeMethods -.. autodata:: LiteralSeriesProps -.. autodata:: LiteralFrameProps - -Validation Classes ------------------- - -.. autoclass:: Countries - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: Currency - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: PropertiesList - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: OpenTimeSeriesPropertiesList - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: OpenFramePropertiesList - :members: - :undoc-members: - :show-inheritance: - -Custom Exceptions ------------------ - -.. autoexception:: MixedValuetypesError -.. autoexception:: AtLeastOneFrameError -.. autoexception:: DateAlignmentError -.. autoexception:: NumberOfItemsAndLabelsNotSameError -.. autoexception:: InitialValueZeroError -.. autoexception:: CountriesNotStringNorListStrError -.. autoexception:: MarketsNotStringNorListStrError -.. autoexception:: TradingDaysNotAboveZeroError -.. autoexception:: BothStartAndEndError -.. autoexception:: NoWeightsError -.. autoexception:: LabelsNotUniqueError -.. autoexception:: RatioInputError -.. autoexception:: MergingResultedInEmptyError -.. autoexception:: IncorrectArgumentComboError -.. autoexception:: PropertiesInputValidationError -.. autoexception:: ResampleDataLossError diff --git a/docs/build/html/_sources/development/changelog.rst.txt b/docs/build/html/_sources/development/changelog.rst.txt deleted file mode 100644 index e962d128..00000000 --- a/docs/build/html/_sources/development/changelog.rst.txt +++ /dev/null @@ -1,21 +0,0 @@ -Changelog -========= - -GitHub Releases ---------------- - -For details on changes, please visit the `GitHub Releases page `_. - -Release Notifications ---------------------- - -Stay updated on new releases: - -**GitHub** - Watch the `openseries repository `_ for release notifications - -**PyPI** - Monitor `openseries on PyPI `_ for new versions - -**Conda-forge** - Track updates on `conda-forge `_ diff --git a/docs/build/html/_sources/development/contributing.rst.txt b/docs/build/html/_sources/development/contributing.rst.txt deleted file mode 100644 index cfb3b523..00000000 --- a/docs/build/html/_sources/development/contributing.rst.txt +++ /dev/null @@ -1,450 +0,0 @@ -Contributing to openseries -========================== - -We welcome contributions to openseries! This guide will help you get started with contributing to the project. - -Getting Started ---------------- - -Development Setup -~~~~~~~~~~~~~~~~~ - -1. Fork the repository on GitHub -2. Clone your fork locally: - -.. code-block:: bash - - git clone https://github.com/yourusername/openseries.git - cd openseries - -3. Create the development environment. This installs the pinned uv version - (``uv==0.11.21``), syncs locked ``dev`` and ``docs`` dependencies from - ``uv.lock``, and installs pre-commit hooks: - -.. code-block:: bash - - make install - -On Windows: - -.. code-block:: powershell - - .\make.ps1 make - -Development Workflow -~~~~~~~~~~~~~~~~~~~~ - -1. Create a new branch for your feature or bug fix: - -.. code-block:: bash - - git checkout -b feature/your-feature-name - -2. Make your changes -3. Run tests to ensure everything works: - -.. code-block:: bash - - make test - -4. Run linting and type checking: - -.. code-block:: bash - - make lint - -5. Commit your changes: - -.. code-block:: bash - - git add . - git commit -m "Add your descriptive commit message" - -6. Push to your fork: - -.. code-block:: bash - - git push origin feature/your-feature-name - -7. Create a pull request on GitHub - -Code Standards --------------- - -Code Style -~~~~~~~~~~ - -openseries uses several tools to maintain code quality: - -- **Ruff**: For linting and code formatting -- **mypy**: For static type checking -- **pre-commit**: For automated checks before commits - -The configuration for these tools is in ``pyproject.toml``. - -Type Hints -~~~~~~~~~~ - -All new code should include proper type hints: - -.. code-block:: python - - def calculate_returns(prices: list[float]) -> list[float]: - """Calculate simple returns from prices.""" - returns = [] - for i in range(1, len(prices)): - ret = (prices[i] / prices[i-1]) - 1 - returns.append(ret) - return returns - -Docstrings -~~~~~~~~~~ - -Use Google-style docstrings for all public functions and classes: - -.. code-block:: python - - def calculate_sharpe_ratio(returns: list[float], risk_free_rate: float = 0.0) -> float: - """Calculate the Sharpe ratio. - - Args: - returns: List of periodic returns. - risk_free_rate: Risk-free rate for the same period. Defaults to 0.0. - - Returns: - The Sharpe ratio. - - Raises: - ValueError: If returns list is empty. - - Example: - >>> returns = [0.01, 0.02, -0.01, 0.03] - >>> sharpe = calculate_sharpe_ratio(returns) - >>> print(f"Sharpe ratio: {sharpe:.3f}") - """ - if not returns: - raise ValueError("Returns list cannot be empty") - - mean_return = sum(returns) / len(returns) - std_dev = (sum((r - mean_return) ** 2 for r in returns) / len(returns)) ** 0.5 - - if std_dev == 0: - return 0.0 - - return (mean_return - risk_free_rate) / std_dev - -Testing -------- - -Test Structure -~~~~~~~~~~~~~~ - -Tests are located in the ``tests/`` directory and use pytest: - -.. code-block:: bash - - tests/ - ├── __init__.py - ├── test_series.py - ├── test_frame.py - ├── test_portfoliotools.py - └── ... - -Writing Tests -~~~~~~~~~~~~~ - -Write comprehensive tests for new functionality: - -.. code-block:: python - - import pytest - import pandas as pd - from pandas.testing import assert_frame_equal - from openseries import OpenTimeSeries - - class TestOpenTimeSeries: - """Test cases for OpenTimeSeries class.""" - - def test_from_arrays_basic(self): - """Test basic creation from arrays.""" - dates = ['2023-01-01', '2023-01-02', '2023-01-03'] - values = [100.0, 102.0, 99.0] - - series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test") - - if series.label != "Test": - msg = f"Expected name 'Test', got '{series.label}'" - raise ValueError(msg) - if series.length != 3: - msg = f"Expected length 3, got {series.length}" - raise ValueError(msg) - if series.first_idx != pd.Timestamp('2023-01-01').date(): - msg = f"Expected first_idx 2023-01-01, got {series.first_idx}" - raise ValueError(msg) - if series.last_idx != pd.Timestamp('2023-01-03').date(): - msg = f"Expected last_idx 2023-01-03, got {series.last_idx}" - raise ValueError(msg) - - def test_from_arrays_invalid_dates(self): - """Test that invalid dates raise appropriate errors.""" - with pytest.raises(ValueError): - OpenTimeSeries.from_arrays( - dates=['invalid-date'], - values=[100.0], - name="Test" - ) - - def test_calculate_returns(self): - """Test return calculation.""" - dates = ['2023-01-01', '2023-01-02', '2023-01-03'] - values = [100.0, 102.0, 99.0] - - series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test") - series.value_to_ret() # Modifies original - - expected_returns = [0.02, -0.0294117647] # Approximate - actual_returns = series.values - - if len(actual_returns) != 2: - msg = f"Expected 2 returns, got {len(actual_returns)}" - raise ValueError(msg) - # Use tolerance-based comparison - if abs(actual_returns[0] - expected_returns[0]) >= 1e-6: - msg = f"First return mismatch: {actual_returns[0]} vs {expected_returns[0]}" - raise ValueError(msg) - if abs(actual_returns[1] - expected_returns[1]) >= 1e-6: - msg = f"Second return mismatch: {actual_returns[1]} vs {expected_returns[1]}" - raise ValueError(msg) - -Running Tests -~~~~~~~~~~~~~ - -Run all tests: - -.. code-block:: bash - - make test - -Run specific test files: - -.. code-block:: bash - - pytest tests/test_series.py - -Run tests with coverage: - -.. code-block:: bash - - pytest --cov=openseries tests/ - -Test Coverage -~~~~~~~~~~~~~ - -openseries maintains high test coverage (>99%). New code should include comprehensive tests: - -- Test normal use cases -- Test edge cases -- Test error conditions -- Test with different data types and sizes - -Documentation -------------- - -Documentation Standards -~~~~~~~~~~~~~~~~~~~~~~~ - -- All public APIs must be documented -- Include examples in docstrings where helpful -- Update relevant documentation files when adding features -- Use clear, concise language - -Building Documentation -~~~~~~~~~~~~~~~~~~~~~~ - -To build documentation locally: - -.. code-block:: bash - - cd docs - make html - -The built documentation will be in ``docs/_build/html/``. - -Contributing Guidelines ------------------------ - -Pull Request Process -~~~~~~~~~~~~~~~~~~~~ - -1. **Fork and Branch**: Create a feature branch from ``master`` -2. **Develop**: Make your changes with tests and documentation -3. **Test**: Ensure all tests pass and coverage remains high -4. **Lint**: Run linting and fix any issues -5. **Document**: Update documentation as needed -6. **Commit**: Use clear, descriptive commit messages -7. **Pull Request**: Create a PR with a clear description - -Commit Messages -~~~~~~~~~~~~~~~ - -Use clear, descriptive commit messages: - -.. code-block:: text - - Add support for custom business day calendars - - - Implement custom calendar functionality in datefixer module - - Add tests for various calendar configurations - - Update documentation with examples - - Fixes #123 - -Code Review Process -~~~~~~~~~~~~~~~~~~~ - -All contributions go through code review: - -1. Automated checks must pass (tests, linting, type checking) -2. At least one maintainer review is required -3. Address any feedback or requested changes -4. Once approved, the PR will be merged - -Types of Contributions ----------------------- - -Bug Reports -~~~~~~~~~~~ - -When reporting bugs, please include: - -- Clear description of the issue -- Steps to reproduce -- Expected vs. actual behavior -- Environment details (Python version, OS, etc.) -- Minimal code example if possible - -Feature Requests -~~~~~~~~~~~~~~~~ - -For new features: - -- Describe the use case and motivation -- Provide examples of how it would be used -- Consider backward compatibility -- Discuss implementation approach if you have ideas - -Code Contributions -~~~~~~~~~~~~~~~~~~ - -Areas where contributions are especially welcome: - -- **New financial metrics**: Additional risk measures, performance ratios -- **Data sources**: Integration with new data providers -- **Visualization**: Enhanced plotting capabilities -- **Performance**: Optimization of calculations -- **Documentation**: Examples, tutorials, API documentation - -Documentation Contributions -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Documentation improvements are always welcome: - -- Fix typos or unclear explanations -- Add examples to existing documentation -- Create new tutorials or guides -- Improve API documentation - -Development Environment ------------------------ - -IDE Setup -~~~~~~~~~ - -For VS Code, recommended extensions: - -- Python -- Pylance -- Ruff -- mypy - -Recommended settings in ``.vscode/settings.json``: - -.. code-block:: json - - { - "python.defaultInterpreterPath": "venv/bin/python", - "python.linting.enabled": true, - "python.linting.ruffEnabled": true, - "python.formatting.provider": "ruff", - "python.typeChecking": "strict" - } - -Debugging -~~~~~~~~~ - -For debugging tests: - -.. code-block:: bash - - pytest --pdb tests/test_specific.py::test_function - -For debugging with VS Code, create ``.vscode/launch.json``: - -.. code-block:: json - - { - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Current File", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal" - }, - { - "name": "Python: Pytest", - "type": "python", - "request": "launch", - "module": "pytest", - "args": ["${workspaceFolder}/tests"], - "console": "integratedTerminal" - } - ] - } - -Release Process ---------------- - -openseries follows semantic versioning (MAJOR.MINOR.PATCH): - -- **MAJOR**: Breaking changes -- **MINOR**: New features, backward compatible -- **PATCH**: Bug fixes, backward compatible - -Releases are managed by maintainers and include: - -1. Version bump in ``pyproject.toml`` -2. Update ``CHANGELOG.md`` -3. Create GitHub release with release notes -4. Publish to PyPI and conda-forge - -Getting Help ------------- - -If you need help with contributing: - -- Check existing issues and discussions on GitHub -- Ask questions in GitHub Discussions -- Reach out to maintainers - -Community Guidelines --------------------- - -openseries is committed to providing a welcoming and inclusive environment: - -- Be respectful and constructive in all interactions -- Focus on what is best for the community -- Show empathy towards other community members -- Welcome newcomers and help them get started - -Thank you for contributing to openseries! diff --git a/docs/build/html/_sources/examples/custom_reports.rst.txt b/docs/build/html/_sources/examples/custom_reports.rst.txt deleted file mode 100644 index 0c71c645..00000000 --- a/docs/build/html/_sources/examples/custom_reports.rst.txt +++ /dev/null @@ -1,75 +0,0 @@ -Reporting -========= - -This example demonstrates how to create analysis reports using openseries and the built-in report functionality. - -Using the Built-in HTML Report -------------------------------- - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries, OpenFrame, report_html - import pandas as pd - - # Load sample data for comparison - tickers = ["AAPL", "MSFT", "GOOGL", "SPY"] - names = ["Apple", "Microsoft", "Google", "S&P 500"] - - series_list = [] - for ticker, name in zip(tickers, names): - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=name) - series_list.append(series) - - # Create frame for report - comparison_frame = OpenFrame(constituents=series_list) - - # Generate HTML report - # The last asset in the frame is used as the benchmark - figure, filepath = report_html( - data=comparison_frame, - output_type="file", - filename="stock_comparison_report.html" - ) - - # filepath contains the path to the saved HTML file - print(f"Report saved to: {filepath}") - - # The figure object can be used for further customization if needed - # figure.show() # Display the figure interactively - -Embedding Reports in Existing HTML Pages ------------------------------------------ - -When you need to embed a report in an existing HTML page, use ``output_type="div"``: - -.. code-block:: python - - # Generate HTML div section for embedding - figure, html_div = report_html( - data=comparison_frame, - output_type="div" - ) - - # html_div contains the responsive HTML div section - # that can be embedded in your existing HTML page - # It includes both desktop and mobile layouts with CSS and JavaScript - - # Example: Save to a custom HTML template - html_template = f""" - - - - My Custom Report - - -

Portfolio Analysis Report

- {html_div} - - - """ - - with open("custom_report.html", "w", encoding="utf-8") as f: - f.write(html_template) diff --git a/docs/build/html/_sources/examples/multi_asset.rst.txt b/docs/build/html/_sources/examples/multi_asset.rst.txt deleted file mode 100644 index e36327b9..00000000 --- a/docs/build/html/_sources/examples/multi_asset.rst.txt +++ /dev/null @@ -1,321 +0,0 @@ -Multi-Asset Analysis -==================== - -This example shows how to analyze multiple assets simultaneously using OpenFrame. - -Setting Up Multi-Asset Analysis --------------------------------- - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries, OpenFrame - - # Define asset universe - assets = { - "AAPL": "Apple Inc.", - "GOOGL": "Alphabet Inc.", - "MSFT": "Microsoft Corp.", - "AMZN": "Amazon.com Inc.", - "TSLA": "Tesla Inc.", - "NVDA": "NVIDIA Corp.", - "META": "Meta Platforms Inc.", - "NFLX": "Netflix Inc." - } - - # Download data for all assets - series_list = [] - for ticker, name in assets.items(): - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df( - dframe=data['Close'] - ) - series.set_new_label(lvl_zero=name) - series_list.append(series) - print(f"Loaded {name}: {series.length} observations") - - # Create OpenFrame - tech_stocks = OpenFrame(constituents=series_list) - print(f"\nCreated frame with {tech_stocks.item_count} assets") - print(f"Common period: {tech_stocks.first_idx} to {tech_stocks.last_idx}") - -Comparative Analysis --------------------- - -.. code-block:: python - - # Get metrics for all assets - all_metrics = tech_stocks.all_properties() - print("=== COMPARATIVE METRICS ===") - print(all_metrics) - - # Focus on key metrics - key_metrics = all_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']] - key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] - - # Convert to percentages for better readability - percentage_metrics = key_metrics.copy() - percentage_metrics.loc[['Annual Return', 'Volatility', 'Max Drawdown']] *= 100 - - print("\n=== KEY METRICS COMPARISON ===") - print(percentage_metrics.round(2)) - -Ranking Analysis ----------------- - -.. code-block:: python - - # Rank assets by different criteria using openseries metrics - # Get key metrics for ranking - returns = all_metrics.loc['Geometric return'] - volatilities = all_metrics.loc['Volatility'] - sharpe_ratios = all_metrics.loc['Return vol ratio'] - drawdowns = all_metrics.loc['Max drawdown'] - - print("\n=== ASSET RANKINGS ===") - print("Ranked by Return (highest first):") - for i, (asset, ret) in enumerate(returns.sort_values(ascending=False).items(), 1): - print(f" {i}. {asset}: {ret:.2%}") - - print("\nRanked by Volatility (lowest first):") - for i, (asset, vol) in enumerate(volatilities.sort_values(ascending=True).items(), 1): - print(f" {i}. {asset}: {vol:.2%}") - - print("\nRanked by Sharpe Ratio (highest first):") - for i, (asset, sharpe) in enumerate(sharpe_ratios.sort_values(ascending=False).items(), 1): - print(f" {i}. {asset}: {sharpe:.2f}") - - print("\nRanked by Max Drawdown (least negative first):") - for i, (asset, dd) in enumerate(drawdowns.sort_values(ascending=False).items(), 1): - print(f" {i}. {asset}: {dd:.2%}") - -Correlation Analysis --------------------- - -.. code-block:: python - - # Calculate correlation matrix - correlation_matrix = tech_stocks.correl_matrix - print("\n=== CORRELATION MATRIX ===") - print(correlation_matrix.round(3)) - - # Find most and least correlated pairs - corr_pairs = [] - for i in range(len(correlation_matrix.columns)): - for j in range(i+1, len(correlation_matrix.columns)): - asset1 = correlation_matrix.columns[i] - asset2 = correlation_matrix.columns[j] - corr = correlation_matrix.iloc[i, j] - corr_pairs.append((asset1, asset2, corr)) - - # Sort by correlation - corr_pairs.sort(key=lambda x: x[2], reverse=True) - - print("\n=== HIGHEST CORRELATIONS ===") - for asset1, asset2, corr in corr_pairs[:5]: - print(f"{asset1} - {asset2}: {corr:.3f}") - - print("\n=== LOWEST CORRELATIONS ===") - for asset1, asset2, corr in corr_pairs[-5:]: - print(f"{asset1} - {asset2}: {corr:.3f}") - -Risk-Return Analysis --------------------- - -.. code-block:: python - - # Analyze risk-return using openseries metrics - returns = all_metrics.loc['Geometric return'] - volatilities = all_metrics.loc['Volatility'] - sharpe_ratios = all_metrics.loc['Return vol ratio'] - - print("\n=== RISK-RETURN ANALYSIS ===") - for asset in returns.index: - ret_pct = returns[asset] * 100 - vol_pct = volatilities[asset] * 100 - sharpe = sharpe_ratios[asset] - print(f"{asset}: Return={ret_pct:.2f}%, Volatility={vol_pct:.2f}%, Sharpe={sharpe:.2f}") - - # Identify efficient assets (high return per unit risk) - # Calculate 75th percentile threshold manually - sorted_sharpes = sorted(sharpe_ratios.values, reverse=True) - threshold_idx = int(len(sorted_sharpes) * 0.25) - efficient_threshold = sorted_sharpes[threshold_idx] if threshold_idx < len(sorted_sharpes) else sorted_sharpes[-1] - - print(f"\n=== MOST EFFICIENT ASSETS (Sharpe >= {efficient_threshold:.2f}) ===") - for asset, sharpe in sharpe_ratios.items(): - if sharpe >= efficient_threshold: - print(f"{asset}: {sharpe:.2f}") - -Sector/Style Analysis ---------------------- - -.. code-block:: python - - # Group assets by characteristics (example grouping) - asset_groups = { - 'Mega Cap': ['Apple Inc.', 'Microsoft Corp.', 'Alphabet Inc.', 'Amazon.com Inc.'], - 'Growth': ['Tesla Inc.', 'NVIDIA Corp.', 'Netflix Inc.'], - 'Social Media': ['Meta Platforms Inc.'] - } - - print("\n=== GROUP ANALYSIS ===") - for group_name, group_assets in asset_groups.items(): - # Filter assets that exist in our data - group_series = [s for s in tech_stocks.constituents if s.label in group_assets] - - if group_series: - group_frame = OpenFrame(constituents=group_series) - group_metrics = group_frame.all_properties() - - avg_return = group_metrics.loc['Geometric return'].mean() - avg_vol = group_metrics.loc['Volatility'].mean() - avg_sharpe = group_metrics.loc['Return vol ratio'].mean() - - print(f"\n{group_name} ({len(group_series)} assets):") - print(f" Average Return: {avg_return:.2%}") - print(f" Average Volatility: {avg_vol:.2%}") - print(f" Average Sharpe: {avg_sharpe:.2f}") - -Time Series Analysis --------------------- - -.. code-block:: python - - # Rolling correlation analysis - # Pick two assets for detailed analysis - apple = next(s for s in tech_stocks.constituents if "Apple" in s.label) - microsoft = next(s for s in tech_stocks.constituents if "Microsoft" in s.label) - - pair_frame = OpenFrame(constituents=[apple, microsoft]) - rolling_corr = pair_frame.rolling_corr(observations=252) # 1-year rolling - - print(f"\n=== ROLLING CORRELATION: {apple.label} vs {microsoft.label} ===") - print(f"Current correlation: {rolling_corr.iloc[-1, 0]:.3f}") - print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}") - print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}") - -Performance Attribution ------------------------ - -.. code-block:: python - - # Create equal-weighted portfolio for attribution - portfolio_df = tech_stocks.make_portfolio(name="Tech Portfolio", weight_strat="eq_weights") - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"\n=== PORTFOLIO vs INDIVIDUAL ASSETS ===") - print(f"Portfolio Return: {portfolio.geo_ret:.2%}") - print(f"Portfolio Volatility: {portfolio.vol:.2%}") - print(f"Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}") - - # Compare with individual assets using OpenFrame - asset_metrics = tech_stocks.all_properties() - individual_returns = asset_metrics.loc['Geometric return'].values - individual_vols = asset_metrics.loc['Volatility'].values - - print(f"\nDiversification benefit:") - equal_weights = [1/tech_stocks.item_count] * tech_stocks.item_count - # Calculate weighted average manually - weighted_avg_return = sum(ret * w for ret, w in zip(individual_returns, equal_weights)) - weighted_avg_vol = sum(vol * w for vol, w in zip(individual_vols, equal_weights)) - print(f" Weighted avg return: {weighted_avg_return:.2%}") - print(f" Portfolio return: {portfolio.geo_ret:.2%}") - print(f" Weighted avg volatility: {weighted_avg_vol:.2%}") - print(f" Portfolio volatility: {portfolio.vol:.2%}") - print(f" Volatility reduction: {(weighted_avg_vol - portfolio.vol):.2%}") - -Stress Testing --------------- - -.. code-block:: python - - # Identify worst market days (modifies original) - market_proxy = tech_stocks.constituents[0] # Use first asset as market proxy - market_proxy.value_to_ret() - market_data = market_proxy.tsdf - # Find worst 5% of days - worst_threshold = market_data.quantile(0.05) - worst_days = market_data[market_data <= worst_threshold] - - print(f"\n=== STRESS TEST ANALYSIS ===") - print(f"Market stress threshold: {worst_threshold:.2%}") - print(f"Number of stress days: {len(worst_days)}") - - # Analyze each asset's performance during stress - print("\nAsset performance during market stress:") - for series in tech_stocks.constituents: - series.value_to_ret() # Modifies original - asset_data = series.tsdf - # Get returns on stress days - stress_returns = asset_data.loc[worst_days.index] - avg_stress_return = stress_returns.mean() - - print(f" {series.label}: {avg_stress_return:.2%}") - -Export Multi-Asset Results --------------------------- - -.. code-block:: python - - # Export using openseries native methods - # Export frame data - tech_stocks.to_xlsx('multi_asset_analysis.xlsx') - - # Note: For comprehensive Excel export with multiple sheets, - # you can use the DataFrame returned by all_properties() and correl_matrix - # which are pandas DataFrames and support to_excel() method - print("\nMulti-asset analysis exported to 'multi_asset_analysis.xlsx'") - -Complete Multi-Asset Analysis Workflow ---------------------------------------- - -Here's how to perform a complete multi-asset analysis using openseries methods directly: - -.. code-block:: python - - # Example: Analyze tech stocks using openseries methods - tech_tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"] - - # Load data using openseries methods - series_list = [] - for ticker in tech_tickers: - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=ticker) - series_list.append(series) - - if not series_list: - print("No data loaded") - else: - # Create frame using openseries - frame = OpenFrame(constituents=series_list) - - # Analysis using openseries properties and methods - print(f"=== MULTI-ASSET ANALYSIS ===") - print(f"Assets: {frame.item_count}") - print(f"Period: {frame.first_idx} to {frame.last_idx}") - - # Key metrics using openseries all_properties method - key_metrics = frame.all_properties( - properties=['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown'] - ) - - print("\nKey Metrics:") - print((key_metrics * 100).round(2)) # Convert to percentages - - # Correlations using openseries correl_matrix property - correlations = frame.correl_matrix - avg_correlation = correlations.mean().mean() - print(f"\nAverage correlation: {avg_correlation:.3f}") - - # Create portfolio using openseries make_portfolio method - portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights") - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"\nEqual-weight portfolio:") - print(f" Return: {portfolio.geo_ret:.2%}") - print(f" Volatility: {portfolio.vol:.2%}") - print(f" Sharpe: {portfolio.ret_vol_ratio:.2f}") diff --git a/docs/build/html/_sources/examples/portfolio_optimization.rst.txt b/docs/build/html/_sources/examples/portfolio_optimization.rst.txt deleted file mode 100644 index c1eba979..00000000 --- a/docs/build/html/_sources/examples/portfolio_optimization.rst.txt +++ /dev/null @@ -1,555 +0,0 @@ -Portfolio Optimization -====================== - -This example demonstrates various portfolio optimization techniques using openseries, including both theoretical approaches and real-world applications with actual fund data. - -Basic Portfolio Optimization Setup ------------------------------------ - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries, OpenFrame - from openseries import efficient_frontier, simulate_portfolios - - # Define investment universe - universe = { - "VTI": "Total Stock Market", - "VEA": "Developed Markets", - "VWO": "Emerging Markets", - "BND": "Total Bond Market", - "VNQ": "Real Estate", - "VDE": "Energy", - "VGT": "Technology", - "VHT": "Healthcare" - } - - # Load data - assets = [] - for ticker, name in universe.items(): - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="5y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=name) - assets.append(series) - print(f"Loaded {name}") - - # Create investment universe frame - investment_universe = OpenFrame(constituents=assets) - print(f"\nInvestment universe: {investment_universe.item_count} assets") - print(f"Period: {investment_universe.first_idx} to {investment_universe.last_idx}") - -Mean-Variance Optimization --------------------------- - -.. code-block:: python - - # Calculate efficient frontier - # This may fail with various exceptions - frontier_df, simulated_df, optimal_portfolio = efficient_frontier( - eframe=investment_universe, - num_ports=100, - seed=42 - ) - - print("=== EFFICIENT FRONTIER RESULTS ===") - print(f"Generated {len(frontier_df)} efficient portfolios") - print(f"Simulated {len(simulated_df)} random portfolios") - - # Find key portfolios - returns = frontier_df['ret'] - volatilities = frontier_df['stdev'] - sharpe_ratios = returns / volatilities - - # Maximum Sharpe ratio portfolio - max_sharpe_idx = sharpe_ratios.idxmax() - max_sharpe_weights = optimal_portfolio[-len(investment_universe.constituents):] - - print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===") - print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}") - print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}") - print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}") - - print("\nOptimal Weights:") - for i, weight in enumerate(max_sharpe_weights): - asset_name = investment_universe.constituents[i].label - if weight > 0.01: # Only show weights > 1% - print(f" {asset_name}: {weight:.1%}") - - # Minimum volatility portfolio - min_vol_idx = volatilities.idxmin() - min_vol_weights = frontier_df.iloc[min_vol_idx][investment_universe.columns_lvl_zero].values - - print(f"\n=== MINIMUM VOLATILITY PORTFOLIO ===") - print(f"Expected Return: {min_vol_row['ret']:.2%}") - print(f"Volatility: {min_vol_row['stdev']:.2%}") - print(f"Sharpe Ratio: {sharpe_ratios.iloc[min_vol_idx]:.2f}") - - print("\nMinimum Volatility Weights:") - for col in investment_universe.columns_lvl_zero: - weight = min_vol_row[col] - if weight > 0.01: - print(f" {col}: {weight:.1%}") - -Monte Carlo Portfolio Simulation --------------------------------- - -.. code-block:: python - - # Generate random portfolios - # This may fail with various exceptions - simulation_results = simulate_portfolios( - simframe=investment_universe, - num_ports=50000, - seed=42 - ) - - print(f"\n=== MONTE CARLO SIMULATION ===") - print(f"Simulated {len(simulation_results)} random portfolios") - - sim_returns = simulation_results['ret'].values - sim_volatilities = simulation_results['stdev'].values - sim_sharpe_ratios = sim_returns / sim_volatilities - - # Statistics of simulated portfolios - print(f"\nSimulation Statistics:") - print(f"Return range: {sim_returns.min():.2%} to {sim_returns.max():.2%}") - print(f"Volatility range: {sim_volatilities.min():.2%} to {sim_volatilities.max():.2%}") - print(f"Sharpe range: {sim_sharpe_ratios.min():.2f} to {sim_sharpe_ratios.max():.2f}") - - # Best portfolios from simulation - sorted_indices = sorted(range(len(sim_sharpe_ratios)), key=lambda i: sim_sharpe_ratios.iloc[i], reverse=True) - top_sharpe_indices = sorted_indices[:5] - - print(f"\n=== TOP 5 SIMULATED PORTFOLIOS ===") - for i, idx in enumerate(reversed(top_sharpe_indices)): - print(f"\nRank {i+1}:") - print(f" Return: {sim_returns[idx]:.2%}") - print(f" Volatility: {sim_volatilities[idx]:.2%}") - print(f" Sharpe: {sim_sharpe_ratios[idx]:.2f}") - - weights = simulation_results.iloc[idx][investment_universe.columns_lvl_zero].values - print(" Weights:") - for j, weight in enumerate(weights): - if weight > 0.05: # Only show weights > 5% - asset_name = investment_universe.constituents[j].label - print(f" {asset_name}: {weight:.1%}") - -Risk-Based Portfolio Strategies -------------------------------- - -Equal Weight Portfolio -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Equal weight portfolio using native weight_strat - equal_weight_portfolio_df = investment_universe.make_portfolio( - name="Equal Weight", - weight_strat="eq_weights" - ) - equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df) - - print(f"\n=== EQUAL WEIGHT PORTFOLIO ===") - print(f"Return: {equal_weight_portfolio.geo_ret:.2%}") - print(f"Volatility: {equal_weight_portfolio.vol:.2%}") - print(f"Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}") - -Inverse Volatility Portfolio -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Inverse volatility weighting using native weight_strat - inv_vol_portfolio_df = investment_universe.make_portfolio( - name="Inverse Volatility", - weight_strat="inv_vol" - ) - inv_vol_portfolio = OpenTimeSeries.from_df(dframe=inv_vol_portfolio_df) - - print(f"\n=== INVERSE VOLATILITY PORTFOLIO ===") - print(f"Return: {inv_vol_portfolio.geo_ret:.2%}") - print(f"Volatility: {inv_vol_portfolio.vol:.2%}") - print(f"Sharpe: {inv_vol_portfolio.ret_vol_ratio:.2f}") - - -Maximum Diversification Portfolio -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The maximum diversification strategy aims to maximize portfolio diversification by optimizing the correlation structure. This strategy can encounter numerical issues in certain scenarios: - -.. code-block:: python - - # Maximum diversification portfolio using native weight_strat - # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError - max_div_portfolio_df = investment_universe.make_portfolio( - name="Maximum Diversification", - weight_strat="max_div" - ) - max_div_portfolio = OpenTimeSeries.from_df(dframe=max_div_portfolio_df) - - print(f"\n=== MAXIMUM DIVERSIFICATION PORTFOLIO ===") - print(f"Return: {max_div_portfolio.geo_ret:.2%}") - print(f"Volatility: {max_div_portfolio.vol:.2%}") - print(f"Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}") - -Minimum Volatility Overweight Portfolio ----------------------------------------- - -.. code-block:: python - - # Minimum volatility overweight portfolio using native weight_strat - min_vol_portfolio_df = investment_universe.make_portfolio( - name="Min Vol Overweight", - weight_strat="min_vol_overweight" - ) - min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df) - - print(f"\n=== MINIMUM VOLATILITY OVERWEIGHT PORTFOLIO ===") - print(f"Return: {min_vol_portfolio.geo_ret:.2%}") - print(f"Volatility: {min_vol_portfolio.vol:.2%}") - print(f"Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}") - -Portfolio Comparison --------------------- - -.. code-block:: python - - # Compare all portfolio strategies - portfolios = [ - equal_weight_portfolio, - inv_vol_portfolio, - max_div_portfolio, - min_vol_portfolio - ] - - # Add optimized portfolios if available - if 'max_sharpe_weights' in locals(): - investment_universe.weights = max_sharpe_weights.tolist() - max_sharpe_portfolio_df = investment_universe.make_portfolio( - name="Max Sharpe (Optimized)" - ) - max_sharpe_portfolio = OpenTimeSeries.from_df(dframe=max_sharpe_portfolio_df) - portfolios.append(max_sharpe_portfolio) - - if 'min_vol_weights' in locals(): - investment_universe.weights = min_vol_weights.tolist() - min_vol_portfolio_df = investment_universe.make_portfolio( - name="Min Vol (Optimized)" - ) - min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df) - portfolios.append(min_vol_portfolio) - - # Create comparison frame - comparison_frame = OpenFrame(constituents=portfolios) - comparison_metrics = comparison_frame.all_properties() - - # Display key metrics - key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']] - key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] - - print(f"\n=== PORTFOLIO STRATEGY COMPARISON ===") - print((key_metrics * 100).round(2)) # Convert to percentages - -Weight Strategy Details -~~~~~~~~~~~~~~~~~~~~~~~ - -The openseries library provides several built-in weight strategies for portfolio construction: - -**Equal Weights (``eq_weights``)** - - Assigns equal weight to all assets - - Most robust strategy, always works - - Good baseline for comparison - -**Inverse Volatility (``inv_vol``)** - - Weights assets inversely to their volatility - - Lower volatility assets get higher weights - - Generally stable and reliable - -**Maximum Diversification (``max_div``)** - - Optimizes correlation structure for maximum diversification - - Can encounter numerical issues with certain data patterns - - May produce negative weights in some scenarios - - Raises ``MaxDiversificationNaNError`` for numerical issues - - Raises ``MaxDiversificationNegativeWeightsError`` for negative weights - -**Minimum Volatility Overweight (``min_vol_overweight``)** - - Overweights the least volatile asset (60% weight) - - Distributes remaining 40% equally among other assets - - Based on the low volatility anomaly - -**Exception Handling** - When using the maximum diversification strategy, it's recommended to handle potential exceptions: - - .. code-block:: python - - from openseries.owntypes import ( - MaxDiversificationNaNError, - MaxDiversificationNegativeWeightsError - ) - - # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError - portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div") - -Backtesting Framework ---------------------- - -.. code-block:: python - - # Define strategies to backtest using native weight_strat - strategies = { - 'Equal Weight': 'eq_weights', - 'Inverse Volatility': 'inv_vol', - 'Max Diversification': 'max_div', - 'Min Vol Overweight': 'min_vol_overweight' - } - - # Run backtest using native strategies - backtest_results = {} - for strategy_name, weight_strat in strategies.items(): - # This may fail with MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError, or other exceptions - portfolio_df = investment_universe.make_portfolio( - name=strategy_name, - weight_strat=weight_strat - ) - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - backtest_results[strategy_name] = { - 'return': portfolio.geo_ret, - 'volatility': portfolio.vol, - 'sharpe': portfolio.ret_vol_ratio, - 'max_drawdown': portfolio.max_drawdown, - 'calmar': portfolio.geo_ret / abs(portfolio.max_drawdown) if portfolio.max_drawdown != 0 else float('nan') - } - - print(f"\n=== BACKTEST RESULTS ===") - for strategy_name, metrics in backtest_results.items(): - print(f"\n{strategy_name}:") - print(f" Return: {metrics['return']:.4f}") - print(f" Volatility: {metrics['volatility']:.4f}") - print(f" Sharpe: {metrics['sharpe']:.4f}") - print(f" Max Drawdown: {metrics['max_drawdown']:.4f}") - print(f" Calmar: {metrics['calmar']:.4f}") - - # Rank strategies - sorted_strategies = sorted(backtest_results.items(), key=lambda x: x[1]['sharpe'], reverse=True) - best_strategy = sorted_strategies[0][0] - - print(f"\nBest performing strategy: {best_strategy}") - print(f"Sharpe ratio: {sorted_strategies[0][1]['sharpe']:.3f}") - -Export Optimization Results ---------------------------- - -.. code-block:: python - - # Export using openseries native methods - # Export frame data - investment_universe.to_xlsx('portfolio_optimization_results.xlsx') - - # Note: For comprehensive Excel export with multiple sheets, - # the DataFrames returned by all_properties() and correl_matrix - # are pandas DataFrames and support to_excel() method - print("\nOptimization results exported to 'portfolio_optimization_results.xlsx'") - -Real-World Fund Portfolio Optimization ---------------------------------------- - -This section demonstrates portfolio optimization using actual fund data from professional fund managers, showing how optimization techniques apply in practice. - -Using Real Fund Data for Optimization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's how to work with real fund data using openseries methods directly: - -.. code-block:: python - - from requests import get as requests_get - from openseries import ( - OpenTimeSeries, OpenFrame, ValueType, - efficient_frontier, prepare_plot_data, sharpeplot, - load_plotly_dict, get_previous_business_day_before_today - ) - - # Define fund universe for optimization - fund_universe_isins = [ - "SE0015243886", # Global High Yield - "SE0011337195", # Global Equity - "SE0011670843", # Global Bond - "SE0017832280", # Alternative Strategy - "SE0017832330", # Multi-Asset Strategy - ] - - # Load fund data using openseries methods - response = requests_get(url="https://api.captor.se/public/api/nav", timeout=10) - response.raise_for_status() - - series_list = [] - result = response.json() - - for data in result: - if data["isin"] in fund_universe_isins: - series = OpenTimeSeries.from_arrays( - name=data["longName"], - isin=data["isin"], - baseccy=data["currency"], - dates=data["dates"], - values=data["navPerUnit"], - valuetype=ValueType.PRICE, - ) - series_list.append(series) - - # Create fund universe using openseries OpenFrame - fund_universe = OpenFrame(constituents=series_list) - - # Process data using openseries methods - fund_universe = fund_universe.value_nan_handle().trunc_frame().to_cumret() - - print(f"Fund universe created with {fund_universe.item_count} funds") - print(f"Analysis period: {fund_universe.first_idx} to {fund_universe.last_idx}") - -Advanced Optimization with Real Data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Set optimization parameters - simulations = 10000 - frontier_points = 50 - seed = 55 - - # Create current portfolio (equal weights) - current_portfolio_df = fund_universe.make_portfolio( - name="Current Portfolio", - weight_strat="eq_weights", - ) - current_portfolio = OpenTimeSeries.from_df(dframe=current_portfolio_df) - - # Calculate efficient frontier - frontier, simulated_portfolios, optimal_portfolio = efficient_frontier( - eframe=fund_universe, - num_ports=simulations, - seed=seed, - frontier_points=frontier_points, - ) - - # Prepare visualization data - plot_data = prepare_plot_data( - assets=fund_universe, - current=current_portfolio, - optimized=optimal_portfolio, - ) - - # Load plotly configuration - figdict, _ = load_plotly_dict() - - # Create efficient frontier plot - optimization_plot, _ = sharpeplot( - sim_frame=simulated_portfolios, - line_frame=frontier, - point_frame=plot_data, - point_frame_mode="markers+text", - title="Real Fund Portfolio Optimization", - add_logo=False, - auto_open=False, - output_type="div", - ) - optimization_plot = optimization_plot.update_layout(width=1200, height=700) - - # Display the optimization results - optimization_plot.show(config=figdict["config"]) - -Performance Comparison Analysis -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Compare different portfolio strategies - strategies = {} - - # Equal weight portfolio - equal_weight_portfolio_df = fund_universe.make_portfolio( - name="Equal Weight", weight_strat="eq_weights" - ) - equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df) - strategies['Equal Weight'] = equal_weight_portfolio - - # Optimal portfolio from efficient frontier - fund_universe.weights = optimal_portfolio[-fund_universe.item_count:].tolist() - optimal_portfolio_df = fund_universe.make_portfolio(name="Optimal Portfolio") - optimal_portfolio_series = OpenTimeSeries.from_df(dframe=optimal_portfolio_df) - strategies['Optimal Portfolio'] = optimal_portfolio_series - - # Create comparison frame - comparison_frame = OpenFrame(constituents=list(strategies.values())) - comparison_metrics = comparison_frame.all_properties() - - # Display key metrics - key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']] - key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] - - print("=== PORTFOLIO STRATEGY COMPARISON ===") - print((key_metrics * 100).round(2)) - - # Calculate improvement metrics - improvement = { - 'Return Improvement': (optimal_portfolio_series.geo_ret - equal_weight_portfolio.geo_ret) * 100, - 'Volatility Change': (optimal_portfolio_series.vol - equal_weight_portfolio.vol) * 100, - 'Sharpe Improvement': optimal_portfolio_series.ret_vol_ratio - equal_weight_portfolio.ret_vol_ratio, - } - - print("\n=== OPTIMIZATION IMPROVEMENTS ===") - for metric, value in improvement.items(): - print(f"{metric}: {value:+.2f}") - -Complete Optimization Workflow ------------------------------- - -Here's how to perform portfolio optimization using openseries methods directly: - -.. code-block:: python - - # Example: Optimize ETF portfolio using openseries methods - etf_tickers = ["VTI", "VEA", "VWO", "BND", "VNQ"] - - # Load data using openseries methods - assets = [] - for ticker in etf_tickers: - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="5y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=ticker) - assets.append(series) - - if len(assets) < 2: - print("Need at least 2 assets for optimization") - else: - frame = OpenFrame(constituents=assets) - - # Use openseries native weight strategies - strategies = { - 'Equal Weight': 'eq_weights', - 'Inverse Volatility': 'inv_vol', - 'Max Diversification': 'max_div', - 'Min Vol Overweight': 'min_vol_overweight' - } - - # Create portfolios using openseries make_portfolio method - results = {} - for name, weight_strat in strategies.items(): - # This may fail with MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError, or other exceptions - portfolio_df = frame.make_portfolio(name=name, weight_strat=weight_strat) - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - results[name] = { - 'Return': portfolio.geo_ret, - 'Volatility': portfolio.vol, - 'Sharpe': portfolio.ret_vol_ratio, - 'Max Drawdown': portfolio.max_drawdown - } - - print("=== PORTFOLIO OPTIMIZATION RESULTS ===") - for name, metrics in results.items(): - print(f"\n{name}:") - print(f" Return: {metrics['Return']*100:.2f}%") - print(f" Volatility: {metrics['Volatility']*100:.2f}%") - print(f" Sharpe: {metrics['Sharpe']:.2f}") - print(f" Max Drawdown: {metrics['Max Drawdown']*100:.2f}%") diff --git a/docs/build/html/_sources/examples/single_asset.rst.txt b/docs/build/html/_sources/examples/single_asset.rst.txt deleted file mode 100644 index b44ab219..00000000 --- a/docs/build/html/_sources/examples/single_asset.rst.txt +++ /dev/null @@ -1,197 +0,0 @@ -Single Asset Analysis -===================== - -This example demonstrates comprehensive analysis of a single financial asset using openseries. - -Basic Setup ------------ - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries - import numpy as np - - # Download Apple stock data - ticker = yf.Ticker("AAPL") - data = ticker.history(period="5y") - - # Create OpenTimeSeries - apple = OpenTimeSeries.from_df( - dframe=data['Close'] - ) - - # Set descriptive label - apple.set_new_label(lvl_zero="Apple Inc. (AAPL)") - - print(f"Loaded {apple.length} observations") - print(f"Date range: {apple.first_idx} to {apple.last_idx}") - -Performance Analysis --------------------- - -.. code-block:: python - - # Basic performance metrics - print("=== PERFORMANCE METRICS ===") - print(f"Total Return: {apple.value_ret:.2%}") - print(f"Annualized Return: {apple.geo_ret:.2%}") - print(f"Annualized Volatility: {apple.vol:.2%}") - print(f"Sharpe Ratio: {apple.ret_vol_ratio:.2f}") - - # Get all metrics at once - all_metrics = apple.all_properties() - print("\n=== ALL METRICS ===") - print(all_metrics) - -Risk Analysis -------------- - -.. code-block:: python - - # Risk metrics - print("=== RISK ANALYSIS ===") - print(f"Maximum Drawdown: {apple.max_drawdown:.2%}") - print(f"Max Drawdown Date: {apple.max_drawdown_date}") - print(f"95% VaR (daily): {apple.var_down:.2%}") - print(f"95% CVaR (daily): {apple.cvar_down:.2%}") - print(f"Worst Single Day: {apple.worst:.2%}") - print(f"Sortino Ratio: {apple.sortino_ratio:.2f}") - -Time Series Transformations ---------------------------- - -.. code-block:: python - - # Convert to returns (modifies original) - apple.value_to_ret() - print(f"Returns series length: {apple.length}") - - # Create drawdown series (modifies original) - apple.to_drawdown_series() - - # Convert to log returns (modifies original) - apple.value_to_log() - - # Resample to monthly (modifies original) - apple.resample_to_business_period_ends(freq="BME") - print(f"Monthly data points: {apple.length}") - -Rolling Analysis ----------------- - -.. code-block:: python - - # Rolling volatility (1-year window) - rolling_vol = apple.rolling_vol(observations=252) - print(f"Current 1Y volatility: {rolling_vol.iloc[-1, 0]:.2%}") - print(f"Average 1Y volatility: {rolling_vol.mean().iloc[0]:.2%}") - - # Rolling returns (30-day) - rolling_returns = apple.rolling_return(observations=30) - - # Rolling VaR - rolling_var = apple.rolling_var_down(observations=252) - -Visualization -------------- - -.. code-block:: python - - # Plot price series - fig, _ = apple.plot_series() - - # Plot returns histogram - fig, _ = apple_returns.plot_histogram() - - # Plot drawdown series - fig, _ = apple_drawdowns.plot_series() - -Calendar Analysis ------------------ - -.. code-block:: python - - # Annual returns by calendar year - years = [2019, 2020, 2021, 2022, 2023, 2024] - - print("=== CALENDAR YEAR RETURNS ===") - for year in years: - # This may fail if no data exists for the year - year_return = apple.value_ret_calendar_period(year=year) - print(f"{year}: {year_return:.2%}") - -Export Results --------------- - -.. code-block:: python - - # Export to Excel - apple.to_xlsx("apple_analysis.xlsx") - - # Export metrics to CSV - all_metrics.to_csv("apple_metrics.csv") - - # Export to JSON - apple.to_json("apple_data.json") - -Complete Analysis Workflow ----------------------------- - -Here's how to perform comprehensive single asset analysis using openseries methods directly: - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries - - # Example: Analyze Apple stock using openseries methods - ticker_symbol = "AAPL" - - # Download data using openseries methods - ticker = yf.Ticker(ticker_symbol) - data = ticker.history(period="5y") - - # Create series using openseries from_df method - series = OpenTimeSeries.from_df( - dframe=data['Close'], - name=ticker_symbol - ) - - # Analysis using openseries properties and methods - print(f"=== {ticker_symbol} ANALYSIS ===") - print(f"Period: {series.first_idx} to {series.last_idx}") - print(f"Observations: {series.length}") - - # Key metrics using openseries properties - metrics = { - 'Total Return': f"{series.value_ret:.2%}", - 'Annual Return': f"{series.geo_ret:.2%}", - 'Volatility': f"{series.vol:.2%}", - 'Sharpe Ratio': f"{series.ret_vol_ratio:.2f}", - 'Max Drawdown': f"{series.max_drawdown:.2%}", - '95% VaR': f"{series.var_down:.2%}", - 'Skewness': f"{series.skew:.2f}", - 'Kurtosis': f"{series.kurtosis:.2f}" - } - - for metric, value in metrics.items(): - print(f"{metric}: {value}") - - # Export results using openseries to_xlsx method - filename = f"{ticker_symbol.lower()}_analysis.xlsx" - series.to_xlsx(filename) - print(f"\nResults exported to {filename}") - - # Example: Analyze multiple assets - tickers = ["AAPL", "TSLA", "MSFT"] - for ticker_symbol in tickers: - ticker = yf.Ticker(ticker_symbol) - data = ticker.history(period="2y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=ticker_symbol) - - print(f"\n{ticker_symbol}:") - print(f" Return: {series.geo_ret:.2%}") - print(f" Volatility: {series.vol:.2%}") - print(f" Sharpe: {series.ret_vol_ratio:.2f}") diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt deleted file mode 100644 index 1ccc713b..00000000 --- a/docs/build/html/_sources/index.rst.txt +++ /dev/null @@ -1,152 +0,0 @@ -openseries Documentation -======================== - -.. image:: https://img.shields.io/pypi/v/openseries.svg - :target: https://pypi.org/project/openseries/ - :alt: PyPI version - -.. image:: https://img.shields.io/conda/vn/conda-forge/openseries.svg - :target: https://anaconda.org/conda-forge/openseries - :alt: Conda Version - -.. image:: https://img.shields.io/badge/platforms-Windows%20%7C%20macOS%20%7C%20Linux-blue - :alt: Platform - -.. image:: https://img.shields.io/pypi/pyversions/openseries.svg - :target: https://www.python.org/ - :alt: Python version - -.. image:: https://github.com/CaptorAB/openseries/actions/workflows/test.yml/badge.svg - :target: https://github.com/CaptorAB/openseries/actions/workflows/test.yml - :alt: GitHub Action Test Suite - -.. image:: https://img.shields.io/codecov/c/gh/CaptorAB/openseries?logo=codecov - :target: https://codecov.io/gh/CaptorAB/openseries/branch/master - :alt: codecov - -.. image:: https://img.shields.io/github/license/CaptorAB/openseries - :target: https://github.com/CaptorAB/openseries/blob/master/LICENSE.md - :alt: GitHub License - -**openseries** is a Python library for analyzing financial time series data. It provides tools to work with single assets or groups of assets, designed specifically for daily or less frequent data. - -The library is built around two main classes: - -- **OpenTimeSeries**: For managing and analyzing individual time series -- **OpenFrame**: For managing groups of time series and portfolio analysis - -Key Features ------------- - -- **Financial Analysis**: Comprehensive set of financial metrics and ratios -- **Risk Management**: VaR, CVaR, drawdown analysis, and risk-adjusted returns -- **Portfolio Tools**: Portfolio optimization, rebalancing, and performance attribution -- **Visualization**: Interactive plots using Plotly -- **Data Handling**: Robust date handling and business day calendars -- **Type Safety**: Built with Pydantic for data validation and type safety - -Quick Start ------------ - -Install openseries using pip: - -.. code-block:: bash - - pip install openseries - -Or using conda: - -.. code-block:: bash - - conda install -c conda-forge openseries - -Here's a simple example to get you started: - -.. code-block:: python - - from openseries import OpenTimeSeries - import yfinance as yf - - # Download data - ticker = yf.Ticker("^GSPC") - history = ticker.history(period="5y") - - # Create OpenTimeSeries - series = OpenTimeSeries.from_df(dframe=history.loc[:, "Close"]) - series.set_new_label(lvl_zero="S&P 500") - - # Calculate key metrics - print(f"Annual Return: {series.geo_ret:.2%}") - print(f"Volatility: {series.vol:.2%}") - print(f"Sharpe Ratio: {series.ret_vol_ratio:.2f}") - print(f"Max Drawdown: {series.max_drawdown:.2%}") - - # Create interactive plot - series.plot_series() - -Documentation Contents ----------------------- - -.. toctree:: - :maxdepth: 2 - :caption: User Guide - - user_guide/installation - user_guide/quickstart - user_guide/core_concepts - user_guide/data_handling - -.. toctree:: - :maxdepth: 2 - :caption: Tutorials - - tutorials/basic_analysis - tutorials/portfolio_analysis - tutorials/risk_management - tutorials/advanced_features - -.. toctree:: - :maxdepth: 2 - :caption: Examples - - examples/single_asset - examples/multi_asset - examples/portfolio_optimization - examples/rebalanced_portfolio - examples/custom_reports - -.. toctree:: - :maxdepth: 1 - :caption: Important Notes - - api_consistency - -Python Version Support ----------------------- - -.. toctree:: - :maxdepth: 2 - :caption: API Reference - - api/openseries - api/series - api/frame - api/portfoliotools - api/simulation - api/report - api/datefixer - api/types - -.. toctree:: - :maxdepth: 1 - :caption: Development - - development/contributing - development/changelog - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/build/html/_sources/tutorials/advanced_features.rst.txt b/docs/build/html/_sources/tutorials/advanced_features.rst.txt deleted file mode 100644 index bf4cf172..00000000 --- a/docs/build/html/_sources/tutorials/advanced_features.rst.txt +++ /dev/null @@ -1,227 +0,0 @@ -Advanced Features -================= - -This tutorial covers advanced openseries features including custom analysis, integration with other libraries, and extending functionality. - -Factor Analysis and Regression ------------------------------- - -Multi-Factor Model Analysis -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries, OpenFrame - - # Load factor data (Fama-French factors would be ideal, using proxies here) - factor_tickers = { - "^GSPC": "Market", - "^RUT": "Small Cap", # Size factor proxy - "EFA": "International", # International factor - "TLT": "Bonds" # Interest rate factor - } - - # Load factor data - factor_series = [] - for ticker, name in factor_tickers.items(): - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=name) - factor_series.append(series) - - # Create factor frame - factors = OpenFrame(constituents=factor_series) - - # Load individual stock for analysis - stock_data = yf.Ticker("AAPL").history(period="3y") - apple = OpenTimeSeries.from_df(dframe=stock_data['Close']) - apple.set_new_label(lvl_zero="Apple") - - # Add stock to factor frame for regression - analysis_frame = OpenFrame(constituents=factor_series + [apple]) - - # Perform multi-factor regression - # This may fail with various exceptions - regression_results = analysis_frame.multi_factor_linear_regression( - dependent_variable_idx=-1 # Apple is the last series (dependent variable) - ) - - print("\n=== MULTI-FACTOR REGRESSION RESULTS ===") - print("Regression Summary:") - print(regression_results['summary']) - - print("\nFactor Loadings (Betas):") - for i, factor_name in enumerate([s.label for s in factor_series]): - beta = regression_results['coefficients'][i+1] # Skip intercept - print(f" {factor_name}: {beta:.4f}") - - print(f"\nR-squared: {regression_results['r_squared']:.4f}") - print(f"Adjusted R-squared: {regression_results['adj_r_squared']:.4f}") - -Rolling Factor Analysis -~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Rolling beta analysis with market - market_series = factor_series[0] # S&P 500 - stock_vs_market = OpenFrame(constituents=[apple, market_series]) - - # Calculate rolling beta - rolling_beta = stock_vs_market.rolling_beta(observations=252) # 1-year rolling - - print(f"\n=== ROLLING BETA ANALYSIS ===") - print(f"Current Beta: {rolling_beta.iloc[-1, 0]:.3f}") - print(f"Average Beta: {rolling_beta.mean().iloc[0]:.3f}") - print(f"Beta Range: {rolling_beta.min().iloc[0]:.3f} to {rolling_beta.max().iloc[0]:.3f}") - print(f"Beta Volatility: {rolling_beta.std().iloc[0]:.3f}") - - # Rolling correlation - rolling_corr = stock_vs_market.rolling_corr(observations=252) - - print(f"\n=== ROLLING CORRELATION ANALYSIS ===") - print(f"Current Correlation: {rolling_corr.iloc[-1, 0]:.3f}") - print(f"Average Correlation: {rolling_corr.mean().iloc[0]:.3f}") - print(f"Correlation Range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}") - -Exporting Custom Plotly Figures ---------------------------------- - -The ``export_plotly_figure`` function allows you to export any Plotly figure to a mobile-responsive HTML file. This is useful when you create custom visualizations using Plotly's graph objects that aren't directly available through openseries plotting methods. - -Creating Custom Plots -~~~~~~~~~~~~~~~~~~~~~ - -You can create any Plotly figure and export it using the same responsive HTML format that openseries uses internally: - -.. code-block:: python - - import plotly.graph_objects as go - from plotly.subplots import make_subplots - from openseries import export_plotly_figure - from pathlib import Path - - # Create a custom subplot figure - fig = make_subplots( - rows=2, cols=2, - subplot_titles=('Price Chart', 'Volume', 'Returns Distribution', 'Drawdown'), - specs=[[{"secondary_y": True}, {"type": "bar"}], - [{"type": "histogram"}, {"type": "scatter"}]] - ) - - # Add traces (example data) - fig.add_trace( - go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], name="Price"), - row=1, col=1 - ) - fig.add_trace( - go.Bar(x=[1, 2, 3, 4], y=[100, 200, 150, 300], name="Volume"), - row=1, col=2 - ) - fig.add_trace( - go.Histogram(x=[0.01, -0.02, 0.015, -0.01, 0.02], name="Returns"), - row=2, col=1 - ) - fig.add_trace( - go.Scatter(x=[1, 2, 3, 4], y=[0, -0.05, -0.03, -0.08], name="Drawdown"), - row=2, col=2 - ) - - # Update layout - fig.update_layout(height=800, title_text="Custom Multi-Panel Dashboard") - - # Export to responsive HTML - output_path = export_plotly_figure( - figure=fig, - fig_config={"responsive": True}, - output_type="file", - filename="custom_dashboard.html", - include_plotlyjs="cdn", - plotfile=Path("output/custom_dashboard.html"), - title="Custom Financial Dashboard", - auto_open=True, - ) - - print(f"Dashboard saved to: {output_path}") - -Using with Plotly Express -~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also use ``export_plotly_figure`` with Plotly Express figures: - -.. code-block:: python - - import plotly.express as px - import pandas as pd - from openseries import export_plotly_figure - from pathlib import Path - - # Create sample data - df = pd.DataFrame({ - 'Date': pd.date_range('2020-01-01', periods=100), - 'Asset_A': 100 + pd.Series(range(100)).cumsum() * 0.1, - 'Asset_B': 100 + pd.Series(range(100)).cumsum() * 0.15, - }) - - # Create a Plotly Express figure - fig = px.line( - df, x='Date', y=['Asset_A', 'Asset_B'], - title='Asset Comparison', - labels={'value': 'Price', 'variable': 'Asset'} - ) - - # Export with responsive HTML - export_plotly_figure( - figure=fig, - fig_config={"responsive": True, "displayModeBar": True}, - output_type="file", - filename="asset_comparison.html", - include_plotlyjs="cdn", - plotfile=Path("output/asset_comparison.html"), - title="Asset Price Comparison", - auto_open=False, - ) - -Inline HTML Output -~~~~~~~~~~~~~~~~~~ - -For embedding in web applications or reports, you can generate inline HTML divs: - -.. code-block:: python - - import plotly.graph_objects as go - from openseries import export_plotly_figure - - # Create a simple figure - fig = go.Figure(data=go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13])) - - # Generate inline HTML div - html_div = export_plotly_figure( - figure=fig, - fig_config={}, - output_type="div", - filename="my_plot.html", - include_plotlyjs="cdn", - plotfile=Path("dummy.html"), # Ignored for div output - ) - - # html_div can now be embedded in HTML documents - print(html_div[:100]) # Preview the HTML - -Benefits of export_plotly_figure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``export_plotly_figure`` function provides several advantages over Plotly's default HTML export: - -- **Mobile Responsive**: Automatically adapts to different screen sizes and device orientations -- **Optimized Viewport**: Proper viewport settings for mobile devices -- **Auto-Resize**: JavaScript handles window resizing and orientation changes -- **Consistent Styling**: Uses the same responsive CSS as openseries internal plots -- **Optional Title Container**: Can include a title and logo in a responsive header - -This makes it ideal for creating dashboards and reports that need to work well on both desktop and mobile devices. - - -This tutorial demonstrates how to extend openseries with advanced functionality for sophisticated financial analysis workflows. diff --git a/docs/build/html/_sources/tutorials/basic_analysis.rst.txt b/docs/build/html/_sources/tutorials/basic_analysis.rst.txt deleted file mode 100644 index f0bfa684..00000000 --- a/docs/build/html/_sources/tutorials/basic_analysis.rst.txt +++ /dev/null @@ -1,356 +0,0 @@ -Basic Financial Analysis -======================== - -This tutorial demonstrates how to perform fundamental financial analysis using openseries with real market data. - -Setting Up ----------- - -First, let's import the necessary libraries and download some data: - -.. code-block:: python - - import yfinance as yf - import pandas as pd - import numpy as np - from openseries import OpenTimeSeries, OpenFrame - from datetime import date, datetime - - # Download S&P 500 data for the last 5 years - ticker = yf.Ticker("^GSPC") - data = ticker.history(period="5y") - - # Create OpenTimeSeries - sp500 = OpenTimeSeries.from_df( - dframe=data['Close'] - ) - - # Set a descriptive label - sp500.set_new_label(lvl_zero="S&P 500 Index") - - print(f"Loaded {sp500.length} observations") - print(f"Date range: {sp500.first_idx} to {sp500.last_idx}") - -Basic Performance Metrics --------------------------- - -Let's calculate the fundamental performance metrics: - -.. code-block:: python - - # Total return over the period - total_return = sp500.value_ret - print(f"Total Return: {total_return:.2%}") - - # Annualized return (CAGR) - annual_return = sp500.geo_ret - print(f"Annualized Return (CAGR): {annual_return:.2%}") - - # Arithmetic mean return - arithmetic_return = sp500.arithmetic_ret - print(f"Arithmetic Mean Return: {arithmetic_return:.2%}") - - # Time period analysis - print(f"Investment period: {sp500.yearfrac:.2f} years") - print(f"Number of observations: {sp500.length}") - print(f"Periods per year: {sp500.periods_in_a_year:.1f}") - -Risk Analysis -------------- - -Now let's examine the risk characteristics: - -.. code-block:: python - - # Volatility (annualized standard deviation) - volatility = sp500.vol - print(f"Annualized Volatility: {volatility:.2%}") - - # Downside deviation (volatility of negative returns only) - downside_vol = sp500.downside_deviation - print(f"Downside Deviation: {downside_vol:.2%}") - - # Value at Risk (95% confidence level) - var_95 = sp500.var_down - print(f"95% Value at Risk (daily): {var_95:.2%}") - - # Conditional Value at Risk (Expected Shortfall) - cvar_95 = sp500.cvar_down - print(f"95% CVaR (daily): {cvar_95:.2%}") - - # Maximum single-day loss - worst_day = sp500.worst - print(f"Worst single day: {worst_day:.2%}") - -Risk-Adjusted Returns ---------------------- - -Calculate risk-adjusted performance metrics: - -.. code-block:: python - - # Sharpe Ratio (return per unit of total risk) - sharpe_ratio = sp500.ret_vol_ratio - print(f"Sharpe Ratio: {sharpe_ratio:.2f}") - - # Sortino Ratio (return per unit of downside risk) - sortino_ratio = sp500.sortino_ratio - print(f"Sortino Ratio: {sortino_ratio:.2f}") - - # Kappa-3 Ratio (penalizes larger downside deviations more) - kappa3_ratio = sp500.kappa3_ratio - print(f"Kappa-3 Ratio: {kappa3_ratio:.2f}") - - # Omega Ratio - omega_ratio = sp500.omega_ratio - print(f"Omega Ratio: {omega_ratio:.2f}") - -Drawdown Analysis ------------------ - -Analyze drawdowns to understand downside risk: - -.. code-block:: python - - # Maximum drawdown - max_drawdown = sp500.max_drawdown - max_dd_date = sp500.max_drawdown_date - print(f"Maximum Drawdown: {max_drawdown:.2%}") - print(f"Max Drawdown Date: {max_dd_date}") - - # Create drawdown series for visualization (modifies original) - sp500.to_drawdown_series() - - # Plot drawdowns - sp500.plot_series() - # This will open an interactive plot in your browser - - # Worst calendar year drawdown - worst_year_dd = sp500.max_drawdown_cal_year - print(f"Worst Calendar Year Drawdown: {worst_year_dd:.2%}") - -Distribution Analysis ---------------------- - -Examine the return distribution characteristics: - -.. code-block:: python - - # Convert to returns for distribution analysis (modifies original) - sp500.value_to_ret() - - # Note: value_to_ret() modifies the original series in place - # Restore the original series for further analysis - sp500 = OpenTimeSeries.from_df(dframe=data['Close']) - sp500.set_new_label(lvl_zero="S&P 500 Index") - - # Skewness (asymmetry of the distribution) - skewness = sp500.skew - print(f"Skewness: {skewness:.2f}") - if skewness < 0: - print(" → Negative skew: more extreme negative returns") - elif skewness > 0: - print(" → Positive skew: more extreme positive returns") - - # Kurtosis (tail heaviness) - kurtosis = sp500.kurtosis - print(f"Kurtosis: {kurtosis:.2f}") - if kurtosis > 3: - print(" → Fat tails: more extreme returns than normal distribution") - - # Percentage of positive days - positive_share = sp500.positive_share - print(f"Positive Days: {positive_share:.1%}") - - # Current Z-score (how unusual is the last return?) - z_score = sp500.z_score - print(f"Last Return Z-score: {z_score:.2f}") - -Monthly and Annual Analysis ---------------------------- - -Break down performance by different time periods: - -.. code-block:: python - - # Resample to monthly data (modifies original) - sp500.resample_to_business_period_ends(freq="BME") - print(f"Monthly observations: {sp500.length}") - - # Monthly metrics - monthly_return = sp500.geo_ret - monthly_vol = sp500.vol - print(f"Monthly Return (annualized): {monthly_return:.2%}") - print(f"Monthly Volatility (annualized): {monthly_vol:.2%}") - - # Worst month - worst_month = sp500.worst_month - print(f"Worst Month: {worst_month:.2%}") - - # Annual data (modifies original) - sp500.resample_to_business_period_ends(freq="BYE") - print(f"Annual observations: {sp500.length}") - -Calendar Year Returns -~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Calculate calendar year returns - years = range(2019, 2025) # Adjust based on your data range - - for year in years: - # This may fail if no data exists for the year - year_return = sp500.value_ret_calendar_period(year=year) - print(f"{year}: {year_return:.2%}") - -Rolling Analysis ----------------- - -Analyze how metrics change over time: - -.. code-block:: python - - # 252-day (1-year) rolling volatility - rolling_vol = sp500.rolling_vol(observations=252) - print(f"Rolling volatility calculated for {len(rolling_vol)} periods") - - # 30-day rolling returns - rolling_returns = sp500.rolling_return(observations=30) - - # Plot rolling volatility - # Convert to OpenTimeSeries for plotting - vol_dates = rolling_vol.index.strftime('%Y-%m-%d').tolist() - vol_values = rolling_vol.iloc[:, 0].tolist() - - vol_series = OpenTimeSeries.from_arrays( - dates=vol_dates, - values=vol_values, - name="Rolling Volatility" - ) - - vol_series.plot_series() - -Comprehensive Report --------------------- - -Get all metrics at once: - -.. code-block:: python - - # Generate comprehensive metrics report - all_metrics = sp500.all_properties() - print("\n=== COMPREHENSIVE ANALYSIS REPORT ===") - print(all_metrics) - - # Save to Excel for further analysis - sp500.to_xlsx(filename="sp500_analysis.xlsx") - all_metrics.to_excel(excel_writer="sp500_metrics.xlsx", engine="openpyxl") - -Visualization -------------- - -Create various visualizations: - -.. code-block:: python - - # Price chart - sp500.plot_series() - - # Returns bar plot and histogram - returns = sp500.from_deepcopy() - returns.value_to_ret() - returns.plot_bars() - returns.plot_histogram() - - # Drawdown chart - sp500.to_drawdown_series() - sp500.plot_series() - -Comparison with Benchmark -------------------------- - -Let's compare with a bond index: - -.. code-block:: python - - # Download bond data (10-year Treasury) - bond_ticker = yf.Ticker("^TNX") - bond_data = bond_ticker.history(period="5y") - - # Create bond series (using yield data) - bonds = OpenTimeSeries.from_df( - dframe=bond_data['Close'] - ) - bonds.set_new_label(lvl_zero="10Y Treasury Yield") - - # Create frame for comparison - comparison_frame = OpenFrame(constituents=[sp500, bonds]) - - # Compare metrics - comparison_metrics = comparison_frame.all_properties() - print("\n=== ASSET COMPARISON ===") - print(comparison_metrics) - - # Calculate correlation - correlation_matrix = comparison_frame.correl_matrix - print("\n=== CORRELATION MATRIX ===") - print(correlation_matrix) - -Advanced Risk Metrics ---------------------- - -Calculate some advanced risk measures: - -.. code-block:: python - - # VaR at different confidence levels - var_90 = sp500.var_down_func(level=0.90) - var_95 = sp500.var_down_func(level=0.95) - var_99 = sp500.var_down_func(level=0.99) - - print(f"90% VaR: {var_90:.2%}") - print(f"95% VaR: {var_95:.2%}") - print(f"99% VaR: {var_99:.2%}") - - # CVaR at different confidence levels - cvar_90 = sp500.cvar_down_func(level=0.90) - cvar_95 = sp500.cvar_down_func(level=0.95) - cvar_99 = sp500.cvar_down_func(level=0.99) - - print(f"90% CVaR: {cvar_90:.2%}") - print(f"95% CVaR: {cvar_95:.2%}") - print(f"99% CVaR: {cvar_99:.2%}") - - # Implied volatility from VaR (assuming normal distribution) - vol_from_var = sp500.vol_from_var - print(f"Volatility implied from VaR: {vol_from_var:.2%}") - print(f"Actual volatility: {sp500.vol:.2%}") - -Summary and Interpretation --------------------------- - -.. code-block:: python - - print("\n=== INVESTMENT SUMMARY ===") - print(f"Asset: {sp500.label}") - print(f"Period: {sp500.first_idx} to {sp500.last_idx}") - print(f"Total Return: {sp500.value_ret:.2%}") - print(f"Annualized Return: {sp500.geo_ret:.2%}") - print(f"Annualized Volatility: {sp500.vol:.2%}") - print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}") - print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}") - print(f"95% VaR (daily): {sp500.var_down:.2%}") - - # Risk assessment - if sp500.ret_vol_ratio > 1.0: - print("✓ Good risk-adjusted returns (Sharpe > 1.0)") - else: - print("⚠ Moderate risk-adjusted returns (Sharpe < 1.0)") - - if abs(sp500.max_drawdown) < 0.20: - print("✓ Moderate maximum drawdown (< 20%)") - else: - print("⚠ Significant maximum drawdown (> 20%)") - -This tutorial provides a comprehensive foundation for financial analysis using openseries. You can adapt these techniques for any financial time series data. diff --git a/docs/build/html/_sources/tutorials/portfolio_analysis.rst.txt b/docs/build/html/_sources/tutorials/portfolio_analysis.rst.txt deleted file mode 100644 index beb30f65..00000000 --- a/docs/build/html/_sources/tutorials/portfolio_analysis.rst.txt +++ /dev/null @@ -1,524 +0,0 @@ -Portfolio Analysis -================== - -This tutorial demonstrates how to construct and analyze portfolios using openseries, including optimization techniques and performance attribution. - -Setting Up the Data --------------------- - -Let's start by downloading data for a diversified set of assets: - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries, OpenFrame - from openseries import efficient_frontier, simulate_portfolios - - # Define our universe of assets - tickers = { - "^GSPC": "S&P 500", - "EFA": "EAFE International", - "EEM": "Emerging Markets", - "AGG": "US Aggregate Bonds", - "VNQ": "US REITs", - "GLD": "Gold", - "DBC": "Commodities" - } - - # Download 5 years of data - series_list = [] - for ticker, name in tickers.items(): - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="5y") - series = OpenTimeSeries.from_df( - dframe=data['Close'] - ) - series.set_new_label(lvl_zero=name) - series_list.append(series) - print(f"Loaded {name}: {series.length} observations") - - # Create OpenFrame - assets = OpenFrame(constituents=series_list) - print(f"\nCreated frame with {assets.item_count} assets") - print(f"Common date range: {assets.first_idx} to {assets.last_idx}") - -Asset Analysis --------------- - -First, let's analyze the individual assets: - -.. code-block:: python - - # Get metrics for all assets - asset_metrics = assets.all_properties() - print("=== INDIVIDUAL ASSET METRICS ===") - print(asset_metrics) - - # Key metrics comparison - returns = asset_metrics.loc['Geometric return'] - volatilities = asset_metrics.loc['Volatility'] - sharpe_ratios = asset_metrics.loc['Return vol ratio'] - max_drawdowns = asset_metrics.loc['Max drawdown'] - - print("\n=== ASSET COMPARISON ===") - for asset in returns.index: - print(f"{asset}:") - print(f" Annual Return: {returns[asset]:.2%}") - print(f" Volatility: {volatilities[asset]:.2%}") - print(f" Sharpe Ratio: {sharpe_ratios[asset]:.2f}") - print(f" Max Drawdown: {max_drawdowns[asset]:.2%}") - -Correlation Analysis --------------------- - -Understanding correlations is crucial for portfolio construction: - -.. code-block:: python - - # Calculate correlation matrix - correlation_matrix = assets.correl_matrix - print("\n=== CORRELATION MATRIX ===") - print(correlation_matrix.round(3)) - - # Identify highly correlated pairs - print("\n=== HIGHLY CORRELATED PAIRS (>0.7) ===") - for i in range(len(correlation_matrix.columns)): - for j in range(i+1, len(correlation_matrix.columns)): - corr = correlation_matrix.iloc[i, j] - if abs(corr) > 0.7: - asset1 = correlation_matrix.columns[i] - asset2 = correlation_matrix.columns[j] - print(f"{asset1} - {asset2}: {corr:.3f}") - - # Average correlation with other assets - avg_correlations = correlation_matrix.mean() - print("\n=== AVERAGE CORRELATIONS ===") - for asset, avg_corr in avg_correlations.items(): - print(f"{asset}: {avg_corr:.3f}") - -Simple Portfolio Construction ------------------------------ - -Let's start with basic portfolio construction methods: - -Equal Weight Portfolio -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Create equal-weighted portfolio using native weight_strat - portfolio_df = assets.make_portfolio(name="Equal Weight Portfolio", weight_strat="eq_weights") - equal_weight_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"Equal Weight Portfolio Return: {equal_weight_portfolio.geo_ret:.2%}") - print(f"Equal Weight Portfolio Volatility: {equal_weight_portfolio.vol:.2%}") - print(f"Equal Weight Portfolio Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}") - -Custom Weight Portfolio -~~~~~~~~~~~~~~~~~~~~~~~ - -You can also specify custom weights for portfolio construction: - -.. code-block:: python - - # Define custom weights (must sum to 1) - custom_weights = [0.50, 0.15, 0.10, 0.15, 0.05, 0.03, 0.02] - - assets.weights = custom_weights - portfolio_df = assets.make_portfolio(name="Custom Weighted") - custom_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"Custom Portfolio Return: {custom_portfolio.geo_ret:.2%}") - print(f"Custom Portfolio Volatility: {custom_portfolio.vol:.2%}") - print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}") - -Risk Parity Portfolio -~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Use native inverse volatility weighting (risk parity) - portfolio_df = assets.make_portfolio(name="Risk Parity", weight_strat="inv_vol") - risk_parity_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"Risk Parity Portfolio Return: {risk_parity_portfolio.geo_ret:.2%}") - print(f"Risk Parity Portfolio Volatility: {risk_parity_portfolio.vol:.2%}") - print(f"Risk Parity Portfolio Sharpe: {risk_parity_portfolio.ret_vol_ratio:.2f}") - -Advanced Weight Strategies -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -OpenSeries provides additional weight strategies beyond basic equal weighting and risk parity: - -Maximum Diversification Strategy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The maximum diversification strategy optimizes the correlation structure to maximize portfolio diversification: - -.. code-block:: python - - from openseries.owntypes import MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError - - # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError - max_div_portfolio_df = assets.make_portfolio( - name="Maximum Diversification", - weight_strat="max_div" - ) - max_div_portfolio = OpenTimeSeries.from_df(dframe=max_div_portfolio_df) - - print(f"Max Diversification Return: {max_div_portfolio.geo_ret:.2%}") - print(f"Max Diversification Volatility: {max_div_portfolio.vol:.2%}") - print(f"Max Diversification Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}") - -Minimum Volatility Overweight Strategy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The minimum volatility overweight strategy overweights the least volatile asset: - -.. code-block:: python - - # This may fail with various exceptions - min_vol_portfolio_df = assets.make_portfolio( - name="Min Vol Overweight", - weight_strat="min_vol_overweight" - ) - min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df) - - print(f"Min Vol Overweight Return: {min_vol_portfolio.geo_ret:.2%}") - print(f"Min Vol Overweight Volatility: {min_vol_portfolio.vol:.2%}") - print(f"Min Vol Overweight Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}") - -Strategy Comparison with Error Handling -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When comparing multiple strategies, it's important to handle potential failures gracefully: - -.. code-block:: python - - strategies = { - 'Equal Weight': 'eq_weights', - 'Risk Parity': 'inv_vol', - 'Max Diversification': 'max_div', - 'Min Vol Overweight': 'min_vol_overweight' - } - - results = {} - for name, strategy in strategies.items(): - # This may fail with MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError, or other exceptions - portfolio_df = assets.make_portfolio(name=name, weight_strat=strategy) - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - results[name] = { - 'Return': portfolio.geo_ret, - 'Volatility': portfolio.vol, - 'Sharpe': portfolio.ret_vol_ratio - } - - if results: - print("\n=== STRATEGY COMPARISON ===") - for strategy_name, metrics in results.items(): - print(f"\n{strategy_name}:") - print(f" Return: {metrics['return']*100:.2f}%") - print(f" Volatility: {metrics['volatility']*100:.2f}%") - print(f" Sharpe: {metrics['sharpe']:.2f}") - print(f" Max Drawdown: {metrics['max_drawdown']*100:.2f}%") - -Portfolio Optimization ----------------------- - -Now let's use openseries' optimization tools: - -Efficient Frontier -~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Calculate efficient frontier - # This may fail with various exceptions - frontier_df, simulated_df, optimal_portfolio = efficient_frontier( - eframe=assets, - num_ports=50, - seed=42 - ) - - print("Efficient frontier calculated successfully") - print(f"Number of frontier points: {len(frontier_df)}") - print(f"Number of simulated portfolios: {len(simulated_df)}") - - # Find maximum Sharpe ratio portfolio - sharpe_ratios = frontier_df['ret'] / frontier_df['stdev'] - max_sharpe_idx = sharpe_ratios.idxmax() - - print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===") - print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}") - print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}") - print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}") - - # Get optimal weights - optimal_weights = optimal_portfolio[-len(assets.constituents):] - print("\nOptimal Weights:") - for i, weight in enumerate(optimal_weights): - asset_name = assets.constituents[i].label - print(f" {asset_name}: {weight:.1%}") - -Monte Carlo Portfolio Simulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Simulate random portfolios - # This may fail with various exceptions - simulation_results = simulate_portfolios( - simframe=assets, - num_ports=10000, - seed=42 - ) - - print(f"\nSimulated {len(simulation_results)} random portfolios") - - # Find best performing portfolios - sim_sharpe_ratios = simulation_results['ret'] / simulation_results['stdev'] - - # Top 5 Sharpe ratios - sorted_indices = sorted(range(len(sim_sharpe_ratios)), key=lambda i: sim_sharpe_ratios.iloc[i], reverse=True) - top_indices = sorted_indices[:5] - - print("\n=== TOP 5 SIMULATED PORTFOLIOS ===") - for i, idx in enumerate(top_indices, 1): - print(f"\nRank {i}:") - print(f" Return: {simulation_results.iloc[idx]['ret']:.2%}") - print(f" Volatility: {simulation_results.iloc[idx]['stdev']:.2%}") - print(f" Sharpe: {sim_sharpe_ratios.iloc[idx]:.2f}") - -Portfolio Comparison --------------------- - -Let's compare all our portfolios: - -.. code-block:: python - - # Add all portfolios to a comparison frame - portfolios = [equal_weight_portfolio, market_cap_portfolio, risk_parity_portfolio] - - # Add individual assets for comparison - all_series = assets.constituents + portfolios - comparison_frame = OpenFrame(constituents=all_series) - - # Get comprehensive metrics - portfolio_metrics = comparison_frame.all_properties() - - # Focus on key metrics - key_metrics = portfolio_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']] - key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] - - print("\n=== PORTFOLIO COMPARISON ===") - print((key_metrics * 100).round(2)) # Convert to percentages - -Risk Attribution ----------------- - -Analyze the risk contribution of each asset: - -.. code-block:: python - - # Calculate portfolio statistics using openseries methods - # Create equal weight portfolio - equal_weight_portfolio_df = assets.make_portfolio(name="Equal Weight", weight_strat="eq_weights") - equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df) - - print("\n=== RISK ATTRIBUTION (Equal Weight Portfolio) ===") - print(f"Portfolio Volatility: {equal_weight_portfolio.vol:.4f}") - print(f"Portfolio Return: {equal_weight_portfolio.geo_ret:.4f}") - - # Individual asset contributions can be analyzed using openseries properties - for i, series in enumerate(assets.constituents): - weight = equal_weights[i] - asset_vol = series.vol - print(f"\n{series.label}:") - print(f" Weight: {weight:.4f}") - print(f" Individual Volatility: {asset_vol:.4f}") - print(f" Weighted Contribution: {weight * asset_vol:.4f}") - -Performance Attribution ------------------------ - -Analyze performance contribution over time: - -.. code-block:: python - - # Calculate performance attribution using openseries - # Individual asset performance is available through openseries properties - print("\n=== PERFORMANCE ATTRIBUTION ===") - for i, series in enumerate(assets.constituents): - weight = equal_weights[i] - asset_return = series.geo_ret - contribution = weight * asset_return - print(f"{series.label}:") - print(f" Weight: {weight:.2%}") - print(f" Return: {asset_return:.2%}") - print(f" Contribution: {contribution:.2%}") - - # Cumulative contribution - cumulative_contrib = (1 + weighted_returns).cumprod() - - print("\n=== PERFORMANCE ATTRIBUTION ===") - print("Final cumulative contribution by asset:") - final_contrib = cumulative_contrib.iloc[-1] - for asset, contrib in final_contrib.items(): - print(f" {asset}: {contrib:.3f}") - -Rolling Portfolio Analysis --------------------------- - -Analyze how portfolio characteristics change over time: - -.. code-block:: python - - # Rolling correlation with market (S&P 500) - market_proxy = assets.constituents[0] # Assuming first asset is S&P 500 - - # Create frame with portfolio and market - portfolio_vs_market = OpenFrame(constituents=[equal_weight_portfolio, market_proxy]) - - # Calculate rolling correlation - rolling_corr = portfolio_vs_market.rolling_corr(observations=252) # 1-year rolling - - print(f"\nRolling correlation calculated for {len(rolling_corr)} periods") - print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}") - print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}") - - # Rolling portfolio volatility - portfolio_rolling_vol = equal_weight_portfolio.rolling_vol(observations=252) - - print(f"\nRolling volatility statistics:") - print(f"Average volatility: {portfolio_rolling_vol.mean().iloc[0]:.2%}") - print(f"Volatility range: {portfolio_rolling_vol.min().iloc[0]:.2%} to {portfolio_rolling_vol.max().iloc[0]:.2%}") - -Rebalancing Analysis --------------------- - -Analyze the impact of rebalancing frequency using the realistic `rebalanced_portfolio` method: - -.. code-block:: python - - # Compare different rebalancing frequencies using realistic simulation - frequencies = [1, 21, 63] # Daily, monthly, quarterly - frequency_names = ["Daily", "Monthly", "Quarterly"] - - rebalanced_portfolios = [] - - for freq, name in zip(frequencies, frequency_names): - portfolio = assets.rebalanced_portfolio( - name=f"{name} Rebalanced", - frequency=freq, - bal_weights=equal_weights - ) - rebalanced_portfolios.append(portfolio.constituents[-1]) - - # Compare with theoretical portfolio - assets.weights = equal_weights - theoretical_portfolio_df = assets.make_portfolio(name="Theoretical") - theoretical_portfolio = OpenTimeSeries.from_df(dframe=theoretical_portfolio_df) - - # Create comprehensive comparison - all_portfolios = [theoretical_portfolio] + rebalanced_portfolios - comparison_frame = OpenFrame(constituents=all_portfolios) - comparison_metrics = comparison_frame.all_properties() - - print("\n=== REALISTIC REBALANCING COMPARISON ===") - print("Strategy | Return | Volatility | Sharpe | Max DD") - print("-" * 50) - - for series in all_portfolios: - ret = comparison_metrics.loc['Geometric return', series.label].iloc[0] * 100 - vol = comparison_metrics.loc['Volatility', series.label].iloc[0] * 100 - sharpe = comparison_metrics.loc['Return vol ratio', series.label].iloc[0] - max_dd = comparison_metrics.loc['Max drawdown', series.label].iloc[0] * 100 - - print(f"{series.label:>15} | {ret:6.2f}% | {vol:10.2f}% | {sharpe:6.2f} | {max_dd:6.2f}%") - - # Analyze transaction costs - print(f"\n=== TRANSACTION COST ANALYSIS ===") - for freq, name in zip(frequencies, frequency_names): - detailed_portfolio = assets.rebalanced_portfolio( - name=f"{name} Detailed", - frequency=freq, - bal_weights=equal_weights, - drop_extras=False # Get detailed trading data - ) - - # Count rebalancing events - rebalancing_days = 0 - for series in detailed_portfolio.constituents: - if "buysell_qty" in series.label: - # Count days with non-zero trading - trading_days = (series.tsdf != 0).any(axis=1).sum() - rebalancing_days = max(rebalancing_days, trading_days) - - print(f"{name:>15}: {rebalancing_days} rebalancing events") - -Stress Testing --------------- - -Test portfolio performance during market stress: - -.. code-block:: python - - # Identify worst periods for the market (modifies original) - market_proxy.value_to_ret() - market_returns_df = market_proxy.tsdf - - # Find worst 5% of days - worst_days_threshold = market_returns_df.quantile(0.05).iloc[0] - worst_days = market_returns_df[market_returns_df <= worst_days_threshold] - - print(f"\n=== STRESS TEST RESULTS ===") - print(f"Market stress threshold: {worst_days_threshold:.2%}") - print(f"Number of stress days: {len(worst_days)}") - - # Portfolio performance during stress (modifies original) - equal_weight_portfolio.value_to_ret() - portfolio_returns_df = equal_weight_portfolio.tsdf - - # Align dates and calculate portfolio performance during market stress - stress_dates = worst_days.index - portfolio_stress_returns = portfolio_returns_df.loc[stress_dates] - - print(f"Portfolio average return during stress: {portfolio_stress_returns.mean().iloc[0]:.2%}") - print(f"Portfolio worst day during stress: {portfolio_stress_returns.min().iloc[0]:.2%}") - -Summary Report --------------- - -Generate a comprehensive portfolio analysis report: - -.. code-block:: python - - print("\n" + "="*60) - print("PORTFOLIO ANALYSIS SUMMARY REPORT") - print("="*60) - - print(f"\nAnalysis Period: {assets.first_idx} to {assets.last_idx}") - print(f"Number of Assets: {assets.item_count}") - print(f"Asset Universe: {', '.join([s.label for s in assets.constituents])}") - - print(f"\n--- EQUAL WEIGHT PORTFOLIO PERFORMANCE ---") - print(f"Total Return: {equal_weight_portfolio.value_ret:.2%}") - print(f"Annualized Return: {equal_weight_portfolio.geo_ret:.2%}") - print(f"Annualized Volatility: {equal_weight_portfolio.vol:.2%}") - print(f"Sharpe Ratio: {equal_weight_portfolio.ret_vol_ratio:.2f}") - print(f"Maximum Drawdown: {equal_weight_portfolio.max_drawdown:.2%}") - print(f"95% VaR (daily): {equal_weight_portfolio.var_down:.2%}") - - print(f"\n--- PORTFOLIO CHARACTERISTICS ---") - avg_correlation = correlation_matrix.mean().mean() - print(f"Average Asset Correlation: {avg_correlation:.3f}") - print(f"Portfolio Diversification Benefit: {(asset_metrics.loc['Volatility'].mean() - equal_weight_portfolio.vol):.2%}") - - # Export results - portfolio_metrics.to_excel("portfolio_analysis.xlsx") - correlation_matrix.to_excel("correlation_matrix.xlsx") - - print(f"\nResults exported to Excel files") - print("Analysis complete!") - -This tutorial provides a comprehensive framework for portfolio analysis using openseries. You can extend these techniques for more sophisticated portfolio management strategies. diff --git a/docs/build/html/_sources/tutorials/risk_management.rst.txt b/docs/build/html/_sources/tutorials/risk_management.rst.txt deleted file mode 100644 index 776f51ad..00000000 --- a/docs/build/html/_sources/tutorials/risk_management.rst.txt +++ /dev/null @@ -1,536 +0,0 @@ -Risk Management -=============== - -This tutorial demonstrates comprehensive risk management techniques using openseries, including VaR calculations, stress testing, and risk monitoring. - -Setting Up Risk Analysis -------------------------- - -Let's start with a portfolio of assets for risk analysis: - -.. code-block:: python - - import yfinance as yf - from openseries import OpenTimeSeries, OpenFrame - from datetime import datetime, timedelta - import warnings - warnings.filterwarnings('ignore') - - # Download data for a mixed portfolio - tickers = { - "AAPL": "Apple Inc.", - "GOOGL": "Alphabet Inc.", - "MSFT": "Microsoft Corp.", - "TSLA": "Tesla Inc.", - "SPY": "SPDR S&P 500 ETF", - "QQQ": "Invesco QQQ Trust", - "TLT": "iShares 20+ Year Treasury", - "GLD": "SPDR Gold Shares" - } - - # Download 3 years of data - series_list = [] - for ticker, name in tickers.items(): - # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df( - dframe=data['Close'] - ) - series.set_new_label(lvl_zero=name) - series_list.append(series) - print(f"Loaded {name}: {series.length} observations") - - # Create portfolio frame - portfolio_assets = OpenFrame(constituents=series_list) - - # Create equal-weighted portfolio - n_assets = portfolio_assets.item_count - - # Set weights on the frame first - portfolio_df = portfolio_assets.make_portfolio( - name="Diversified Portfolio", - weight_strat="eq_weights" - ) - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"\nPortfolio created with {n_assets} assets") - print(f"Date range: {portfolio.first_idx} to {portfolio.last_idx}") - -Basic Risk Metrics ------------------- - -Start with fundamental risk measurements: - -.. code-block:: python - - print("=== BASIC RISK METRICS ===") - - # Volatility measures - print(f"Annualized Volatility: {portfolio.vol:.2%}") - print(f"Downside Deviation: {portfolio.downside_deviation:.2%}") - - # Return distribution - print(f"Skewness: {portfolio.skew:.3f}") - print(f"Kurtosis: {portfolio.kurtosis:.3f}") - - # Tail risk - print(f"Worst Single Day: {portfolio.worst:.2%}") - print(f"Worst Month: {portfolio.worst_month:.2%}") - - # Drawdown analysis - print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}") - print(f"Max Drawdown Date: {portfolio.max_drawdown_date}") - -Value at Risk (VaR) Analysis ------------------------------ - -Calculate VaR at different confidence levels: - -.. code-block:: python - - print("\n=== VALUE AT RISK ANALYSIS ===") - - # VaR at different confidence levels - confidence_levels = [0.90, 0.95, 0.99] - - for level in confidence_levels: - var_value = portfolio.var_down_func(level=level) - print(f"{level*100:.0f}% VaR (daily): {var_value:.2%}") - - # Convert daily VaR to different time horizons - # Assuming normal distribution and independence - daily_var_95 = portfolio.var_down_func(level=0.95) - - print(f"\n=== VaR TIME HORIZONS (95% confidence) ===") - print(f"1-day VaR: {daily_var_95:.2%}") - # Scale VaR to different time horizons - print(f"1-week VaR: {daily_var_95 * (5 ** 0.5):.2%}") - print(f"1-month VaR: {daily_var_95 * (22 ** 0.5):.2%}") - print(f"1-year VaR: {daily_var_95 * (252 ** 0.5):.2%}") - -Conditional Value at Risk (CVaR) --------------------------------- - -Analyze expected shortfall beyond VaR: - -.. code-block:: python - - print("\n=== CONDITIONAL VALUE AT RISK (CVaR) ===") - - for level in confidence_levels: - cvar_value = portfolio.cvar_down_func(level=level) - var_value = portfolio.var_down_func(level=level) - - print(f"{level*100:.0f}% CVaR: {cvar_value:.2%} (VaR: {var_value:.2%})") - print(f" Expected loss beyond VaR: {cvar_value - var_value:.2%}") - -Rolling Risk Analysis ---------------------- - -Monitor how risk changes over time: - -.. code-block:: python - - # Calculate rolling risk metrics - window = 252 # 1-year rolling window - - print(f"\n=== ROLLING RISK ANALYSIS ({window}-day window) ===") - - # Rolling volatility - rolling_vol = portfolio.rolling_vol(observations=window) - print(f"Rolling Volatility - Current: {rolling_vol.iloc[-1, 0]:.2%}") - print(f"Rolling Volatility - Average: {rolling_vol.mean().iloc[0]:.2%}") - print(f"Rolling Volatility - Range: {rolling_vol.min().iloc[0]:.2%} to {rolling_vol.max().iloc[0]:.2%}") - - # Rolling VaR - rolling_var = portfolio.rolling_var_down(observations=window) - print(f"Rolling VaR (95%) - Current: {rolling_var.iloc[-1, 0]:.2%}") - print(f"Rolling VaR (95%) - Average: {rolling_var.mean().iloc[0]:.2%}") - - # Rolling CVaR - rolling_cvar = portfolio.rolling_cvar_down(observations=window) - print(f"Rolling CVaR (95%) - Current: {rolling_cvar.iloc[-1, 0]:.2%}") - print(f"Rolling CVaR (95%) - Average: {rolling_cvar.mean().iloc[0]:.2%}") - -Stress Testing --------------- - -Test portfolio performance under extreme scenarios: - -Historical Stress Testing -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - print("\n=== HISTORICAL STRESS TESTING ===") - - # Convert to returns for analysis (modifies original) - portfolio.value_to_ret() - returns_data = portfolio.tsdf - - # Note: value_to_ret() modifies the original series in place - # Restore the original portfolio for further analysis - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - # Identify worst periods - worst_1_percent = returns_data.quantile(0.01).iloc[0] - worst_5_percent = returns_data.quantile(0.05).iloc[0] - - print(f"Worst 1% threshold: {worst_1_percent:.2%}") - print(f"Worst 5% threshold: {worst_5_percent:.2%}") - - # Count extreme events - extreme_events_1pct = (returns_data <= worst_1_percent).sum().iloc[0] - extreme_events_5pct = (returns_data <= worst_5_percent).sum().iloc[0] - - print(f"Days with returns <= 1% threshold: {extreme_events_1pct}") - print(f"Days with returns <= 5% threshold: {extreme_events_5pct}") - - # Worst consecutive days - simplified approach - print(f"\nWorst 5 single days:") - returns_series = returns_data.iloc[:, 0] # Get the first (and only) column - worst_5_days = returns_series.nsmallest(5) - for i, (date, return_val) in enumerate(worst_5_days.items()): - print(f" {i+1}. {date.strftime('%Y-%m-%d')}: {return_val:.2%}") - -Scenario Analysis -~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - print("\n=== SCENARIO ANALYSIS ===") - - # Define stress scenarios (percentage moves in underlying assets) - scenarios = { - "Market Crash": [-0.20, -0.25, -0.22, -0.30, -0.18, -0.20, 0.05, 0.10], - "Tech Selloff": [-0.35, -0.40, -0.30, -0.45, -0.10, -0.15, 0.02, 0.03], - "Interest Rate Shock": [-0.10, -0.12, -0.08, -0.15, -0.05, -0.08, -0.15, 0.01], - "Flight to Quality": [0.05, 0.02, 0.08, -0.10, 0.10, 0.12, 0.20, 0.15] - } - - print("Portfolio impact under stress scenarios:") - for scenario_name, asset_moves in scenarios.items(): - # Calculate portfolio impact - portfolio_impact = sum(w * move for w, move in zip(equal_weights, asset_moves)) - print(f" {scenario_name}: {portfolio_impact:.2%}") - -Monte Carlo Risk Simulation ---------------------------- - -Use Monte Carlo methods for risk assessment: - -.. code-block:: python - - print("\n=== MONTE CARLO RISK SIMULATION ===") - - # Import the simulate_portfolios function - from openseries.portfoliotools import simulate_portfolios - - # Monte Carlo simulation using native function - num_simulations = 10000 - seed = 42 # For reproducible results - - # Generate simulated portfolios using the native function - simulated_portfolios = simulate_portfolios( - simframe=portfolio_assets, - num_ports=num_simulations, - seed=seed - ) - - # Extract portfolio metrics from simulation - portfolio_returns = simulated_portfolios['ret'] - portfolio_volatilities = simulated_portfolios['stdev'] - portfolio_sharpes = simulated_portfolios['sharpe'] - - # Calculate risk metrics from simulation - # Calculate 5th percentile manually - sorted_returns = sorted(portfolio_returns) - percentile_idx = int(len(sorted_returns) * 0.05) - sim_var_95 = sorted_returns[percentile_idx] - sim_cvar_95 = portfolio_returns[portfolio_returns <= sim_var_95].mean() - - print(f"Monte Carlo Results ({num_simulations:,} simulations):") - print(f"Expected Return: {portfolio_returns.mean():.2%}") - print(f"Average Volatility: {portfolio_volatilities.mean():.2%}") - print(f"95% VaR: {sim_var_95:.2%}") - print(f"95% CVaR: {sim_cvar_95:.2%}") - # Calculate percentiles manually - worst_idx = int(len(sorted_returns) * 0.001) - best_idx = int(len(sorted_returns) * 0.999) - print(f"Worst Case (0.1%): {sorted_returns[worst_idx]:.2%}") - print(f"Best Case (99.9%): {sorted_returns[best_idx]:.2%}") - print(f"Average Sharpe Ratio: {portfolio_sharpes.mean():.3f}") - - # Show distribution of portfolio characteristics - print(f"\nPortfolio Distribution:") - print(f"Return Range: {portfolio_returns.min():.2%} to {portfolio_returns.max():.2%}") - print(f"Volatility Range: {portfolio_volatilities.min():.2%} to {portfolio_volatilities.max():.2%}") - print(f"Sharpe Range: {portfolio_sharpes.min():.3f} to {portfolio_sharpes.max():.3f}") - -Risk Decomposition ------------------- - -Analyze risk contribution by asset: - -.. code-block:: python - - print("\n=== RISK DECOMPOSITION ===") - - # Calculate individual asset volatilities using OpenFrame - asset_metrics = portfolio_assets.all_properties() - asset_vols = asset_metrics.loc['Volatility'].values - - # Portfolio volatility - portfolio_vol = portfolio.vol - - # Calculate correlation matrix - correlation_matrix = portfolio_assets.correl_matrix - - # Risk contribution analysis using openseries - # Create portfolio to get portfolio-level metrics - portfolio_df = portfolio_assets.make_portfolio(name="Portfolio", weight_strat="eq_weights") - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - portfolio_vol = portfolio.vol - - print("Risk Contribution Analysis:") - for i, series in enumerate(portfolio_assets.constituents): - weight = equal_weights[i] - asset_vol = asset_vols[i] - print(f"\n{series.label}:") - print(f" Weight: {weight:.4f}") - print(f" Individual Volatility: {asset_vol:.4f}") - print(f" Weighted Volatility Contribution: {weight * asset_vol:.4f}") - print(f"\nPortfolio Volatility: {portfolio_vol:.4f}") - - # Verify portfolio metrics - print(f"\nVerification:") - print(f"Portfolio volatility: {portfolio_vol:.4f}") - -Risk-Adjusted Performance -------------------------- - -Evaluate risk-adjusted returns: - -.. code-block:: python - - print("\n=== RISK-ADJUSTED PERFORMANCE ===") - - # Sharpe ratio - print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}") - - # Sortino ratio (downside risk only) - print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}") - - # Kappa-3 ratio (higher-order downside risk) - print(f"Kappa-3 Ratio: {portfolio.kappa3_ratio:.3f}") - - # Omega ratio - print(f"Omega Ratio: {portfolio.omega_ratio:.3f}") - - # Compare with individual assets - print(f"\n=== RISK-ADJUSTED COMPARISON ===") - all_assets = portfolio_assets.constituents + [portfolio] - comparison_frame = OpenFrame(constituents=all_assets) - - risk_adj_metrics = comparison_frame.all_properties( - properties=['ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'omega_ratio'] - ) - - print(risk_adj_metrics.round(3)) - -Risk Monitoring Dashboard -------------------------- - -Create a comprehensive risk monitoring summary using openseries properties and methods: - -.. code-block:: python - - print("\n" + "="*60) - print("RISK MONITORING DASHBOARD") - print("="*60) - - # Current date and lookback period - current_date = portfolio.last_idx - lookback_date = portfolio.first_idx - - print(f"Portfolio: {portfolio.label}") - print(f"Current Date: {current_date}") - print(f"Analysis Period: {lookback_date} to {current_date}") - print(f"Observations: {portfolio.length}") - - # Risk metrics using openseries properties - print(f"\n--- CURRENT RISK METRICS ---") - print(f"Volatility (annualized): {portfolio.vol:.2%}") - print(f"Downside Deviation: {portfolio.downside_deviation:.2%}") - print(f"95% VaR (daily): {portfolio.var_down:.2%}") - print(f"95% CVaR (daily): {portfolio.cvar_down:.2%}") - print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}") - - # Performance metrics using openseries properties - print(f"\n--- PERFORMANCE METRICS ---") - print(f"Total Return: {portfolio.value_ret:.2%}") - print(f"Annualized Return: {portfolio.geo_ret:.2%}") - print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}") - print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}") - - # Distribution characteristics using openseries properties - print(f"\n--- RETURN DISTRIBUTION ---") - print(f"Skewness: {portfolio.skew:.3f}") - print(f"Kurtosis: {portfolio.kurtosis:.3f}") - print(f"Positive Days: {portfolio.positive_share:.1%}") - - # Recent performance using openseries properties - recent_return = portfolio.z_score - print(f"\n--- RECENT ACTIVITY ---") - print(f"Last Return Z-Score: {recent_return:.2f}") - - if abs(recent_return) > 2: - print(" ⚠️ ALERT: Recent return is unusual (|z| > 2)") - elif abs(recent_return) > 3: - print(" 🚨 WARNING: Recent return is extreme (|z| > 3)") - else: - print(" ✅ Recent return is within normal range") - - # Risk alerts based on openseries metrics - print(f"\n--- RISK ALERTS ---") - alerts = [] - - if portfolio.vol > 0.25: - alerts.append("High volatility (>25%)") - - if abs(portfolio.max_drawdown) > 0.20: - alerts.append("Large maximum drawdown (>20%)") - - if portfolio.ret_vol_ratio < 0.5: - alerts.append("Low Sharpe ratio (<0.5)") - - if portfolio.skew < -1: - alerts.append("Highly negative skew (<-1)") - - if portfolio.kurtosis > 5: - alerts.append("High kurtosis (>5) - fat tails") - - if alerts: - for alert in alerts: - print(f" ⚠️ {alert}") - else: - print(" ✅ No risk alerts") - -Risk Limits and Controls ------------------------- - -Implement risk limit monitoring: - -.. code-block:: python - - print("\n=== RISK LIMITS MONITORING ===") - - # Define risk limits - risk_limits = { - 'max_volatility': 0.20, # 20% annual volatility - 'max_var_daily': -0.03, # 3% daily VaR - 'max_drawdown': -0.15, # 15% maximum drawdown - 'min_sharpe': 0.5, # Minimum Sharpe ratio - 'max_concentration': 0.30 # Maximum single asset weight - } - - # Check current metrics against limits - current_metrics = { - 'volatility': portfolio.vol, - 'var_daily': portfolio.var_down, - 'drawdown': portfolio.max_drawdown, - 'sharpe': portfolio.ret_vol_ratio, - 'max_weight': max(equal_weights) - } - - print("Risk Limit Monitoring:") - print("-" * 40) - - # Volatility check - if current_metrics['volatility'] > risk_limits['max_volatility']: - print(f"❌ BREACH: Volatility {current_metrics['volatility']:.2%} > {risk_limits['max_volatility']:.2%}") - else: - print(f"✅ OK: Volatility {current_metrics['volatility']:.2%} <= {risk_limits['max_volatility']:.2%}") - - # VaR check - if current_metrics['var_daily'] < risk_limits['max_var_daily']: - print(f"❌ BREACH: VaR {current_metrics['var_daily']:.2%} < {risk_limits['max_var_daily']:.2%}") - else: - print(f"✅ OK: VaR {current_metrics['var_daily']:.2%} >= {risk_limits['max_var_daily']:.2%}") - - # Drawdown check - if current_metrics['drawdown'] < risk_limits['max_drawdown']: - print(f"❌ BREACH: Drawdown {current_metrics['drawdown']:.2%} < {risk_limits['max_drawdown']:.2%}") - else: - print(f"✅ OK: Drawdown {current_metrics['drawdown']:.2%} >= {risk_limits['max_drawdown']:.2%}") - - # Sharpe ratio check - if current_metrics['sharpe'] < risk_limits['min_sharpe']: - print(f"❌ BREACH: Sharpe {current_metrics['sharpe']:.3f} < {risk_limits['min_sharpe']:.3f}") - else: - print(f"✅ OK: Sharpe {current_metrics['sharpe']:.3f} >= {risk_limits['min_sharpe']:.3f}") - - # Concentration check - if current_metrics['max_weight'] > risk_limits['max_concentration']: - print(f"❌ BREACH: Max weight {current_metrics['max_weight']:.2%} > {risk_limits['max_concentration']:.2%}") - else: - print(f"✅ OK: Max weight {current_metrics['max_weight']:.2%} <= {risk_limits['max_concentration']:.2%}") - -Export Risk Report ------------------- - -Save comprehensive risk analysis: - -.. code-block:: python - - # Create comprehensive risk report - # Create risk report using openseries methods - print("\n=== RISK REPORT ===") - print("Risk metrics are available through openseries properties:") - for series in portfolio_assets.constituents: - print(f"\n{series.label}:") - print(f" VaR (95%): {series.var_down:.4f}") - print(f" CVaR (95%): {series.cvar_down:.4f}") - print(f" Volatility: {series.vol:.4f}") - print(f" Max Drawdown: {series.max_drawdown:.4f}") - - # Note: For comprehensive Excel export, use openseries to_xlsx() method - portfolio_assets.to_xlsx('risk_analysis_report.xlsx') - - # Alternative: risk_report = pd.DataFrame({ - 'Metric': [ - 'Annualized Return', 'Annualized Volatility', 'Sharpe Ratio', - 'Sortino Ratio', 'Maximum Drawdown', '95% VaR (daily)', - '95% CVaR (daily)', 'Skewness', 'Kurtosis', 'Positive Days %' - ], - 'Value': [ - f"{portfolio.geo_ret:.2%}", - f"{portfolio.vol:.2%}", - f"{portfolio.ret_vol_ratio:.3f}", - f"{portfolio.sortino_ratio:.3f}", - f"{portfolio.max_drawdown:.2%}", - f"{portfolio.var_down:.2%}", - f"{portfolio.cvar_down:.2%}", - f"{portfolio.skew:.3f}", - f"{portfolio.kurtosis:.3f}", - f"{portfolio.positive_share:.1%}" - ] - }) - - # Export to Excel - # Export using openseries native method (commented out ExcelWriter approach) - # with pd.ExcelWriter('risk_analysis_report.xlsx') as writer: - risk_report.to_excel(writer, sheet_name='Risk Metrics', index=False) - risk_decomp.to_excel(writer, sheet_name='Risk Decomposition', index=False) - correlation_matrix.to_excel(writer, sheet_name='Correlations') - - # Add rolling metrics if available - if 'rolling_vol' in locals(): - rolling_vol.to_excel(writer, sheet_name='Rolling Volatility') - if 'rolling_var' in locals(): - rolling_var.to_excel(writer, sheet_name='Rolling VaR') - - print(f"\nRisk analysis report exported to 'risk_analysis_report.xlsx'") - print("Risk management analysis complete!") - -This comprehensive risk management tutorial provides the foundation for implementing robust risk controls and monitoring systems using openseries. diff --git a/docs/build/html/_sources/user_guide/core_concepts.rst.txt b/docs/build/html/_sources/user_guide/core_concepts.rst.txt deleted file mode 100644 index 573f52cf..00000000 --- a/docs/build/html/_sources/user_guide/core_concepts.rst.txt +++ /dev/null @@ -1,453 +0,0 @@ -Core Concepts -============= - -This section explains the fundamental concepts and design principles behind openseries. - -Architecture Overview ----------------------- - -openseries is built around two main classes that inherit from Pydantic's BaseModel: - -- **OpenTimeSeries**: Manages individual financial time series -- **OpenFrame**: Manages collections of OpenTimeSeries objects - -Both classes provide: - -- **Type safety** through Pydantic validation -- **Immutable data** - original data is preserved -- **Consistent API** - similar methods across both classes -- **Financial focus** - methods designed for financial analysis - -Mutation and data layers ------------------------- - -openseries favors in-place transformations. Many methods modify the existing object -and return ``self`` for chaining rather than creating a new object. -On ``OpenTimeSeries``, the ``dates`` and ``values`` arrays are always left untouched, -while the working data in the ``tsdf`` pandas ``DataFrame`` is mutable. -On ``OpenFrame``, the ``tsdf`` ``DataFrame`` is also mutable and reflects -transformations applied to the frame. If you need to preserve the original state or -compare before/after results, create an explicit copy -(for example, ``OpenTimeSeries.from_deepcopy()`` or ``OpenFrame.from_deepcopy()``). - -The OpenTimeSeries Class -------------------------- - -Core Properties -~~~~~~~~~~~~~~~ - -Every OpenTimeSeries has these fundamental properties: - -.. code-block:: python - - # Create a sample series using openseries simulation - from openseries import ReturnSimulation, ValueType - import datetime as dt - - simulation = ReturnSimulation.from_lognormal( - number_of_sims=1, - trading_days=100, - mean_annual_return=0.25, # ~0.001 daily - mean_annual_vol=0.32, # ~0.02 daily - trading_days_in_year=252, - seed=42 - ) - - series = OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="Sample Asset", end=dt.date(2023, 12, 31)), - valuetype=ValueType.RTRN - ).to_cumret() # Convert returns to cumulative prices - - # Core properties - print(f"Name: {series.label}") - print(f"Length: {series.length}") - print(f"First date: {series.first_idx}") - print(f"Last date: {series.last_idx}") - print(f"Value type: {series.valuetype}") - -Data Immutability -~~~~~~~~~~~~~~~~~ - -The original data is never modified: - -.. code-block:: python - - # Original data is preserved - original_dates = series.dates # List of date strings - original_values = series.values # List of float values - - # Working data is in the tsdf DataFrame - working_data = series.tsdf # pandas DataFrame - - # Transformations modify the original object (method chaining) - series.value_to_ret() # Modifies original series - print(f"Series length: {series.length}") # Usually length - 1 - -Value Types -~~~~~~~~~~~ - -The ValueType enum identifies what the series represents: - -.. code-block:: python - - from openseries import ValueType - - # Common value types - print(ValueType.PRICE) # "Price(Close)" - print(ValueType.RTRN) # "Return(Total)" - print(ValueType.ROLLVOL) # "Rolling volatility" - - # Check series type - print(f"Series type: {series.valuetype}") - - # Type changes with transformations - series.value_to_ret() # Modifies original - print(f"Returns type: {series.valuetype}") - -The OpenFrame Class --------------------- - -Managing Multiple Series -~~~~~~~~~~~~~~~~~~~~~~~~ - -OpenFrame manages collections of OpenTimeSeries: - -.. code-block:: python - - from openseries import OpenFrame - - # Create multiple series using openseries simulation - simulation = ReturnSimulation.from_lognormal( - number_of_sims=3, - trading_days=100, - mean_annual_return=0.25, # ~0.001 daily - mean_annual_vol=0.32, # ~0.02 daily - trading_days_in_year=252, - seed=42 - ) - - # Create OpenFrame with multiple series from simulation - frame = OpenFrame( - constituents=[ - OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="Asset", end=dt.date(2023, 12, 31)), - column_nmbr=serie, - valuetype=ValueType.RTRN, - ).to_cumret() # Convert returns to cumulative prices - for serie in range(simulation.number_of_sims) - ] - ) - - # Frame properties - print(f"Number of series: {frame.item_count}") - print(f"Column names: {frame.columns_lvl_zero}") - print(f"Common length: {frame.length}") - -Data Alignment -~~~~~~~~~~~~~~~ - -OpenFrame concatenates series data but does **not** automatically align them. -The library provides explicit methods for alignment that require user choice: - -.. code-block:: python - - # Series with different date ranges are concatenated (not aligned) - print("Individual series lengths:") - print(frame.lengths_of_items) - - print(f"Frame length (concatenated): {frame.length}") - - # Explicit alignment methods require user choice: - - # 1. Truncate to common date range - frame.trunc_frame() - - # 2. Align to business day calendar (modifies original) - frame.align_index_to_local_cdays(countries="US") - - # 3. Handle missing values (modifies original) - frame.value_nan_handle(method="fill") - - # 4. Merge with explicit join strategy - frame.merge_series(how="inner") - frame.merge_series(how="outer") - -Financial Calculations ----------------------- - -Return Calculations -~~~~~~~~~~~~~~~~~~~ - -openseries uses standard financial formulas: - -.. code-block:: python - - # Simple returns: (P_t / P_{t-1}) - 1 - series.value_to_ret() # Modifies original - - # Log returns: ln(P_t / P_{t-1}) - series.value_to_log() # Modifies original - - # Cumulative returns: rebasing to start at 1.0 (modifies original) - series.to_cumret() - -Annualization -~~~~~~~~~~~~~ - -Metrics are annualized using the actual number of observations per year: - -.. code-block:: python - - # Automatic calculation of periods per year - print(f"Periods per year: {series.periods_in_a_year:.1f}") - - # Annualized return (geometric mean) - annual_return = series.geo_ret - print(f"Annualized return: {annual_return:.2%}") - - # Annualized volatility - annual_vol = series.vol - print(f"Annualized volatility: {annual_vol:.2%}") - -Risk Metrics -~~~~~~~~~~~~ - -Risk calculations follow industry standards: - -.. code-block:: python - - # Value at Risk (95% confidence) - var_95 = series.var_down - print(f"95% VaR: {var_95:.2%}") - - # Conditional Value at Risk (Expected Shortfall) - cvar_95 = series.cvar_down - print(f"95% CVaR: {cvar_95:.2%}") - - # Maximum Drawdown - max_dd = series.max_drawdown - print(f"Maximum Drawdown: {max_dd:.2%}") - - # Sortino Ratio (downside deviation) - sortino = series.sortino_ratio - print(f"Sortino Ratio: {sortino:.2f}") - -Date Handling -------------- - -Business Day Calendars -~~~~~~~~~~~~~~~~~~~~~~~ - -openseries integrates with business day calendars: - -.. code-block:: python - - # Align to specific country's business days (modifies original) - series.align_index_to_local_cdays(countries="US") - - # Multiple countries (intersection of business days) (modifies original) - series.align_index_to_local_cdays(countries=["US", "GB"]) - - # Custom markets using pandas-market-calendars (modifies original) - series.align_index_to_local_cdays(markets="NYSE") - -Resampling -~~~~~~~~~~ - -Convert between different frequencies: - -.. code-block:: python - - # Resample to month-end (modifies original) - series.resample_to_business_period_ends(freq="BME") - - # Resample to quarter-end (modifies original) - series.resample_to_business_period_ends(freq="BQE") - - # Custom resampling (modifies original) - series.resample(freq="W") - -Data Validation ---------------- - -Type Safety -~~~~~~~~~~~ - -Pydantic ensures data integrity: - -.. code-block:: python - - # Dates must be valid ISO format strings - # This will fail with a validation error - invalid_series = OpenTimeSeries.from_arrays( - dates=["invalid-date"], - values=[100.0] - ) - - # Values must be numeric - # This will fail with a validation error - invalid_series = OpenTimeSeries.from_arrays( - dates=["2023-01-01"], - values=["not a number"] - ) - -Consistency Checks -~~~~~~~~~~~~~~~~~~ - -The library performs consistency checks: - -.. code-block:: python - - # Dates and values must have same length - # Mixed value types in OpenFrame are detected - # Date alignment issues are caught - -Method Categories ------------------ - -openseries methods fall into several categories: - -Properties vs Methods -~~~~~~~~~~~~~~~~~~~~~ - -- **Properties**: Return calculated values (e.g., ``series.vol``) -- **Methods**: Perform operations or take parameters (e.g., ``series.vol_func()``) - -.. code-block:: python - - # Property - uses full series - volatility = series.vol - - # Method - can specify date range - recent_vol = series.vol_func(months_from_last=12) - -Transformation Methods -~~~~~~~~~~~~~~~~~~~~~~ - -Methods that modify the original object (return self for chaining): - -.. code-block:: python - - # Data transformations (modify original) - series.value_to_ret() # Prices to returns - series.to_drawdown_series() # Drawdown series - series.to_cumret() # Cumulative returns - - # Time transformations (modify original) - series.resample_to_business_period_ends(freq="BME") - series.align_index_to_local_cdays(countries="US") - -Methods that return new objects: - -.. code-block:: python - - # Analysis methods (return new objects) - rolling_vol = series.rolling_vol(observations=30) - rolling_ret = series.rolling_return(observations=30) - -Analysis Methods -~~~~~~~~~~~~~~~~ - -Methods that return calculated values: - -.. code-block:: python - - # Rolling calculations - rolling_vol = series.rolling_vol(observations=30) - rolling_corr = frame.rolling_corr(observations=60) - - # Statistical analysis - beta = frame.beta() - tracking_error = frame.tracking_error_func() - -Export Methods -~~~~~~~~~~~~~~ - -Methods for saving results: - -.. code-block:: python - - # File exports - series.to_xlsx("analysis.xlsx") - series.to_json("data.json") - - # Visualization - series.plot_series() - series.plot_histogram() - -Best Practices --------------- - -Data Loading -~~~~~~~~~~~~ - -.. code-block:: python - - # Prefer from_df for pandas data - series = OpenTimeSeries.from_df(dframe=dataframe['Close']) - series.set_new_label(lvl_zero="Asset") - - # Use from_arrays for custom data - series = OpenTimeSeries.from_arrays(dates=date_list, values=value_list) - - # Always set meaningful names - series.set_new_label(lvl_zero="Descriptive Name") - -Analysis Workflow -~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # 1. Load and validate data - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero="Asset") - - # 2. Basic analysis - metrics = series.all_properties() - - # 3. Specific calculations - series.to_drawdown_series() # Convert to drawdown (modifies original) - rolling_metrics = series.rolling_vol(observations=252) # Returns DataFrame - - # 4. Visualization - series.plot_series() - - # 5. Export results - series.to_xlsx(fiilename="analysis.xlsx") - -Memory Management -~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Original data is preserved - use deepcopy if needed - series_copy = OpenTimeSeries.from_deepcopy(series) - - # Large datasets - consider resampling (modifies original) - series.resample_to_business_period_ends(freq="BME") - - # Clean up intermediate results - del intermediate_series - -Portfolio Construction -~~~~~~~~~~~~~~~~~~~~~~ - -OpenFrame provides several built-in weight strategies for portfolio construction: - -.. code-block:: python - - from openseries.owntypes import MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError - - # Available weight strategies - strategies = { - 'eq_weights': 'Equal weights for all assets', - 'inv_vol': 'Inverse volatility weighting (risk parity)', - 'max_div': 'Maximum diversification optimization', - 'min_vol_overweight': 'Minimum volatility overweight strategy' - } - - # Example with error handling - # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError - portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div") - -Understanding these core concepts will help you use openseries effectively and build more sophisticated financial analysis workflows. diff --git a/docs/build/html/_sources/user_guide/data_handling.rst.txt b/docs/build/html/_sources/user_guide/data_handling.rst.txt deleted file mode 100644 index 86705f71..00000000 --- a/docs/build/html/_sources/user_guide/data_handling.rst.txt +++ /dev/null @@ -1,448 +0,0 @@ -Data Handling -============= - -This guide covers data loading, validation, transformation, and management in openseries. - -Loading Data ------------- - -From pandas DataFrame/Series -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The most common way to load data is from pandas objects: - -.. code-block:: python - - import pandas as pd - from openseries import OpenTimeSeries - - # From pandas Series with DatetimeIndex - data = pd.Series([100, 101, 99, 102], - index=pd.date_range('2023-01-01', periods=4)) - series = OpenTimeSeries.from_df(dframe=data) - series.set_new_label(lvl_zero="Sample") - - # From pandas DataFrame column - df = pd.DataFrame({ - 'Date': pd.date_range('2023-01-01', periods=4), - 'Close': [100, 101, 99, 102], - 'Volume': [1000, 1100, 900, 1200] - }) - df.set_index('Date', inplace=True) - series = OpenTimeSeries.from_df(dframe=df['Close']) - series.set_new_label(lvl_zero="Stock") - -From Arrays -~~~~~~~~~~~ - -For custom data or when working with lists: - -.. code-block:: python - - # From date strings and values - dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'] - values = [100.0, 101.0, 99.0, 102.0] - - series = OpenTimeSeries.from_arrays( - dates=dates, - values=values, - name="Custom Data" - ) - -From Fixed Rate -~~~~~~~~~~~~~~~ - -Generate synthetic data from a fixed rate: - -.. code-block:: python - - from datetime import date - - # Create 252 trading days at 5% annual rate - series = OpenTimeSeries.from_fixed_rate( - rate=0.05, - days=252, - end_date=date(2023, 12, 31), - name="5% Fixed Rate" - ) - -Data Validation ---------------- - -Date Format Validation -~~~~~~~~~~~~~~~~~~~~~~ - -openseries enforces strict date formats: - -.. code-block:: python - - # Valid date formats - valid_dates = ['2023-01-01', '2023-12-31', '2024-02-29'] # ISO format - - # Invalid formats will raise ValidationError - # This will fail with a validation error - invalid_series = OpenTimeSeries.from_arrays( - dates=['01/01/2023', '2023-1-1'], # Wrong format - values=[100, 101] - ) - -Value Validation -~~~~~~~~~~~~~~~~ - -Values must be numeric and finite: - -.. code-block:: python - - import numpy as np - - # Valid values - valid_values = [100.0, 101.5, 99.25, 102.75] - - # Handle NaN values appropriately - values_with_nan = [100.0, np.nan, 99.0, 102.0] - series = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'], - values=values_with_nan, - name="Data with NaN" - ) - - # Clean NaN values (modifies original) - series.value_nan_handle() # Forward fill - -Length Consistency -~~~~~~~~~~~~~~~~~~ - -Dates and values must have the same length: - -.. code-block:: python - - # This will raise an error - # This will fail with a length mismatch error - invalid_series = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02'], - values=[100.0, 101.0, 102.0] # Different length - ) - -Data Transformations --------------------- - -Price and Return Conversions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Assume we have a price series - prices = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03'], - values=[100.0, 102.0, 99.0], - name="Stock Price" - ) - - # Convert to simple returns (modifies original) - prices.value_to_ret() - print(f"Returns: {prices.values}") # [0.02, -0.0294...] - - # Convert to log returns (modifies original) - prices.value_to_log() - - # Convert returns back to cumulative values (modifies original) - prices.to_cumret() - - # Convert to differences (absolute changes) (modifies original) - prices.value_to_diff() - -Resampling -~~~~~~~~~~ - -Change the frequency of your data: - -.. code-block:: python - - # Daily to monthly (business month end) (modifies original) - series.resample_to_business_period_ends(freq="BME") - - # Daily to quarterly (modifies original) - series.resample_to_business_period_ends(freq="BQE") - - # Daily to annual (modifies original) - series.resample_to_business_period_ends(freq="BYE") - - # Custom resampling with pandas frequency strings (modifies original) - series.resample(freq="W") - - # Resample with specific method (modifies original) - series.resample(freq="W", method="mean") - -Business Day Alignment -~~~~~~~~~~~~~~~~~~~~~~ - -Align data to business day calendars: - -.. code-block:: python - - # Align to US business days (modifies original) - series.align_index_to_local_cdays(countries="US") - - # Align to multiple countries (intersection) (modifies original) - series.align_index_to_local_cdays(countries=["US", "GB", "JP"]) - - # Align to specific market calendar (modifies original) - series.align_index_to_local_cdays(markets="NYSE") - -Handling Missing Data ---------------------- - -NaN Handling Strategies -~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import numpy as np - - # Create series with missing values - dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'] - values = [100.0, np.nan, 102.0, np.nan] - - series_with_nan = OpenTimeSeries.from_arrays( - dates=dates, values=values, name="With NaN" - ) - - # Forward fill missing values (for price series) (modifies original) - series_with_nan.value_nan_handle() - - # For return series, replace NaN with 0.0 (modifies original) - series_with_nan.value_to_ret() - series_with_nan.return_nan_handle() - -Dropping Missing Data -~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Remove NaN values entirely (modifies original) - series_with_nan.value_nan_handle(method="drop") - -Working with Multiple Assets ------------------------------ - -Creating OpenFrame -~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from openseries import OpenFrame - - # Create multiple series - series1 = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03'], - values=[100, 102, 99], name="Asset A" - ) - - series2 = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03'], - values=[50, 51, 49], name="Asset B" - ) - - # Create frame - frame = OpenFrame(constituents=[series1, series2]) - -Handling Different Date Ranges -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -OpenFrame automatically handles series with different date ranges: - -.. code-block:: python - - # Series with different start/end dates - early_series = OpenTimeSeries.from_arrays( - dates=['2022-12-01', '2023-01-01', '2023-01-02'], - values=[95, 100, 102], name="Early Start" - ) - - late_series = OpenTimeSeries.from_arrays( - dates=['2023-01-02', '2023-01-03', '2023-01-04'], - values=[51, 49, 52], name="Late Start" - ) - - # Frame will align to common date range - frame = OpenFrame(constituents=[early_series, late_series]) - print(f"Frame date range: {frame.first_idx} to {frame.last_idx}") - -Adding and Removing Series -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Add a new series - new_series = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03'], - values=[200, 205, 198], name="Asset C" - ) - frame.add_timeseries(new_series) - - # Remove a series by index - frame.delete_timeseries(item_idx=0) - -Data Export and Import ----------------------- - -Excel Export -~~~~~~~~~~~~ - -.. code-block:: python - - # Export single series - series.to_xlsx(filename="single_series.xlsx") - - # Export frame (multiple series) - frame.to_xlsx(filename="multiple_series.xlsx") - - # Export with custom sheet title - series.to_xlsx( - filename="formatted_export.xlsx", - sheet_title="Analysis" - ) - -JSON Export -~~~~~~~~~~~ - -.. code-block:: python - - # Export series values only - series.to_json(what_output="values", filename="series_values.json") - - # Export full dataframe structure - series.to_json(what_output="tsdf", filename="series_dataframe.json") - - # Export frame data - frame.to_json(what_output="values", filename="frame_values.json") - -Working with Real Data Sources -------------------------------- - -Yahoo Finance Integration -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import yfinance as yf - - # Single asset - ticker = yf.Ticker("AAPL") - data = ticker.history(period="2y") - - apple = OpenTimeSeries.from_df( - dframe=data['Close'], - name="Apple Inc." - ) - - # Multiple assets - tickers = ["AAPL", "GOOGL", "MSFT"] - series_list = [] - - for ticker_symbol in tickers: - ticker = yf.Ticker(ticker_symbol) - data = ticker.history(period="1y") - series = OpenTimeSeries.from_df( - dframe=data['Close'], - name=ticker_symbol - ) - series_list.append(series) - - tech_frame = OpenFrame(constituents=series_list) - -CSV Data -~~~~~~~~ - -.. code-block:: python - - # Load from CSV - df = pd.read_csv("stock_data.csv", index_col=0, parse_dates=True) - - series = OpenTimeSeries.from_df( - dframe=df['Close'], - name="Stock from CSV" - ) - -Data Quality Checks -------------------- - -Validation Methods -~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Check for data quality issues - print(f"Series length: {series.length}") - print(f"Date range: {series.first_idx} to {series.last_idx}") - print(f"Span of days: {series.span_of_days}") - - # Check for gaps in data - expected_length = (series.last_idx - series.first_idx).days + 1 - actual_length = series.length - - if expected_length != actual_length: - print(f"Data gaps detected: expected {expected_length}, got {actual_length}") - -Outlier Detection -~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Convert to returns for outlier analysis (modifies original) - series.value_to_ret() - - # Detect outliers using the built-in method - outliers = series.outliers(threshold=3.0) - print(f"Found {len(outliers)} outliers (|z| > 3)") - - # For OpenFrame, outliers returns a DataFrame - frame_outliers = frame.outliers(threshold=3.0) - print(f"Found outliers in frame: {len(frame_outliers)} rows") - - # Customize threshold and date range - recent_outliers = series.outliers( - threshold=2.5, - months_from_last=6 - ) - -Performance Considerations --------------------------- - -Memory Usage -~~~~~~~~~~~~ - -.. code-block:: python - - # For large datasets, consider resampling - large_series = series # Assume this is large daily data - - # Reduce to monthly for analysis (modifies original) - large_series.resample_to_business_period_ends(freq="BME") - - # Use monthly for computationally intensive operations - monthly_metrics = large_series.all_properties() - -Efficient Data Loading -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # When loading multiple assets, batch the operations - tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"] - - # Download all at once - data = yf.download(tickers, period="2y")['Close'] - - # Create series efficiently - series_list = [] - for ticker in tickers: - series = OpenTimeSeries.from_df( - dframe=data[ticker].dropna(), - name=ticker - ) - series_list.append(series) - - frame = OpenFrame(constituents=series_list) - -This comprehensive guide should help you handle various data scenarios effectively with openseries. diff --git a/docs/build/html/_sources/user_guide/installation.rst.txt b/docs/build/html/_sources/user_guide/installation.rst.txt deleted file mode 100644 index 69f66f93..00000000 --- a/docs/build/html/_sources/user_guide/installation.rst.txt +++ /dev/null @@ -1,197 +0,0 @@ -Installation -============ - -System Requirements -------------------- - -openseries requires Python 3.11 or higher and is compatible with: - -- **Operating Systems**: Windows, macOS, Linux -- **Python versions**: 3.11, 3.12, 3.13, 3.14 - -Installing openseries ---------------------- - -Using pip (recommended) -~~~~~~~~~~~~~~~~~~~~~~~ - -The easiest way to install openseries is using pip: - -.. code-block:: bash - - pip install openseries - -Using conda -~~~~~~~~~~~ - -openseries is also available on conda-forge: - -.. code-block:: bash - - conda install -c conda-forge openseries - -Installing from source -~~~~~~~~~~~~~~~~~~~~~~ - -To install the latest development version from GitHub: - -.. code-block:: bash - - git clone https://github.com/CaptorAB/openseries.git - cd openseries - pip install -e . - -Dependencies ------------- - -openseries automatically installs the following dependencies: - -Core Dependencies -~~~~~~~~~~~~~~~~~ - -- **pandas** (>=2.1.2) - Data manipulation and analysis -- **numpy** (>=1.23.2) - Numerical computing -- **pydantic** (>=2.5.2) - Data validation and settings management -- **plotly** (>=5.18.0) - Interactive plotting -- **scipy** (>=1.14.1) - Scientific computing -- **scikit-learn** (>=1.4.0) - Machine learning utilities - -Financial and Date Utilities -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- **exchange-calendars** (>=4.8) - Trading calendar support -- **holidays** (>=0.30) - Holiday calendar support -- **python-dateutil** (>=2.8.2) - Date parsing utilities -- **tzdata** (>=2025.3) - IANA time zone data - -File and Network Support -~~~~~~~~~~~~~~~~~~~~~~~~~ - -- **openpyxl** (>=3.1.2) - Excel file support -- **requests** (>=2.20.0) - HTTP library - -Optional Dependencies -~~~~~~~~~~~~~~~~~~~~~ - -For data acquisition examples, you may want to install: - -.. code-block:: bash - - pip install yfinance # For Yahoo Finance data - -Verifying Installation ----------------------- - -To verify that openseries is installed correctly, run: - -.. code-block:: python - - import openseries - print(openseries.__version__) - -You can also run a quick test: - -.. code-block:: python - - from openseries import OpenTimeSeries, ReturnSimulation, ValueType - import datetime as dt - - # Create sample data using openseries simulation - simulation = ReturnSimulation.from_lognormal( - number_of_sims=1, - trading_days=100, - mean_annual_return=0.25, # ~0.001 daily - mean_annual_vol=0.32, # ~0.02 daily - trading_days_in_year=252, - seed=42 - ) - - # Create OpenTimeSeries - series = OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="Test Series", end=dt.date(2023, 12, 31)), - valuetype=ValueType.RTRN - ).to_cumret() # Convert returns to cumulative prices - - print(f"Series length: {series.length}") - print(f"Annual return: {series.geo_ret:.2%}") - -Development Installation ------------------------- - -If you plan to contribute to openseries or need the development dependencies, -use the same pinned tooling as CI (``uv==0.11.21``): - -.. code-block:: bash - - git clone https://github.com/CaptorAB/openseries.git - cd openseries - make install - -On Windows, run ``.\make.ps1 make`` instead of ``make install``. - -This creates ``venv``, installs locked runtime, development, and documentation -dependencies from ``uv.lock``, and installs pre-commit hooks. Development -dependencies include: - -- **pytest** (>=9.1.0) - Testing framework -- **pytest-cov** (>=7.1.0) - Coverage plugin -- **pytest-xdist** (>=3.8.0) - Parallel test runner -- **mypy** (==2.1.0) - Static type checking -- **ruff** (==0.15.18) - Linting and formatting -- **pre-commit** (>=4.6.0) - Git hooks for code quality - -Troubleshooting ---------------- - -Common Issues -~~~~~~~~~~~~~ - -**ImportError: No module named 'openseries'** - -Make sure openseries is installed in the correct Python environment. If using virtual environments, ensure it's activated. - -**Version conflicts** - -If you encounter dependency conflicts, try creating a fresh virtual environment: - -.. code-block:: bash - - python -m venv openseries_env - source openseries_env/bin/activate # On Windows: openseries_env\Scripts\activate - pip install openseries - -**Performance issues** - -For better performance with large datasets, consider installing optional accelerated packages: - -.. code-block:: bash - - pip install numba # For numerical acceleration - pip install bottleneck # For faster pandas operations - -Getting Help -~~~~~~~~~~~~ - -If you encounter issues: - -1. Check the `GitHub Issues `_ -2. Review the `Release Notes `_ -3. Create a new issue with a minimal reproducible example - -Platform-Specific Notes ------------------------- - -Windows -~~~~~~~ - -On Windows, you may need to install Microsoft Visual C++ Build Tools if you encounter compilation errors with dependencies. - -macOS -~~~~~ - -On macOS with Apple Silicon (M1/M2), all dependencies should install without issues. If you encounter problems, try using conda instead of pip. - -Linux -~~~~~ - -Most Linux distributions should work without issues. On minimal installations, you may need to install additional system packages for some dependencies. diff --git a/docs/build/html/_sources/user_guide/quickstart.rst.txt b/docs/build/html/_sources/user_guide/quickstart.rst.txt deleted file mode 100644 index 2f781d3a..00000000 --- a/docs/build/html/_sources/user_guide/quickstart.rst.txt +++ /dev/null @@ -1,291 +0,0 @@ -Quick Start Guide -================= - -This guide will get you up and running with openseries in just a few minutes. - -Your First OpenTimeSeries --------------------------- - -Let's start by creating a simulated financial time series using openseries' built-in simulation capabilities: - -.. code-block:: python - - from openseries import OpenTimeSeries, ReturnSimulation, ValueType - import datetime as dt - - # Create a simulated time series using lognormal distribution - simulation = ReturnSimulation.from_lognormal( - number_of_sims=1, - trading_days=1000, - mean_annual_return=0.08, # 8% annual return - mean_annual_vol=0.15, # 15% annual volatility - trading_days_in_year=252, - seed=71 - ) - - # Convert simulation to OpenTimeSeries - sp500 = OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="S&P 500", end=dt.date(2023, 12, 31)), - valuetype=ValueType.RTRN - ).to_cumret() # Convert returns to cumulative prices - - sp500.set_new_label(lvl_zero="S&P 500") - - # Display basic information - print(f"Series: {sp500.label}") - print(f"Start date: {sp500.first_idx}") - print(f"End date: {sp500.last_idx}") - print(f"Number of observations: {sp500.length}") - -Loading Data from External Sources ------------------------------------ - -Alternatively, you can load data from external sources like yfinance: - -.. code-block:: python - - import yfinance as yf # pip install yfinance - from openseries import OpenTimeSeries - - # Download S&P 500 data - ticker = yf.Ticker("^GSPC") - data = ticker.history(period="2y") - - # Create OpenTimeSeries from the Close prices - sp500 = OpenTimeSeries.from_df(dframe=data['Close']) - - # Set a more descriptive label - sp500.set_new_label(lvl_zero="S&P 500 Index") - - print(f"Loaded {sp500.length} observations") - print(f"Date range: {sp500.first_idx} to {sp500.last_idx}") - -Basic Financial Metrics ------------------------- - -openseries provides a comprehensive set of financial metrics: - -.. code-block:: python - - # Key performance metrics - print(f"Total Return: {sp500.value_ret:.2%}") - print(f"Annualized Return (CAGR): {sp500.geo_ret:.2%}") - print(f"Annualized Volatility: {sp500.vol:.2%}") - print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}") - print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}") - - # Risk metrics - print(f"95% VaR (daily): {sp500.var_down:.2%}") - print(f"95% CVaR (daily): {sp500.cvar_down:.2%}") - print(f"Sortino Ratio: {sp500.sortino_ratio:.2f}") - - # Distribution statistics - print(f"Skewness: {sp500.skew:.2f}") - print(f"Kurtosis: {sp500.kurtosis:.2f}") - print(f"Positive Days: {sp500.positive_share:.1%}") - -Get All Metrics at Once -~~~~~~~~~~~~~~~~~~~~~~~ - -Use the ``all_properties`` attribute to get a comprehensive overview: - -.. code-block:: python - - # Get all metrics of an OpenTimeSeries or OpenFrame - metrics = sp500.all_properties() - print(metrics) - -Creating Visualizations ------------------------ - -openseries integrates with Plotly for interactive visualizations: - -.. code-block:: python - - # Plot the timeseries - sp500.plot_series() - # This opens an interactive plot in your browser - - # Plot returns histogram - returns = sp500.from_deepcopy() - returns.value_to_ret() # Convert to returns (modifies original) - returns.plot_histogram() - - # Plot bar chart (useful for plotting returns) - returns.plot_bars() - - # Plot drawdown series - sp500.to_drawdown_series() # Convert to drawdown (modifies original) - sp500.plot_series() - - -Working with Multiple Assets (OpenFrame) ------------------------------------------ - -For multi-asset analysis, use the OpenFrame class: - -.. code-block:: python - - from openseries import OpenFrame - import yfinance as yf - - # Download data for multiple assets - tickers = ["^GSPC", "^IXIC", "^RUT"] # S&P 500, NASDAQ, Russell 2000 - names = ["S&P 500", "NASDAQ", "Russell 2000"] - - series_list = [] - for ticker, name in zip(tickers, names): - data = yf.Ticker(ticker).history(period="2y") - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero=name) - series_list.append(series) - - # Create OpenFrame - frame = OpenFrame(constituents=series_list) - frame.value_nan_handle().trunc_frame() - - # Get metrics for all series - all_metrics = frame.all_properties() - print(all_metrics) - - # Calculate correlations - correlations = frame.correl_matrix - print("\nCorrelation Matrix:") - print(correlations) - -Portfolio Analysis ------------------- - -Create and analyze portfolios: - -.. code-block:: python - - # Equal-weighted portfolio - portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights") - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - - print(f"Equal Weight Portfolio Return: {portfolio.geo_ret:.2%}") - print(f"Equal Weight Portfolio Volatility: {portfolio.vol:.2%}") - print(f"Equal Weight Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}") - - # Create custom weighted portfolio - frame.weights = [0.8, 0.2] # Custom allocation - custom_df = frame.make_portfolio(name="Custom Portfolio") - custom_portfolio = OpenTimeSeries.from_df(dframe=custom_df) - print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}") - - # Compare with individual assets - frame.add_timeseries(portfolio) - frame.add_timeseries(custom_portfolio) - comparison = frame.all_properties() - print(comparison) - -Data Transformations --------------------- - -openseries provides various data transformation methods: - -.. code-block:: python - - # Convert prices to returns (modifies original) - sp500.value_to_ret() - print(f"Returns series length: {sp500.length}") - - # Convert to log returns (modifies original) - sp500.value_to_log() - - # Calculate rolling statistics - rolling_vol = sp500.rolling_vol(observations=30) # 30-day rolling volatility - rolling_ret = sp500.rolling_return(observations=30) # 30-day rolling returns - - # Resample to monthly data (modifies original) - sp500.resample_to_business_period_ends(freq="BME") - print(f"Monthly data points: {sp500.length}") - -Exporting Results ------------------ - -Save your analysis results: - -.. code-block:: python - - # Export to Excel - sp500.to_xlsx(filename="sp500_analysis.xlsx") - - # Export to JSON - sp500.to_json(filename="sp500_data.json", what_output="tsdf") - -Working with Business Days --------------------------- - -openseries handles business day calendars automatically: - -.. code-block:: python - - # Align to Swedish business days (modifies original) - sp500.align_index_to_local_cdays(countries="SE") - - # Use multiple countries (modifies original) - sp500.align_index_to_local_cdays(countries=["US", "GB"]) - - # Handle missing values (modifies original) - sp500.value_nan_handle() # Forward fill NaN values - -Next Steps ----------- - -Now that you've learned the basics, explore: - -1. **Tutorials** - Detailed examples for specific use cases -2. **API Reference** - Complete documentation of all methods and properties -3. **Examples** - Real-world analysis scenarios - -Key Concepts to Remember ------------------------- - -- **OpenTimeSeries**: For single asset analysis -- **OpenFrame**: For multi-asset and portfolio analysis -- **ValueType**: Enum to identify data types (prices, returns, etc.) -- **Business day handling**: Automatic alignment to trading calendars -- **Interactive plotting**: Built-in Plotly integration -- **Type safety**: Pydantic-based validation ensures data integrity - -Common Patterns ---------------- - -Here are some common usage patterns: - -.. code-block:: python - - # Pattern 1: Load, analyze, visualize - series = OpenTimeSeries.from_df(dframe=data['Close']) - series.set_new_label(lvl_zero="Asset") - metrics = series.all_properties() - series.plot_series() - - # Pattern 2: Multi-asset comparison - frame = OpenFrame(constituents=[series1, series2, series3]) - comparison = frame.all_properties() - correlations = frame.correl_matrix - - # Pattern 3: Portfolio construction (built-in strategies) - portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights") - portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) - frame.add_timeseries(portfolio) - - # Pattern 3b: Custom portfolio construction (create fresh frame) - custom_frame = OpenFrame(constituents=[series1, series2, series3]) - custom_frame.weights = [0.4, 0.3, 0.3] - custom_df = custom_frame.make_portfolio(name="Custom Portfolio") - custom_portfolio = OpenTimeSeries.from_df(dframe=custom_df) - - # Pattern 4: Risk analysis - risk_series = series.from_deepcopy() # Create copy for risk analysis - var_95 = risk_series.var_down # VaR on returns - max_dd = series.max_drawdown - rolling_risk = risk_series.rolling_vol(observations=252) - - # Drawdown analysis (on original series) - series.to_drawdown_series() # Convert to drawdown (modifies original) - -This should give you a solid foundation to start using openseries for your financial analysis needs! diff --git a/docs/build/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/build/html/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803..00000000 --- a/docs/build/html/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/docs/build/html/_static/basic.css b/docs/build/html/_static/basic.css deleted file mode 100644 index 4738b2ed..00000000 --- a/docs/build/html/_static/basic.css +++ /dev/null @@ -1,906 +0,0 @@ -/* - * Sphinx stylesheet -- basic theme. - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin-top: 10px; -} - -ul.search li { - padding: 5px 0; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 360px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a:visited { - color: #551A8B; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -nav.contents, -aside.topic, -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -nav.contents, -aside.topic, -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -nav.contents > :last-child, -aside.topic > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -nav.contents::after, -aside.topic::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -aside.footnote > span, -div.citation > span { - float: left; -} -aside.footnote > span:last-of-type, -div.citation > span:last-of-type { - padding-right: 0.5em; -} -aside.footnote > p { - margin-left: 2em; -} -div.citation > p { - margin-left: 4em; -} -aside.footnote > p:last-of-type, -div.citation > p:last-of-type { - margin-bottom: 0em; -} -aside.footnote > p:last-of-type:after, -div.citation > p:last-of-type:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.sig dd { - margin-top: 0px; - margin-bottom: 0px; -} - -.sig dl { - margin-top: 0px; - margin-bottom: 0px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/build/html/_static/css/badge_only.css b/docs/build/html/_static/css/badge_only.css deleted file mode 100644 index 88ba55b9..00000000 --- a/docs/build/html/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/build/html/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/docs/build/html/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/build/html/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/docs/build/html/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/build/html/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/docs/build/html/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-bold-italic.woff b/docs/build/html/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-bold.woff b/docs/build/html/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-bold.woff2 b/docs/build/html/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-normal-italic.woff b/docs/build/html/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-normal.woff b/docs/build/html/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/docs/build/html/_static/css/fonts/lato-normal.woff2 b/docs/build/html/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/docs/build/html/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/docs/build/html/_static/css/theme.css b/docs/build/html/_static/css/theme.css deleted file mode 100644 index a88467c1..00000000 --- a/docs/build/html/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%;float:none;margin-left:0}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/build/html/_static/custom.css b/docs/build/html/_static/custom.css deleted file mode 100644 index 274a850f..00000000 --- a/docs/build/html/_static/custom.css +++ /dev/null @@ -1,217 +0,0 @@ -/* Custom CSS for openseries documentation */ - -/* Improve code block styling */ -.highlight { - background: #f8f9fa !important; - border: 1px solid #e9ecef; - border-radius: 4px; - padding: 1em; - margin: 1em 0; -} - -/* Better table styling */ -table.docutils { - border-collapse: collapse; - border-spacing: 0; - width: 100%; - margin: 1em 0; -} - -table.docutils th, -table.docutils td { - border: 1px solid #ddd; - padding: 8px 12px; - text-align: left; -} - -table.docutils th { - background-color: #f8f9fa; - font-weight: bold; -} - -/* Improve admonition styling */ -.admonition { - margin: 1em 0; - padding: 1em; - border-left: 4px solid #007bff; - background-color: #f8f9fa; - border-radius: 4px; -} - -.admonition.note { - border-left-color: #17a2b8; -} - -.admonition.warning { - border-left-color: #ffc107; - background-color: #fff3cd; -} - -.admonition.danger { - border-left-color: #dc3545; - background-color: #f8d7da; -} - -/* Better spacing for method signatures */ -.sig { - background-color: #f8f9fa; - border: 1px solid #e9ecef; - border-radius: 4px; - padding: 0.5em; - margin: 0.5em 0; - font-family: 'Courier New', monospace; -} - -/* Improve navigation */ -.wy-nav-content { - max-width: 1200px; -} - -/* Better mobile responsiveness */ -@media screen and (max-width: 768px) { - .wy-nav-content { - margin-left: 0; - } - - .wy-nav-side { - left: -300px; - } -} - -/* Custom styling for API documentation */ -.class > dt { - background-color: #e3f2fd; - border-left: 4px solid #2196f3; - padding: 0.5em; - margin-top: 1em; -} - -.method > dt { - background-color: #f3e5f5; - border-left: 4px solid #9c27b0; - padding: 0.5em; - margin-top: 0.5em; -} - -.function > dt { - background-color: #e8f5e8; - border-left: 4px solid #4caf50; - padding: 0.5em; - margin-top: 0.5em; -} - -/* Improve readability of parameter lists */ -.field-list { - margin: 1em 0; -} - -.field-list dt { - font-weight: bold; - margin-top: 0.5em; -} - -.field-list dd { - margin-left: 2em; - margin-bottom: 0.5em; -} - -/* Style for version badges */ -.version-badge { - display: inline-block; - padding: 0.2em 0.5em; - background-color: #007bff; - color: white; - border-radius: 3px; - font-size: 0.8em; - margin-left: 0.5em; -} - -/* Improve search results */ -.search-results .highlighted { - background-color: #fff3cd; - padding: 0.1em 0.2em; - border-radius: 2px; -} - -/* Better styling for toctree */ -.toctree-wrapper ul { - list-style-type: none; - padding-left: 0; -} - -.toctree-wrapper li { - margin: 0.5em 0; - padding-left: 1em; - border-left: 2px solid #e9ecef; -} - -.toctree-wrapper a { - text-decoration: none; - color: #007bff; -} - -.toctree-wrapper a:hover { - text-decoration: underline; -} - -/* Custom styling for examples */ -.example-box { - background-color: #f8f9fa; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5em; - margin: 1em 0; -} - -.example-title { - font-weight: bold; - color: #495057; - margin-bottom: 1em; - font-size: 1.1em; -} - -/* Improve inline code styling */ -code.literal { - background-color: #f8f9fa; - color: #e83e8c; - padding: 0.2em 0.4em; - border-radius: 3px; - font-size: 0.9em; -} - -/* Better styling for definition lists */ -dl dt { - font-weight: bold; - margin-top: 1em; - color: #495057; -} - -dl dd { - margin-left: 2em; - margin-bottom: 0.5em; -} - -/* Improve header hierarchy */ -h1 { - border-bottom: 3px solid #007bff; - padding-bottom: 0.5em; -} - -h2 { - border-bottom: 2px solid #6c757d; - padding-bottom: 0.3em; -} - -h3 { - color: #495057; - margin-top: 1.5em; -} - -/* Custom footer styling */ -.footer { - margin-top: 2em; - padding-top: 1em; - border-top: 1px solid #dee2e6; - color: #6c757d; - font-size: 0.9em; -} diff --git a/docs/build/html/_static/doctools.js b/docs/build/html/_static/doctools.js deleted file mode 100644 index 807cdb17..00000000 --- a/docs/build/html/_static/doctools.js +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Base JavaScript utilities for all Sphinx HTML documentation. - */ -"use strict"; - -const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ - "TEXTAREA", - "INPUT", - "SELECT", - "BUTTON", -]); - -const _ready = (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } -}; - -/** - * Small JavaScript module for the documentation. - */ -const Documentation = { - init: () => { - Documentation.initDomainIndexTable(); - Documentation.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS: {}, - PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), - LOCALE: "unknown", - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext: (string) => { - const translated = Documentation.TRANSLATIONS[string]; - switch (typeof translated) { - case "undefined": - return string; // no translation - case "string": - return translated; // translation exists - default: - return translated[0]; // (singular, plural) translation tuple exists - } - }, - - ngettext: (singular, plural, n) => { - const translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated !== "undefined") - return translated[Documentation.PLURAL_EXPR(n)]; - return n === 1 ? singular : plural; - }, - - addTranslations: (catalog) => { - Object.assign(Documentation.TRANSLATIONS, catalog.messages); - Documentation.PLURAL_EXPR = new Function( - "n", - `return (${catalog.plural_expr})`, - ); - Documentation.LOCALE = catalog.locale; - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar: () => { - document.querySelectorAll("input[name=q]")[0]?.focus(); - }, - - /** - * Initialise the domain index toggle buttons - */ - initDomainIndexTable: () => { - const toggler = (el) => { - const idNumber = el.id.substr(7); - const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); - if (el.src.substr(-9) === "minus.png") { - el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; - toggledRows.forEach((el) => (el.style.display = "none")); - } else { - el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; - toggledRows.forEach((el) => (el.style.display = "")); - } - }; - - const togglerElements = document.querySelectorAll("img.toggler"); - togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)), - ); - togglerElements.forEach((el) => (el.style.display = "")); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); - }, - - initOnKeyListeners: () => { - // only install a listener if it is really needed - if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS - && !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - ) - return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) - return; - // bail with special keys - if (event.altKey || event.ctrlKey || event.metaKey) return; - - if (!event.shiftKey) { - switch (event.key) { - case "ArrowLeft": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const prevLink = document.querySelector('link[rel="prev"]'); - if (prevLink && prevLink.href) { - window.location.href = prevLink.href; - event.preventDefault(); - } - break; - case "ArrowRight": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const nextLink = document.querySelector('link[rel="next"]'); - if (nextLink && nextLink.href) { - window.location.href = nextLink.href; - event.preventDefault(); - } - break; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case "/": - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; - Documentation.focusSearchBar(); - event.preventDefault(); - } - }); - }, -}; - -// quick alias for translations -const _ = Documentation.gettext; - -_ready(Documentation.init); diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js deleted file mode 100644 index 5355a780..00000000 --- a/docs/build/html/_static/documentation_options.js +++ /dev/null @@ -1,13 +0,0 @@ -const DOCUMENTATION_OPTIONS = { - VERSION: '2.1.10', - LANGUAGE: 'en', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/docs/build/html/_static/file.png b/docs/build/html/_static/file.png deleted file mode 100644 index a858a410..00000000 Binary files a/docs/build/html/_static/file.png and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.eot b/docs/build/html/_static/fonts/Lato/lato-bold.eot deleted file mode 100644 index 3361183a..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bold.eot and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.ttf b/docs/build/html/_static/fonts/Lato/lato-bold.ttf deleted file mode 100644 index 29f691d5..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bold.ttf and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.woff b/docs/build/html/_static/fonts/Lato/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bold.woff and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bold.woff2 b/docs/build/html/_static/fonts/Lato/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bold.woff2 and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot b/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot deleted file mode 100644 index 3d415493..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf b/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf deleted file mode 100644 index f402040b..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff b/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.eot b/docs/build/html/_static/fonts/Lato/lato-italic.eot deleted file mode 100644 index 3f826421..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-italic.eot and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.ttf b/docs/build/html/_static/fonts/Lato/lato-italic.ttf deleted file mode 100644 index b4bfc9b2..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-italic.ttf and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.woff b/docs/build/html/_static/fonts/Lato/lato-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-italic.woff and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-italic.woff2 b/docs/build/html/_static/fonts/Lato/lato-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-italic.woff2 and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.eot b/docs/build/html/_static/fonts/Lato/lato-regular.eot deleted file mode 100644 index 11e3f2a5..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-regular.eot and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.ttf b/docs/build/html/_static/fonts/Lato/lato-regular.ttf deleted file mode 100644 index 74decd9e..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-regular.ttf and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.woff b/docs/build/html/_static/fonts/Lato/lato-regular.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-regular.woff and /dev/null differ diff --git a/docs/build/html/_static/fonts/Lato/lato-regular.woff2 b/docs/build/html/_static/fonts/Lato/lato-regular.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/docs/build/html/_static/fonts/Lato/lato-regular.woff2 and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot deleted file mode 100644 index 79dc8efe..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf deleted file mode 100644 index df5d1df2..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot deleted file mode 100644 index 2f7ca78a..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf deleted file mode 100644 index eb52a790..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff and /dev/null differ diff --git a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 and /dev/null differ diff --git a/docs/build/html/_static/jquery.js b/docs/build/html/_static/jquery.js deleted file mode 100644 index c4c6022f..00000000 --- a/docs/build/html/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); - - const languagesHTML = ` -
-
Languages
- ${languages - .map( - (translation) => ` -
- ${translation.language.code} -
- `, - ) - .join("\n")} -
- `; - return languagesHTML; - } - - function renderVersions(config) { - if (!config.versions.active.length) { - return ""; - } - const versionsHTML = ` -
-
Versions
- ${config.versions.active - .map( - (version) => ` -
- ${version.slug} -
- `, - ) - .join("\n")} -
- `; - return versionsHTML; - } - - function renderDownloads(config) { - if (!Object.keys(config.versions.current.downloads).length) { - return ""; - } - const downloadsNameDisplay = { - pdf: "PDF", - epub: "Epub", - htmlzip: "HTML", - }; - - const downloadsHTML = ` -
-
Downloads
- ${Object.entries(config.versions.current.downloads) - .map( - ([name, url]) => ` -
- ${downloadsNameDisplay[name]} -
- `, - ) - .join("\n")} -
- `; - return downloadsHTML; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const flyout = ` -
- - Read the Docs - v: ${config.versions.current.slug} - - -
-
- ${renderLanguages(config)} - ${renderVersions(config)} - ${renderDownloads(config)} -
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
- Downloads -
-
-
-
Search
-
-
- -
-
-
-
- - Hosted by Read the Docs - -
-
- `; - - // Inject the generated flyout into the body HTML element. - document.body.insertAdjacentHTML("beforeend", flyout); - - // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. - document - .querySelector("#flyout-search-form") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); - }) -} - -if (themeLanguageSelector || themeVersionSelector) { - function onSelectorSwitch(event) { - const option = event.target.selectedIndex; - const item = event.target.options[option]; - window.location.href = item.dataset.url; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const versionSwitch = document.querySelector( - "div.switch-menus > div.version-switch", - ); - if (themeVersionSelector) { - let versions = config.versions.active; - if (config.versions.current.hidden || config.versions.current.type === "external") { - versions.unshift(config.versions.current); - } - const versionSelect = ` - - `; - - versionSwitch.innerHTML = versionSelect; - versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - - const languageSwitch = document.querySelector( - "div.switch-menus > div.language-switch", - ); - - if (themeLanguageSelector) { - if (config.projects.translations.length) { - // Add the current language to the options on the selector - let languages = config.projects.translations.concat( - config.projects.current, - ); - languages = languages.sort((a, b) => - a.language.name.localeCompare(b.language.name), - ); - - const languageSelect = ` - - `; - - languageSwitch.innerHTML = languageSelect; - languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - else { - languageSwitch.remove(); - } - } - }); -} - -document.addEventListener("readthedocs-addons-data-ready", function (event) { - // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. - document - .querySelector("[role='search'] input") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); -}); \ No newline at end of file diff --git a/docs/build/html/_static/language_data.js b/docs/build/html/_static/language_data.js deleted file mode 100644 index 57767864..00000000 --- a/docs/build/html/_static/language_data.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This script contains the language-specific data used by searchtools.js, - * namely the set of stopwords, stemmer, scorer and splitter. - */ - -const stopwords = new Set(["a", "about", "above", "after", "again", "against", "all", "am", "an", "and", "any", "are", "aren't", "as", "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "can't", "cannot", "could", "couldn't", "did", "didn't", "do", "does", "doesn't", "doing", "don't", "down", "during", "each", "few", "for", "from", "further", "had", "hadn't", "has", "hasn't", "have", "haven't", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him", "himself", "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if", "in", "into", "is", "isn't", "it", "it's", "its", "itself", "let's", "me", "more", "most", "mustn't", "my", "myself", "no", "nor", "not", "of", "off", "on", "once", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "same", "shan't", "she", "she'd", "she'll", "she's", "should", "shouldn't", "so", "some", "such", "than", "that", "that's", "the", "their", "theirs", "them", "themselves", "then", "there", "there's", "these", "they", "they'd", "they'll", "they're", "they've", "this", "those", "through", "to", "too", "under", "until", "up", "very", "was", "wasn't", "we", "we'd", "we'll", "we're", "we've", "were", "weren't", "what", "what's", "when", "when's", "where", "where's", "which", "while", "who", "who's", "whom", "why", "why's", "with", "won't", "would", "wouldn't", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves"]); -window.stopwords = stopwords; // Export to global scope - - -/* Non-minified versions are copied as separate JavaScript files, if available */ -BaseStemmer=function(){this.current="",this.cursor=0,this.limit=0,this.limit_backward=0,this.bra=0,this.ket=0,this.setCurrent=function(t){this.current=t,this.cursor=0,this.limit=this.current.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},this.getCurrent=function(){return this.current},this.copy_from=function(t){this.current=t.current,this.cursor=t.cursor,this.limit=t.limit,this.limit_backward=t.limit_backward,this.bra=t.bra,this.ket=t.ket},this.in_grouping=function(t,r,i){return!(this.cursor>=this.limit||i<(i=this.current.charCodeAt(this.cursor))||i>>3]&1<<(7&i))||(this.cursor++,0))},this.go_in_grouping=function(t,r,i){for(;this.cursor>>3]&1<<(7&s)))return!0;this.cursor++}return!1},this.in_grouping_b=function(t,r,i){return!(this.cursor<=this.limit_backward||i<(i=this.current.charCodeAt(this.cursor-1))||i>>3]&1<<(7&i))||(this.cursor--,0))},this.go_in_grouping_b=function(t,r,i){for(;this.cursor>this.limit_backward;){var s=this.current.charCodeAt(this.cursor-1);if(i>>3]&1<<(7&s)))return!0;this.cursor--}return!1},this.out_grouping=function(t,r,i){return!(this.cursor>=this.limit)&&(i<(i=this.current.charCodeAt(this.cursor))||i>>3]&1<<(7&i)))&&(this.cursor++,!0)},this.go_out_grouping=function(t,r,i){for(;this.cursor>>3]&1<<(7&s)))return!0;this.cursor++}return!1},this.out_grouping_b=function(t,r,i){return!(this.cursor<=this.limit_backward)&&(i<(i=this.current.charCodeAt(this.cursor-1))||i>>3]&1<<(7&i)))&&(this.cursor--,!0)},this.go_out_grouping_b=function(t,r,i){for(;this.cursor>this.limit_backward;){var s=this.current.charCodeAt(this.cursor-1);if(s<=i&&r<=s&&0!=(t[(s-=r)>>>3]&1<<(7&s)))return!0;this.cursor--}return!1},this.eq_s=function(t){return!(this.limit-this.cursor>>1),o=0,a=e=(l=t[r])[0].length){if(this.cursor=s+l[0].length,l.length<4)return l[2];var g=l[3](this);if(this.cursor=s+l[0].length,g)return l[2]}}while(0<=(r=l[1]));return 0},this.find_among_b=function(t){for(var r=0,i=t.length,s=this.cursor,h=this.limit_backward,e=0,n=0,c=!1;;){for(var u,o=r+(i-r>>1),a=0,l=e=(u=t[r])[0].length){if(this.cursor=s-u[0].length,u.length<4)return u[2];var g=u[3](this);if(this.cursor=s-u[0].length,g)return u[2]}}while(0<=(r=u[1]));return 0},this.replace_s=function(t,r,i){var s=i.length-(r-t);return this.current=this.current.slice(0,t)+i+this.current.slice(r),this.limit+=s,this.cursor>=r?this.cursor+=s:this.cursor>t&&(this.cursor=t),s},this.slice_check=function(){return!(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>this.current.length)},this.slice_from=function(t){var r=!1;return this.slice_check()&&(this.replace_s(this.bra,this.ket,t),r=!0),r},this.slice_del=function(){return this.slice_from("")},this.insert=function(t,r,i){r=this.replace_s(t,r,i);t<=this.bra&&(this.bra+=r),t<=this.ket&&(this.ket+=r)},this.slice_to=function(){var t="";return t=this.slice_check()?this.current.slice(this.bra,this.ket):t},this.assign_to=function(){return this.current.slice(0,this.limit)}}; -var EnglishStemmer=function(){var a=new BaseStemmer,c=[["arsen",-1,-1],["commun",-1,-1],["emerg",-1,-1],["gener",-1,-1],["later",-1,-1],["organ",-1,-1],["past",-1,-1],["univers",-1,-1]],o=[["'",-1,1],["'s'",0,1],["'s",-1,1]],u=[["ied",-1,2],["s",-1,3],["ies",1,2],["sses",1,1],["ss",1,-1],["us",1,-1]],t=[["succ",-1,1],["proc",-1,1],["exc",-1,1]],l=[["even",-1,2],["cann",-1,2],["inn",-1,2],["earr",-1,2],["herr",-1,2],["out",-1,2],["y",-1,1]],n=[["",-1,-1],["ed",0,2],["eed",1,1],["ing",0,3],["edly",0,2],["eedly",4,1],["ingly",0,2]],f=[["",-1,3],["bb",0,2],["dd",0,2],["ff",0,2],["gg",0,2],["bl",0,1],["mm",0,2],["nn",0,2],["pp",0,2],["rr",0,2],["at",0,1],["tt",0,2],["iz",0,1]],_=[["anci",-1,3],["enci",-1,2],["ogi",-1,14],["li",-1,16],["bli",3,12],["abli",4,4],["alli",3,8],["fulli",3,9],["lessli",3,15],["ousli",3,10],["entli",3,5],["aliti",-1,8],["biliti",-1,12],["iviti",-1,11],["tional",-1,1],["ational",14,7],["alism",-1,8],["ation",-1,7],["ization",17,6],["izer",-1,6],["ator",-1,7],["iveness",-1,11],["fulness",-1,9],["ousness",-1,10],["ogist",-1,13]],m=[["icate",-1,4],["ative",-1,6],["alize",-1,3],["iciti",-1,4],["ical",-1,4],["tional",-1,1],["ational",5,2],["ful",-1,5],["ness",-1,5]],b=[["ic",-1,1],["ance",-1,1],["ence",-1,1],["able",-1,1],["ible",-1,1],["ate",-1,1],["ive",-1,1],["ize",-1,1],["iti",-1,1],["al",-1,1],["ism",-1,1],["ion",-1,2],["er",-1,1],["ous",-1,1],["ant",-1,1],["ent",-1,1],["ment",15,1],["ement",16,1]],k=[["e",-1,1],["l",-1,2]],g=[["andes",-1,-1],["atlas",-1,-1],["bias",-1,-1],["cosmos",-1,-1],["early",-1,5],["gently",-1,3],["howe",-1,-1],["idly",-1,2],["news",-1,-1],["only",-1,6],["singly",-1,7],["skies",-1,1],["sky",-1,-1],["ugly",-1,4]],d=[17,64],v=[17,65,16,1],i=[1,17,65,208,1],w=[55,141,2],p=!1,y=0,h=0;function q(){var r=a.limit-a.cursor;return!!(a.out_grouping_b(i,89,121)&&a.in_grouping_b(v,97,121)&&a.out_grouping_b(v,97,121)||(a.cursor=a.limit-r,a.out_grouping_b(v,97,121)&&a.in_grouping_b(v,97,121)&&!(a.cursor>a.limit_backward))||(a.cursor=a.limit-r,a.eq_s_b("past")))}function z(){return h<=a.cursor}function Y(){return y<=a.cursor}this.stem=function(){var r=a.cursor;if(!(()=>{var r;if(a.bra=a.cursor,0!=(r=a.find_among(g))&&(a.ket=a.cursor,!(a.cursora.limit)a.cursor=i;else{a.cursor=e,a.cursor=r,(()=>{p=!1;var r=a.cursor;if(a.bra=a.cursor,!a.eq_s("'")||(a.ket=a.cursor,a.slice_del())){a.cursor=r;r=a.cursor;if(a.bra=a.cursor,a.eq_s("y")){if(a.ket=a.cursor,!a.slice_from("Y"))return;p=!0}a.cursor=r;for(r=a.cursor;;){var i=a.cursor;r:{for(;;){var e=a.cursor;if(a.in_grouping(v,97,121)&&(a.bra=a.cursor,a.eq_s("y"))){a.ket=a.cursor,a.cursor=e;break}if(a.cursor=e,a.cursor>=a.limit)break r;a.cursor++}if(!a.slice_from("Y"))return;p=!0;continue}a.cursor=i;break}a.cursor=r}})(),h=a.limit,y=a.limit;i=a.cursor;r:{var s=a.cursor;if(0==a.find_among(c)){if(a.cursor=s,!a.go_out_grouping(v,97,121))break r;if(a.cursor++,!a.go_in_grouping(v,97,121))break r;a.cursor++}h=a.cursor,a.go_out_grouping(v,97,121)&&(a.cursor++,a.go_in_grouping(v,97,121))&&(a.cursor++,y=a.cursor)}a.cursor=i,a.limit_backward=a.cursor,a.cursor=a.limit;var e=a.limit-a.cursor,r=((()=>{var r=a.limit-a.cursor;if(a.ket=a.cursor,0==a.find_among_b(o))a.cursor=a.limit-r;else if(a.bra=a.cursor,!a.slice_del())return;if(a.ket=a.cursor,0!=(r=a.find_among_b(u)))switch(a.bra=a.cursor,r){case 1:if(a.slice_from("ss"))break;return;case 2:r:{var i=a.limit-a.cursor,e=a.cursor-2;if(!(e{a.ket=a.cursor,o=a.find_among_b(n),a.bra=a.cursor;r:{var r=a.limit-a.cursor;i:{switch(o){case 1:var i=a.limit-a.cursor;e:{var e=a.limit-a.cursor;if(0==a.find_among_b(t)||a.cursor>a.limit_backward){if(a.cursor=a.limit-e,!z())break e;if(!a.slice_from("ee"))return}}a.cursor=a.limit-i;break;case 2:break i;case 3:if(0==(o=a.find_among_b(l)))break i;switch(o){case 1:var s=a.limit-a.cursor;if(!a.out_grouping_b(v,97,121))break i;if(a.cursor>a.limit_backward)break i;if(a.cursor=a.limit-s,a.bra=a.cursor,a.slice_from("ie"))break;return;case 2:if(a.cursor>a.limit_backward)break i}}break r}a.cursor=a.limit-r;var c=a.limit-a.cursor;if(!a.go_out_grouping_b(v,97,121))return;if(a.cursor--,a.cursor=a.limit-c,!a.slice_del())return;a.ket=a.cursor,a.bra=a.cursor;var o,c=a.limit-a.cursor;switch(o=a.find_among_b(f)){case 1:return a.slice_from("e");case 2:var u=a.limit-a.cursor;if(a.in_grouping_b(d,97,111)&&!(a.cursor>a.limit_backward))return;a.cursor=a.limit-u;break;case 3:return a.cursor!=h||(u=a.limit-a.cursor,q()&&(a.cursor=a.limit-u,a.slice_from("e")))}if(a.cursor=a.limit-c,a.ket=a.cursor,a.cursor<=a.limit_backward)return;if(a.cursor--,a.bra=a.cursor,!a.slice_del())return}})(),a.cursor=a.limit-r,a.limit-a.cursor),r=(a.ket=a.cursor,e=a.limit-a.cursor,(a.eq_s_b("y")||(a.cursor=a.limit-e,a.eq_s_b("Y")))&&(a.bra=a.cursor,a.out_grouping_b(v,97,121))&&a.cursor>a.limit_backward&&a.slice_from("i"),a.cursor=a.limit-i,a.limit-a.cursor),e=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(_))&&(a.bra=a.cursor,z()))switch(r){case 1:if(a.slice_from("tion"))break;return;case 2:if(a.slice_from("ence"))break;return;case 3:if(a.slice_from("ance"))break;return;case 4:if(a.slice_from("able"))break;return;case 5:if(a.slice_from("ent"))break;return;case 6:if(a.slice_from("ize"))break;return;case 7:if(a.slice_from("ate"))break;return;case 8:if(a.slice_from("al"))break;return;case 9:if(a.slice_from("ful"))break;return;case 10:if(a.slice_from("ous"))break;return;case 11:if(a.slice_from("ive"))break;return;case 12:if(a.slice_from("ble"))break;return;case 13:if(a.slice_from("og"))break;return;case 14:if(!a.eq_s_b("l"))return;if(a.slice_from("og"))break;return;case 15:if(a.slice_from("less"))break;return;case 16:if(!a.in_grouping_b(w,99,116))return;if(a.slice_del())break}})(),a.cursor=a.limit-r,a.limit-a.cursor),i=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(m))&&(a.bra=a.cursor,z()))switch(r){case 1:if(a.slice_from("tion"))break;return;case 2:if(a.slice_from("ate"))break;return;case 3:if(a.slice_from("al"))break;return;case 4:if(a.slice_from("ic"))break;return;case 5:if(a.slice_del())break;return;case 6:if(!Y())return;if(a.slice_del())break}})(),a.cursor=a.limit-e,a.limit-a.cursor),r=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(b))&&(a.bra=a.cursor,Y()))switch(r){case 1:if(a.slice_del())break;return;case 2:var i=a.limit-a.cursor;if(!a.eq_s_b("s")&&(a.cursor=a.limit-i,!a.eq_s_b("t")))return;if(a.slice_del())break}})(),a.cursor=a.limit-i,a.limit-a.cursor),e=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(k)))switch(a.bra=a.cursor,r){case 1:if(!Y()){if(!z())return;var i=a.limit-a.cursor;if(q())return;a.cursor=a.limit-i}if(a.slice_del())break;return;case 2:if(!Y())return;if(!a.eq_s_b("l"))return;if(a.slice_del())break}})(),a.cursor=a.limit-r,a.cursor=a.limit_backward,a.cursor);(()=>{if(p)for(;;){var r=a.cursor;r:{for(;;){var i=a.cursor;if(a.bra=a.cursor,a.eq_s("Y")){a.ket=a.cursor,a.cursor=i;break}if(a.cursor=i,a.cursor>=a.limit)break r;a.cursor++}if(a.slice_from("y"))continue;return}a.cursor=r;break}})(),a.cursor=e}}return!0},this.stemWord=function(r){return a.setCurrent(r),this.stem(),a.getCurrent()}}; -window.Stemmer = EnglishStemmer; diff --git a/docs/build/html/_static/minus.png b/docs/build/html/_static/minus.png deleted file mode 100644 index d96755fd..00000000 Binary files a/docs/build/html/_static/minus.png and /dev/null differ diff --git a/docs/build/html/_static/plus.png b/docs/build/html/_static/plus.png deleted file mode 100644 index 7107cec9..00000000 Binary files a/docs/build/html/_static/plus.png and /dev/null differ diff --git a/docs/build/html/_static/pygments.css b/docs/build/html/_static/pygments.css deleted file mode 100644 index 6f8b210a..00000000 --- a/docs/build/html/_static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #F00 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #04D } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ -.highlight .no { color: #800 } /* Name.Constant */ -.highlight .nd { color: #A2F } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #00F } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #BBB } /* Text.Whitespace */ -.highlight .mb { color: #666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666 } /* Literal.Number.Float */ -.highlight .mh { color: #666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #00F } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/build/html/_static/searchtools.js b/docs/build/html/_static/searchtools.js deleted file mode 100644 index e29b1c75..00000000 --- a/docs/build/html/_static/searchtools.js +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Sphinx JavaScript utilities for the full-text search. - */ -"use strict"; - -/** - * Simple result scoring code. - */ -if (typeof Scorer === "undefined") { - var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [docname, title, anchor, descr, score, filename] - // and returns the new score. - /* - score: result => { - const [docname, title, anchor, descr, score, filename, kind] = result - return score - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5, // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2, - }; -} - -// Global search result kind enum, used by themes to style search results. -// prettier-ignore -class SearchResultKind { - static get index() { return "index"; } - static get object() { return "object"; } - static get text() { return "text"; } - static get title() { return "title"; } -} - -const _removeChildren = (element) => { - while (element && element.lastChild) element.removeChild(element.lastChild); -}; - -/** - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - */ -const _escapeRegExp = (string) => - string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string - -const _escapeHTML = (text) => { - return text - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); -}; - -const _displayItem = (item, searchTerms, highlightTerms) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - const contentRoot = document.documentElement.dataset.content_root; - - const [docName, title, anchor, descr, score, _filename, kind] = item; - - let listItem = document.createElement("li"); - // Add a class representing the item's type: - // can be used by a theme's CSS selector for styling - // See SearchResultKind for the class names. - listItem.classList.add(`kind-${kind}`); - let requestUrl; - let linkUrl; - if (docBuilder === "dirhtml") { - // dirhtml builder - let dirname = docName + "/"; - if (dirname.match(/\/index\/$/)) - dirname = dirname.substring(0, dirname.length - 6); - else if (dirname === "index/") dirname = ""; - requestUrl = contentRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = contentRoot + docName + docFileSuffix; - linkUrl = docName + docLinkSuffix; - } - let linkEl = listItem.appendChild(document.createElement("a")); - linkEl.href = linkUrl + anchor; - linkEl.dataset.score = score; - linkEl.innerHTML = _escapeHTML(title); - if (descr) { - listItem.appendChild(document.createElement("span")).innerHTML = - ` (${_escapeHTML(descr)})`; - // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) - // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js - highlightTerms.forEach((term) => - _highlightText(listItem, term, "highlighted"), - ); - } else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor), - ); - // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) - // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js - highlightTerms.forEach((term) => - _highlightText(listItem, term, "highlighted"), - ); - }); - Search.output.appendChild(listItem); -}; -const _finishSearch = (resultCount) => { - Search.stopPulse(); - Search.title.innerText = _("Search Results"); - if (!resultCount) - Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.", - ); - else - Search.status.innerText = Documentation.ngettext( - "Search finished, found one page matching the search query.", - "Search finished, found ${resultCount} pages matching the search query.", - resultCount, - ).replace("${resultCount}", resultCount); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms, - highlightTerms, -) => { - // results left, load the summary and display it - // this is intended to be dynamic (don't sub resultsCount) - if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5, - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; -// Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. -// Order the results by score (in opposite order of appearance, since the -// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. -const _orderResultsByScoreThenName = (a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; -}; - -/** - * Default splitQuery function. Can be overridden in ``sphinx.search`` with a - * custom function per language. - * - * The regular expression works by splitting the string on consecutive characters - * that are not Unicode letters, numbers, underscores, or emoji characters. - * This is the same as ``\W+`` in Python, preserving the surrogate pair area. - */ -if (typeof splitQuery === "undefined") { - var splitQuery = (query) => - query - .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter((term) => term); // remove remaining empty strings -} - -/** - * Search Module - */ -const Search = { - _index: null, - _queued_query: null, - _pulse_status: -1, - - htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString( - htmlString, - "text/html", - ); - for (const removalQuery of [".headerlink", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { - el.remove(); - }); - } - if (anchor) { - const anchorContent = htmlElement.querySelector( - `[role="main"] ${anchor}`, - ); - if (anchorContent) return anchorContent.textContent; - - console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`, - ); - } - - // if anchor not specified or not found, fall back to main content - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent) return docContent.textContent; - - console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template.", - ); - return ""; - }, - - init: () => { - const query = new URLSearchParams(window.location.search).get("q"); - document - .querySelectorAll('input[name="q"]') - .forEach((el) => (el.value = query)); - if (query) Search.performSearch(query); - }, - - loadIndex: (url) => - (document.body.appendChild(document.createElement("script")).src = url), - - setIndex: (index) => { - Search._index = index; - if (Search._queued_query !== null) { - const query = Search._queued_query; - Search._queued_query = null; - Search.query(query); - } - }, - - hasIndex: () => Search._index !== null, - - deferQuery: (query) => (Search._queued_query = query), - - stopPulse: () => (Search._pulse_status = -1), - - startPulse: () => { - if (Search._pulse_status >= 0) return; - - const pulse = () => { - Search._pulse_status = (Search._pulse_status + 1) % 4; - Search.dots.innerText = ".".repeat(Search._pulse_status); - if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something (or wait until index is loaded) - */ - performSearch: (query) => { - // create the required interface elements - const searchText = document.createElement("h2"); - searchText.textContent = _("Searching"); - const searchSummary = document.createElement("p"); - searchSummary.classList.add("search-summary"); - searchSummary.innerText = ""; - const searchList = document.createElement("ul"); - searchList.setAttribute("role", "list"); - searchList.classList.add("search"); - - const out = document.getElementById("search-results"); - Search.title = out.appendChild(searchText); - Search.dots = Search.title.appendChild(document.createElement("span")); - Search.status = out.appendChild(searchSummary); - Search.output = out.appendChild(searchList); - - const searchProgress = document.getElementById("search-progress"); - // Some themes don't use the search progress node - if (searchProgress) { - searchProgress.innerText = _("Preparing search..."); - } - Search.startPulse(); - - // index already loaded, the browser was quick! - if (Search.hasIndex()) Search.query(query); - else Search.deferQuery(query); - }, - - _parseQuery: (query) => { - // stem the search terms and add them to the correct list - const stemmer = new Stemmer(); - const searchTerms = new Set(); - const excludedTerms = new Set(); - const highlightTerms = new Set(); - const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); - splitQuery(query.trim()).forEach((queryTerm) => { - const queryTermLower = queryTerm.toLowerCase(); - - // maybe skip this "word" - // stopwords set is from language_data.js - if (stopwords.has(queryTermLower) || queryTerm.match(/^\d+$/)) return; - - // stem the word - let word = stemmer.stemWord(queryTermLower); - // select the correct list - if (word[0] === "-") excludedTerms.add(word.substr(1)); - else { - searchTerms.add(word); - highlightTerms.add(queryTermLower); - } - }); - - if (SPHINX_HIGHLIGHT_ENABLED) { - // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js - localStorage.setItem( - "sphinx_highlight_terms", - [...highlightTerms].join(" "), - ); - } - - // console.debug("SEARCH: searching for:"); - // console.info("required: ", [...searchTerms]); - // console.info("excluded: ", [...excludedTerms]); - - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; - }, - - /** - * execute search (requires search index to be loaded) - */ - _performSearch: ( - query, - searchTerms, - excludedTerms, - highlightTerms, - objectTerms, - ) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - - // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename, kind]. - const normalResults = []; - const nonMainIndexResults = []; - - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase().trim(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if ( - title.toLowerCase().trim().includes(queryLower) - && queryLower.length >= title.length / 2 - ) { - for (const [file, id] of foundTitles) { - const score = Math.round( - (Scorer.title * queryLower.length) / title.length, - ); - const boost = titles[file] === title ? 1 : 0; // add a boost for document titles - normalResults.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score + boost, - filenames[file], - SearchResultKind.title, - ]); - } - } - } - - // search for explicit entries in index directives - for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && queryLower.length >= entry.length / 2) { - for (const [file, id, isMain] of foundEntries) { - const score = Math.round((100 * queryLower.length) / entry.length); - const result = [ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - SearchResultKind.index, - ]; - if (isMain) { - normalResults.push(result); - } else { - nonMainIndexResults.push(result); - } - } - } - } - - // lookup as object - objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)), - ); - - // lookup as search terms in fulltext - normalResults.push( - ...Search.performTermsSearch(searchTerms, excludedTerms), - ); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) { - normalResults.forEach((item) => (item[4] = Scorer.score(item))); - nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); - } - - // Sort each group of results by score and then alphabetically by name. - normalResults.sort(_orderResultsByScoreThenName); - nonMainIndexResults.sort(_orderResultsByScoreThenName); - - // Combine the result groups in (reverse) order. - // Non-main index entries are typically arbitrary cross-references, - // so display them after other results. - let results = [...nonMainIndexResults, ...normalResults]; - - // remove duplicate search results - // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept - let seen = new Set(); - results = results.reverse().reduce((acc, result) => { - let resultStr = result - .slice(0, 4) - .concat([result[5]]) - .map((v) => String(v)) - .join(","); - if (!seen.has(resultStr)) { - acc.push(result); - seen.add(resultStr); - } - return acc; - }, []); - - return results.reverse(); - }, - - query: (query) => { - const [ - searchQuery, - searchTerms, - excludedTerms, - highlightTerms, - objectTerms, - ] = Search._parseQuery(query); - const results = Search._performSearch( - searchQuery, - searchTerms, - excludedTerms, - highlightTerms, - objectTerms, - ); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); - }, - - /** - * search for object names - */ - performObjectSearch: (object, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const objects = Search._index.objects; - const objNames = Search._index.objnames; - const titles = Search._index.titles; - - const results = []; - - const objectSearchCallback = (prefix, match) => { - const name = match[4]; - const fullname = (prefix ? prefix + "." : "") + name; - const fullnameLower = fullname.toLowerCase(); - if (fullnameLower.indexOf(object) < 0) return; - - let score = 0; - const parts = fullnameLower.split("."); - - // check for different match types: exact matches of full name or - // "last name" (i.e. last dotted part) - if (fullnameLower === object || parts.slice(-1)[0] === object) - score += Scorer.objNameMatch; - else if (parts.slice(-1)[0].indexOf(object) > -1) - score += Scorer.objPartialMatch; // matches in last name - - const objName = objNames[match[1]][2]; - const title = titles[match[0]]; - - // If more than one term searched for, we require other words to be - // found in the name/title/description - const otherTerms = new Set(objectTerms); - otherTerms.delete(object); - if (otherTerms.size > 0) { - const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); - if ( - [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) - ) - return; - } - - let anchor = match[3]; - if (anchor === "") anchor = fullname; - else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; - - const descr = objName + _(", in ") + title; - - // add custom score for some objects according to scorer - if (Scorer.objPrio.hasOwnProperty(match[2])) - score += Scorer.objPrio[match[2]]; - else score += Scorer.objPrioDefault; - - results.push([ - docNames[match[0]], - fullname, - "#" + anchor, - descr, - score, - filenames[match[0]], - SearchResultKind.object, - ]); - }; - Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => objectSearchCallback(prefix, array)), - ); - return results; - }, - - /** - * search for full-text terms in the index - */ - performTermsSearch: (searchTerms, excludedTerms) => { - // prepare search - const terms = Search._index.terms; - const titleTerms = Search._index.titleterms; - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - - const scoreMap = new Map(); - const fileMap = new Map(); - - // perform the search on the required terms - searchTerms.forEach((word) => { - const files = []; - // find documents, if any, containing the query word in their text/title term indices - // use Object.hasOwnProperty to avoid mismatching against prototype properties - const arr = [ - { - files: terms.hasOwnProperty(word) ? terms[word] : undefined, - score: Scorer.term, - }, - { - files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, - score: Scorer.title, - }, - ]; - // add support for partial matches - if (word.length > 2) { - const escapedWord = _escapeRegExp(word); - if (!terms.hasOwnProperty(word)) { - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - } - if (!titleTerms.hasOwnProperty(word)) { - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); - }); - } - } - - // no match but word was a required one - if (arr.every((record) => record.files === undefined)) return; - - // found search word in contents - arr.forEach((record) => { - if (record.files === undefined) return; - - let recordFiles = record.files; - if (recordFiles.length === undefined) recordFiles = [recordFiles]; - files.push(...recordFiles); - - // set score for the word in each file - recordFiles.forEach((file) => { - if (!scoreMap.has(file)) scoreMap.set(file, new Map()); - const fileScores = scoreMap.get(file); - fileScores.set(word, record.score); - }); - }); - - // create the mapping - files.forEach((file) => { - if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - }); - }); - - // now check if the files don't contain excluded terms - const results = []; - for (const [file, wordList] of fileMap) { - // check if all requirements are matched - - // as search terms with length < 3 are discarded - const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2, - ).length; - if ( - wordList.length !== searchTerms.size - && wordList.length !== filteredTermCount - ) - continue; - - // ensure that none of the excluded terms is in the search result - if ( - [...excludedTerms].some( - (term) => - terms[term] === file - || titleTerms[term] === file - || (terms[term] || []).includes(file) - || (titleTerms[term] || []).includes(file), - ) - ) - break; - - // select one (max) score for the file. - const score = Math.max(...wordList.map((w) => scoreMap.get(file).get(w))); - // add result to the result list - results.push([ - docNames[file], - titles[file], - "", - null, - score, - filenames[file], - SearchResultKind.text, - ]); - } - return results; - }, - - /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words. - */ - makeSearchSummary: (htmlText, keywords, anchor) => { - const text = Search.htmlToText(htmlText, anchor); - if (text === "") return null; - - const textLower = text.toLowerCase(); - const actualStartPosition = [...keywords] - .map((k) => textLower.indexOf(k.toLowerCase())) - .filter((i) => i > -1) - .slice(-1)[0]; - const startWithContext = Math.max(actualStartPosition - 120, 0); - - const top = startWithContext === 0 ? "" : "..."; - const tail = startWithContext + 240 < text.length ? "..." : ""; - - let summary = document.createElement("p"); - summary.classList.add("context"); - summary.textContent = - top + text.substr(startWithContext, 240).trim() + tail; - - return summary; - }, -}; - -_ready(Search.init); diff --git a/docs/build/html/_static/sphinx_highlight.js b/docs/build/html/_static/sphinx_highlight.js deleted file mode 100644 index a74e103a..00000000 --- a/docs/build/html/_static/sphinx_highlight.js +++ /dev/null @@ -1,159 +0,0 @@ -/* Highlighting utilities for Sphinx HTML documentation. */ -"use strict"; - -const SPHINX_HIGHLIGHT_ENABLED = true; - -/** - * highlight a given string on a node by wrapping it in - * span elements with the given class name. - */ -const _highlight = (node, addItems, text, className) => { - if (node.nodeType === Node.TEXT_NODE) { - const val = node.nodeValue; - const parent = node.parentNode; - const pos = val.toLowerCase().indexOf(text); - if ( - pos >= 0 - && !parent.classList.contains(className) - && !parent.classList.contains("nohighlight") - ) { - let span; - - const closestNode = parent.closest("body, svg, foreignObject"); - const isInSVG = closestNode && closestNode.matches("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.classList.add(className); - } - - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore(span, parent.insertBefore(rest, node.nextSibling)); - node.nodeValue = val.substr(0, pos); - /* There may be more occurrences of search term in this node. So call this - * function recursively on the remaining fragment. - */ - _highlight(rest, addItems, text, className); - - if (isInSVG) { - const rect = document.createElementNS( - "http://www.w3.org/2000/svg", - "rect", - ); - const bbox = parent.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute("class", className); - addItems.push({ parent: parent, target: rect }); - } - } - } else if (node.matches && !node.matches("button, select, textarea")) { - node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); - } -}; -const _highlightText = (thisNode, text, className) => { - let addItems = []; - _highlight(thisNode, addItems, text, className); - addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target), - ); -}; - -/** - * Small JavaScript module for the documentation. - */ -const SphinxHighlight = { - /** - * highlight the search words provided in localstorage in the text - */ - highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight - - // get and clear terms from localstorage - const url = new URL(window.location); - const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms"); - // Update history only if '?highlight' is present; otherwise it - // clears text fragments (not set in window.location by the browser) - if (url.searchParams.has("highlight")) { - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); - } - - // get individual terms from highlight string - const terms = highlight - .toLowerCase() - .split(/\s+/) - .filter((x) => x); - if (terms.length === 0) return; // nothing to do - - // There should never be more than one element matching "div.body" - const divBody = document.querySelectorAll("div.body"); - const body = divBody.length ? divBody[0] : document.querySelector("body"); - window.setTimeout(() => { - terms.forEach((term) => _highlightText(body, term, "highlighted")); - }, 10); - - const searchBox = document.getElementById("searchbox"); - if (searchBox === null) return; - searchBox.appendChild( - document - .createRange() - .createContextualFragment( - '", - ), - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms"); - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) - return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) - return; - if ( - DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - && event.key === "Escape" - ) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/docs/build/html/api/datefixer.html b/docs/build/html/api/datefixer.html deleted file mode 100644 index 93262dd8..00000000 --- a/docs/build/html/api/datefixer.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - - - Date Utilities — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Date Utilities

-
-

Date Handling Functions

-
-
-openseries.datefixer.date_fix(fixerdate)[source]
-

Parse different date formats into datetime.date.

-
-
Parameters:
-

fixerdate (DateType) – The data item to parse.

-
-
Returns:
-

Parsed date.

-
-
Raises:
-

TypeError – If the provided fixerdate type is not supported.

-
-
Return type:
-

dt.date

-
-
-
- -
-
-openseries.datefixer.date_offset_foll(raw_date, months_offset=12, countries='SE', markets=None, custom_holidays=None, *, adjust=False, following=True)[source]
-

Offset dates according to a given calendar.

-
-
Parameters:
-
    -
  • raw_date (DateType) – The date to offset from.

  • -
  • months_offset (int) – Number of months as integer. Defaults to 12.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
  • adjust (bool) – Determines if offset should adjust for business days. -Defaults to False.

  • -
  • following (bool) – Determines if days should be offset forward (following) or backward. -Defaults to True.

  • -
-
-
Returns:
-

Offset date.

-
-
Return type:
-

dt.date

-
-
-
- -
-
-openseries.datefixer.generate_calendar_date_range(trading_days, start=None, end=None, countries='SE', markets=None, custom_holidays=None)[source]
-

Generate a list of business day calendar dates.

-
-
Parameters:
-
    -
  • trading_days (int) – Number of days to generate. Must be greater than zero.

  • -
  • start (dt.date | None) – Date when the range starts.

  • -
  • end (dt.date | None) – Date when the range ends.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

List of business day calendar dates.

-
-
Return type:
-

list[dt.date]

-
-
-
- -
-
-openseries.datefixer.get_previous_business_day_before_today(today=None, countries='SE', markets=None, custom_holidays=None)[source]
-

Bump date backwards to find the previous business day.

-
-
Parameters:
-
    -
  • today (dt.date | None) – Manual input of the day from where the previous business day is found.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

The previous business day.

-
-
Return type:
-

dt.date

-
-
-
- -
-
-openseries.datefixer.holiday_calendar(startyear, endyear, countries='SE', markets=None, custom_holidays=None)[source]
-

Generate a business calendar.

-
-
Parameters:
-
    -
  • startyear (int) – First year in date range generated.

  • -
  • endyear (int) – Last year in date range generated.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

Generate a business calendar.

-
-
Raises:
-

CountriesNotStringNorListStrError – If countries is not a supported - ISO 3166-1 alpha-2 string or a list of such strings.

-
-
Return type:
-

busdaycalendar

-
-
-
- -
-
-openseries.datefixer.offset_business_days(ddate, days, countries='SE', markets=None, custom_holidays=None)[source]
-

Bump date by business days.

-

It first adjusts to a valid business day and then bumps with given -number of business days from there.

-
-
Parameters:
-
    -
  • ddate (dt.date) – A starting date that does not have to be a business day.

  • -
  • days (int) – The number of business days to offset from the business day -that is given. -If days is set as anything other than an integer its value is set to zero.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

The new offset business day.

-
-
Return type:
-

dt.date

-
-
-
- -

The datefixer module provides utilities for handling business days, holidays, and date calculations commonly needed in financial analysis.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/frame.html b/docs/build/html/api/frame.html deleted file mode 100644 index eadfdeb8..00000000 --- a/docs/build/html/api/frame.html +++ /dev/null @@ -1,3070 +0,0 @@ - - - - - - - - - OpenFrame — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

OpenFrame

-
-
-class openseries.OpenFrame(constituents, weights=None)[source]
-

Bases: _CommonModel[Series]

-

OpenFrame objects hold OpenTimeSeries in the list constituents.

-

The intended use is to allow comparisons across these timeseries.

-
-
Parameters:
-
    -
  • constituents (list[Any]) – List of objects of Class OpenTimeSeries.

  • -
  • weights (list[float] | None) – List of weights in float format. Optional.

  • -
-
-
-
-
-__init__(constituents, weights=None)[source]
-

OpenFrame objects hold OpenTimeSeries in the list constituents.

-

The intended use is to allow comparisons across these timeseries.

-
-
Parameters:
-
    -
  • constituents (list[OpenTimeSeries]) – List of objects of Class OpenTimeSeries.

  • -
  • weights (list[float] | None) – List of weights in float format. Optional.

  • -
  • self (Self)

  • -
-
-
Return type:
-

None

-
-
-
- -
-
-from_deepcopy()[source]
-

Create copy of the OpenFrame object.

-
-
Returns:
-

An OpenFrame object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-merge_series(how='outer')[source]
-

Merge index of Pandas Dataframes of the constituent OpenTimeSeries.

-
-
Parameters:
-
    -
  • how (Literal['outer', 'inner']) – The Pandas merge method. Defaults to “outer”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-all_properties(properties=None)[source]
-

Calculate chosen timeseries properties.

-
-
Parameters:
-
    -
  • properties (list[Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'autocorr', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown', 'max_drawdown_date', 'max_drawdown_cal_year', 'first_indices', 'last_indices', 'lengths_of_items', 'span_of_days_all']] | None) – The properties to calculate. Defaults to calculating all -available. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Properties of the constituent OpenTimeSeries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-property lengths_of_items: Series[int]
-

Number of observations of all constituents.

-
-
Returns:
-

Number of observations of all constituents.

-
-
-
- -
-
-property item_count: int
-

Number of constituents.

-
-
Returns:
-

Number of constituents.

-
-
-
- -
-
-property columns_lvl_zero: list[str]
-

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
Returns:
-

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
-
- -
-
-property columns_lvl_one: list[ValueType]
-

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
Returns:
-

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
-
- -
-
-property first_indices: Series[dt.date]
-

The first dates in the timeseries of all constituents.

-
-
Returns:
-

The first dates in the timeseries of all constituents.

-
-
-
- -
-
-property last_indices: Series[dt.date]
-

The last dates in the timeseries of all constituents.

-
-
Returns:
-

The last dates in the timeseries of all constituents.

-
-
-
- -
-
-property span_of_days_all: Series[int]
-

Number of days from the first date to the last for all items in the frame.

-
-
Returns:
-

Number of days from the first date to the last for all -items in the frame.

-
-
-
- -
-
-value_to_ret()[source]
-

Convert series of values into series of returns.

-
-
Returns:
-

The returns of the values in the series.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-value_to_diff(periods=1)[source]
-

Convert series of values to series of their period differences.

-
-
Parameters:
-
    -
  • periods (int) – The number of periods between observations over which -difference is calculated. Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-to_cumret()[source]
-

Convert series of returns into cumulative series of values.

-
-
Returns:
-

An OpenFrame object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-resample(freq='BME')[source]
-

Resample the timeseries frequency.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE'] | str) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-resample_to_business_period_ends(freq='BME', method='nearest')[source]
-

Resamples timeseries frequency to the business calendar month end dates.

-

Stubs left in place. Stubs will be aligned to the shortest stub.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE']) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • method (Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None) – Controls the method used to align values across columns. -Defaults to nearest.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-ewma_risk(lmbda=0.94, day_chunk=11, dlta_degr_freedms=0, first_column=0, second_column=1, corr_scale=2.0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Volatilities and Correlation.

-

Exponentially Weighted Moving Average (EWMA) for Volatilities and -Correlation.

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. Defaults to 11.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • first_column (int) – Column of first timeseries. Defaults to 0.

  • -
  • second_column (int) – Column of second timeseries. Defaults to 1.

  • -
  • corr_scale (float) – Correlation scale factor. Defaults to 2.0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series volatilities and correlation.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-property correl_matrix: DataFrame
-

Correlation matrix.

-

This property returns the correlation matrix of the time series -in the frame.

-
-
Returns:
-

Correlation matrix of the time series in the frame.

-
-
-
- -
-
-add_timeseries(new_series)[source]
-

To add an OpenTimeSeries object.

-
-
Parameters:
-
    -
  • new_series (OpenTimeSeries) – The timeseries to add.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-delete_timeseries(lvl_zero_item)[source]
-

To delete an OpenTimeSeries object.

-
-
Parameters:
-
    -
  • lvl_zero_item (str) – The .tsdf column level 0 value of the timeseries to delete.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-trunc_frame(start_cut=None, end_cut=None, where='both')[source]
-

Truncate DataFrame such that all timeseries have the same time span.

-
-
Parameters:
-
    -
  • start_cut (dt.date | None) – New first date. Optional.

  • -
  • end_cut (dt.date | None) – New last date. Optional.

  • -
  • where (LiteralTrunc) – Determines where dataframe is truncated also when start_cut -or end_cut is None. Defaults to both.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-relative(long_column=0, short_column=1, *, base_zero=True)[source]
-

Calculate cumulative relative return between two series.

-
-
Parameters:
-
    -
  • long_column (int) – Column number of timeseries bought. Defaults to 0.

  • -
  • short_column (int) – Column number of timeseries sold. Defaults to 1.

  • -
  • base_zero (bool) – If set to False 1.0 is added to allow for a capital base and -to allow a volatility calculation. Defaults to True.

  • -
  • self (Self)

  • -
-
-
Return type:
-

None

-
-
-
- -
-
-tracking_error_func(base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Tracking Error.

-

Calculates Tracking Error which is the standard deviation of the -difference between the fund and its index returns.

-

Reference: https://www.investopedia.com/terms/t/trackingerror.asp.

-
-
Parameters:
-
    -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Tracking Errors.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-info_ratio_func(base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Information Ratio.

-

The Information Ratio equals ( fund return less index return ) divided -by the Tracking Error. And the Tracking Error is the standard deviation of -the difference between the fund and its index returns. -The ratio is calculated using the annualized arithmetic mean of returns.

-
-
Parameters:
-
    -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Information Ratios.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-capture_ratio_func(ratio, base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Capture Ratio.

-

The Up (Down) Capture Ratio is calculated by dividing the CAGR -of the asset during periods that the benchmark returns are positive (negative) -by the CAGR of the benchmark during the same periods. -CaptureRatio.BOTH is the Up ratio divided by the Down ratio. -Source: ‘Capture Ratios: A Popular Method of Measuring Portfolio Performance -in Practice’, Don R. Cox and Delbert C. Goff, Journal of Economics and -Finance Education (Vol 2 Winter 2013).

-

Reference: https://www.economics-finance.org/jefe/volume12-2/11ArticleCox.pdf.

-
-
Parameters:
-
    -
  • ratio (LiteralCaptureRatio) – The ratio to calculate.

  • -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Capture Ratios.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-beta(asset, market, dlta_degr_freedms=1)[source]
-

Market Beta.

-

Calculates Beta as Co-variance of asset & market divided by Variance -of the market.

-

Reference: https://www.investopedia.com/terms/b/beta.asp.

-
-
Parameters:
-
    -
  • asset (tuple[str, ValueType] | int) – The column of the asset.

  • -
  • market (tuple[str, ValueType] | int) – The column of the market against which Beta is measured.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Beta as Co-variance of x & y divided by Variance of x.

-
-
Return type:
-

float

-
-
-
- -
-
-ord_least_squares_fit(y_column, x_column, *, fitted_series=True)[source]
-

Ordinary Least Squares fit.

-

Performs a linear regression and adds a new column with a fitted line -using Ordinary Least Squares fit.

-
-
Parameters:
-
    -
  • y_column (tuple[str, ValueType] | int) – The column level values of the dependent variable y.

  • -
  • x_column (tuple[str, ValueType] | int) – The column level values of the exogenous variable x.

  • -
  • fitted_series (bool) – If True the fit is added as a new column in the .tsdf -Pandas.DataFrame. Defaults to True.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A dictionary with the coefficient, intercept and rsquared outputs.

-
-
Return type:
-

dict[str, float]

-
-
-
- -
-
-jensen_alpha(asset, market, riskfree_rate=0.0, dlta_degr_freedms=1)[source]
-

Jensen’s alpha.

-

The Jensen’s measure, or Jensen’s alpha, is a risk-adjusted performance -measure that represents the average return on a portfolio or investment, -above or below that predicted by the capital asset pricing model (CAPM), -given the portfolio’s or investment’s beta and the average market return. -This metric is also commonly referred to as simply alpha.

-

Reference: https://www.investopedia.com/terms/j/jensensmeasure.asp.

-
-
Parameters:
-
    -
  • asset (tuple[str, ValueType] | int) – The column of the asset.

  • -
  • market (tuple[str, ValueType] | int) – The column of the market against which Jensen’s alpha is measured.

  • -
  • riskfree_rate (float) – The return of the zero volatility riskfree asset. -Defaults to 0.0.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Jensen’s alpha.

-
-
Return type:
-

float

-
-
-
- -
-
-make_portfolio(name, weight_strat=None)[source]
-

Calculate a basket timeseries based on the supplied weights.

-
-
Parameters:
-
    -
  • name (str) – Name of the basket timeseries.

  • -
  • weight_strat (Literal['eq_weights', 'inv_vol', 'max_div', 'min_vol_overweight'] | None) – Weight calculation strategies. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A basket timeseries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-rolling_info_ratio(long_column=0, short_column=1, observations=21, periods_in_a_year_fixed=None)[source]
-

Calculate rolling Information Ratio.

-

The Information Ratio equals ( fund return less index return ) divided by -the Tracking Error. And the Tracking Error is the standard deviation of the -difference between the fund and its index returns.

-
-
Parameters:
-
    -
  • long_column (int) – Column of timeseries that is the numerator in the ratio. -Defaults to 0.

  • -
  • short_column (int) – Column of timeseries that is the denominator in the ratio. -Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • periods_in_a_year_fixed (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])] | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Information Ratios.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-rolling_beta(asset_column=0, market_column=1, observations=21, dlta_degr_freedms=1)[source]
-

Calculate rolling Market Beta.

-

Calculates Beta as Co-variance of asset & market divided by Variance -of the market.

-

Reference: https://www.investopedia.com/terms/b/beta.asp.

-
-
Parameters:
-
    -
  • asset_column (int) – Column of timeseries that is the asset. Defaults to 0.

  • -
  • market_column (int) – Column of timeseries that is the market. Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Betas.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-rolling_corr(first_column=0, second_column=1, observations=21)[source]
-

Calculate rolling Correlation.

-

Calculates correlation between two series. The period with -at least the given number of observations is the first period calculated.

-
-
Parameters:
-
    -
  • first_column (int) – The position as integer of the first timeseries to compare. -Defaults to 0.

  • -
  • second_column (int) – The position as integer of the second timeseries to compare. -Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Correlations.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-multi_factor_linear_regression(dependent_column)[source]
-

Perform a multi-factor linear regression.

-

This function treats one specified column in the DataFrame as the dependent -variable (y) and uses all remaining columns as independent variables (X). -It utilizes a scikit-learn LinearRegression model and returns a DataFrame -with summary output and an OpenTimeSeries of predicted values.

-
-
Parameters:
-
    -
  • dependent_column (tuple[str, ValueType]) – A tuple key to select the column in the -OpenFrame.tsdf.columns to use as the dependent variable.

  • -
  • self (Self)

  • -
-
-
Returns:
-

    -
  • A DataFrame with the R-squared, the intercept and the regression -coefficients

  • -
  • An OpenTimeSeries of predicted values

  • -
-

-
-
Return type:
-

A tuple containing

-
-
Raises:
-
    -
  • KeyError – If the column tuple is not found in the OpenFrame.tsdf.columns.

  • -
  • ValueError – If not all series are returnseries (ValueType.RTRN).

  • -
-
-
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-rebalanced_portfolio(name, items=None, bal_weights=None, frequency=1, cash_index=None, *, equal_weights=False, drop_extras=True)[source]
-

Create a rebalanced portfolio from the OpenFrame constituents.

-
-
Parameters:
-
    -
  • name (str) – Name of the portfolio.

  • -
  • items (list[str] | None) – List of items to include in the portfolio. If None, uses all items. -Optional.

  • -
  • bal_weights (list[float] | None) – List of weights for rebalancing. If None, uses frame weights. -Optional.

  • -
  • frequency (int) – Rebalancing frequency. Defaults to 1.

  • -
  • cash_index (OpenTimeSeries | None) – Cash index series for cash component. Optional.

  • -
  • equal_weights (bool) – If True, use equal weights for all items. Defaults to False.

  • -
  • drop_extras (bool) – If True, only return TWR series; if False, return all details. -Defaults to True.

  • -
  • self (Self)

  • -
-
-
Returns:
-

OpenFrame containing the rebalanced portfolio.

-
-
Return type:
-

OpenFrame

-
-
-
- -
- -

The OpenFrame class manages collections of OpenTimeSeries objects and provides functionality for:

-
    -
  • Multi-asset analysis and comparison

  • -
  • Portfolio construction and optimization

  • -
  • Correlation and regression analysis

  • -
  • Risk attribution and factor analysis

  • -
  • Batch processing of multiple time series

  • -
-
-

Class Methods for Construction

-
-
-OpenFrame.from_deepcopy()[source]
-

Create copy of the OpenFrame object.

-
-
Returns:
-

An OpenFrame object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-

Properties

-
-

Frame-specific Properties

-
-
-OpenFrame.constituents
-
- -
-
-OpenFrame.columns_lvl_zero
-

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
Returns:
-

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
-
- -
-
-OpenFrame.columns_lvl_one
-

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
Returns:
-

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
-
- -
-
-OpenFrame.item_count
-

Number of constituents.

-
-
Returns:
-

Number of constituents.

-
-
-
- -
-
-OpenFrame.weights
-
- -
-
-OpenFrame.first_indices
-

The first dates in the timeseries of all constituents.

-
-
Returns:
-

The first dates in the timeseries of all constituents.

-
-
-
- -
-
-OpenFrame.last_indices
-

The last dates in the timeseries of all constituents.

-
-
Returns:
-

The last dates in the timeseries of all constituents.

-
-
-
- -
-
-OpenFrame.lengths_of_items
-

Number of observations of all constituents.

-
-
Returns:
-

Number of observations of all constituents.

-
-
-
- -
-
-OpenFrame.span_of_days_all
-

Number of days from the first date to the last for all items in the frame.

-
-
Returns:
-

Number of days from the first date to the last for all -items in the frame.

-
-
-
- -
-
-

Common Properties

-
-
-OpenFrame.first_idx
-

The first date in the timeseries.

-
-
Returns:
-

The first date in the timeseries.

-
-
-
- -
-
-OpenFrame.last_idx
-

The last date in the timeseries.

-
-
Returns:
-

The last date in the timeseries.

-
-
-
- -
-
-OpenFrame.length
-

Number of observations.

-
-
Returns:
-

Number of observations.

-
-
-
- -
-
-OpenFrame.span_of_days
-

Number of days from the first date to the last.

-
-
Returns:
-

Number of days from the first date to the last.

-
-
-
- -
-
-OpenFrame.tsdf
-
- -
-
-OpenFrame.max_drawdown_date
-

Date when the maximum drawdown occurred.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-

Returns:

-
-
datetime.date | pandas.Series[dt.date]

Date when the maximum drawdown occurred

-
-
-
-
- -
-
-OpenFrame.periods_in_a_year
-

The average number of observations per year.

-
-
Returns:
-

The average number of observations per year.

-
-
-
- -
-
-OpenFrame.yearfrac
-

Length of series in years assuming 365.25 days per year.

-
-
Returns:
-

Length of the timeseries in years assuming 365.25 days per year.

-
-
-
- -
-
-

Financial Metrics

-
-
-OpenFrame.all_properties = <function OpenFrame.all_properties>[source]
-
-
Parameters:
-
    -
  • self (Self)

  • -
  • properties (list[Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'autocorr', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown', 'max_drawdown_date', 'max_drawdown_cal_year', 'first_indices', 'last_indices', 'lengths_of_items', 'span_of_days_all']] | None)

  • -
-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.arithmetic_ret
-

Annualized arithmetic mean of returns.

-

Reference: https://www.investopedia.com/terms/a/arithmeticmean.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Annualized arithmetic mean of returns. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.geo_ret
-

Compounded Annual Growth Rate (CAGR).

-

Reference: https://www.investopedia.com/terms/c/cagr.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Compounded Annual Growth Rate (CAGR). -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.value_ret
-

Simple return.

-
-

Returns:

-
-
SeriesOrFloat_co

Simple return. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.vol
-

Annualized volatility.

-

Based on Pandas .std() which is the equivalent of stdev.s([…]) in MS Excel.

-

Reference: https://www.investopedia.com/terms/v/volatility.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Annualized volatility. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.downside_deviation
-

Downside Deviation.

-

Standard deviation of returns that are below a Minimum Accepted Return -of zero. It is used to calculate the Sortino Ratio.

-

Reference: https://www.investopedia.com/terms/d/downside-deviation.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Downside deviation. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.ret_vol_ratio
-

Ratio of annualized arithmetic mean of returns and annualized volatility.

-
-

Returns:

-
-
SeriesOrFloat_co

Ratio of the annualized arithmetic mean of returns and annualized -volatility. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.sortino_ratio
-

Sortino ratio.

-

Reference: https://www.investopedia.com/terms/s/sortinoratio.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Sortino ratio calculated as the annualized arithmetic mean of returns -/ downside deviation. The ratio implies that the riskfree asset has zero -volatility, and a minimum acceptable return of zero. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.kappa3_ratio
-

Kappa-3 ratio.

-

The Kappa-3 ratio is a generalized downside-risk ratio defined as -annualized arithmetic return divided by the cubic-root of the -lower partial moment of order 3 (with respect to a minimum acceptable -return, MAR). It penalizes larger downside outcomes more heavily than -the Sortino ratio (which uses order 2).

-
-

Returns:

-
-
SeriesOrFloat_co

Kappa-3 ratio calculation with the riskfree rate and. -Minimum Acceptable Return (MAR) both set to zero. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.omega_ratio
-

Omega ratio.

-

Reference: https://en.wikipedia.org/wiki/Omega_ratio.

-
-

Returns:

-
-
SeriesOrFloat_co

Omega ratio calculation. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.var_down
-

Downside 95% Value At Risk (VaR).

-

The equivalent of percentile.inc([…], 1-level) over returns in MS Excel. -https://www.investopedia.com/terms/v/var.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Downside 95% Value At Risk (VaR). -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.cvar_down
-

Downside 95% Conditional Value At Risk “CVaR”.

-

Reference: https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Downside 95% Conditional Value At Risk “CVaR”. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.worst
-

Most negative percentage change.

-
-

Returns:

-
-
SeriesOrFloat_co

Most negative percentage change. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.worst_month
-

Most negative month.

-
-

Returns:

-
-
SeriesOrFloat_co

Most negative month. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.max_drawdown
-

Maximum drawdown without any limit on date range.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Maximum drawdown without any limit on date range. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.max_drawdown_cal_year
-

Maximum drawdown in a single calendar year.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Maximum drawdown in a single calendar year. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.positive_share
-

The share of percentage changes that are greater than zero.

-
-

Returns:

-
-
SeriesOrFloat_co

The share of percentage changes that are greater than zero. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.vol_from_var
-

Implied annualized volatility from Downside 95% Value at Risk.

-

Assumes that returns are normally distributed.

-
-

Returns:

-
-
SeriesOrFloat_co

Implied annualized volatility from the Downside 95% VaR using the -assumption that returns are normally distributed. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.autocorr
-

Autocorrelation at lag 1.

-

Shorthand for autocorr_func(lag=1). Returns the lag-1 autocorrelation -of demeaned returns. For price series, returns are computed via -pct_change; for return series, raw values are used after demeaning.

-
-

Returns:

-
-
SeriesOrFloat_co

Autocorrelation at lag 1. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.skew
-

Skew of the return distribution.

-

Reference: https://www.investopedia.com/terms/s/skewness.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Skew of the return distribution. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.kurtosis
-

Kurtosis of the return distribution.

-

Reference: https://www.investopedia.com/terms/k/kurtosis.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Kurtosis of the return distribution. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenFrame.z_score
-

Z-score.

-

Reference: https://www.investopedia.com/terms/z/zscore.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Z-score as (last return - mean return) / standard deviation of returns. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-
-

Methods

-
-

Frame Management

-
-
-OpenFrame.merge_series(how='outer')[source]
-

Merge index of Pandas Dataframes of the constituent OpenTimeSeries.

-
-
Parameters:
-
    -
  • how (Literal['outer', 'inner']) – The Pandas merge method. Defaults to “outer”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.trunc_frame(start_cut=None, end_cut=None, where='both')[source]
-

Truncate DataFrame such that all timeseries have the same time span.

-
-
Parameters:
-
    -
  • start_cut (dt.date | None) – New first date. Optional.

  • -
  • end_cut (dt.date | None) – New last date. Optional.

  • -
  • where (LiteralTrunc) – Determines where dataframe is truncated also when start_cut -or end_cut is None. Defaults to both.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.add_timeseries(new_series)[source]
-

To add an OpenTimeSeries object.

-
-
Parameters:
-
    -
  • new_series (OpenTimeSeries) – The timeseries to add.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.delete_timeseries(lvl_zero_item)[source]
-

To delete an OpenTimeSeries object.

-
-
Parameters:
-
    -
  • lvl_zero_item (str) – The .tsdf column level 0 value of the timeseries to delete.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-

Portfolio Analysis

-
-
-OpenFrame.relative(long_column=0, short_column=1, *, base_zero=True)[source]
-

Calculate cumulative relative return between two series.

-
-
Parameters:
-
    -
  • long_column (int) – Column number of timeseries bought. Defaults to 0.

  • -
  • short_column (int) – Column number of timeseries sold. Defaults to 1.

  • -
  • base_zero (bool) – If set to False 1.0 is added to allow for a capital base and -to allow a volatility calculation. Defaults to True.

  • -
  • self (Self)

  • -
-
-
Return type:
-

None

-
-
-
- -
-
-OpenFrame.make_portfolio(name, weight_strat=None)[source]
-

Calculate a basket timeseries based on the supplied weights.

-
-
Parameters:
-
    -
  • name (str) – Name of the basket timeseries.

  • -
  • weight_strat (Literal['eq_weights', 'inv_vol', 'max_div', 'min_vol_overweight'] | None) – Weight calculation strategies. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A basket timeseries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rebalanced_portfolio(name, items=None, bal_weights=None, frequency=1, cash_index=None, *, equal_weights=False, drop_extras=True)[source]
-

Create a rebalanced portfolio from the OpenFrame constituents.

-
-
Parameters:
-
    -
  • name (str) – Name of the portfolio.

  • -
  • items (list[str] | None) – List of items to include in the portfolio. If None, uses all items. -Optional.

  • -
  • bal_weights (list[float] | None) – List of weights for rebalancing. If None, uses frame weights. -Optional.

  • -
  • frequency (int) – Rebalancing frequency. Defaults to 1.

  • -
  • cash_index (OpenTimeSeries | None) – Cash index series for cash component. Optional.

  • -
  • equal_weights (bool) – If True, use equal weights for all items. Defaults to False.

  • -
  • drop_extras (bool) – If True, only return TWR series; if False, return all details. -Defaults to True.

  • -
  • self (Self)

  • -
-
-
Returns:
-

OpenFrame containing the rebalanced portfolio.

-
-
Return type:
-

OpenFrame

-
-
-
- -
-
-

Statistical Analysis

-
-
-OpenFrame.ord_least_squares_fit(y_column, x_column, *, fitted_series=True)[source]
-

Ordinary Least Squares fit.

-

Performs a linear regression and adds a new column with a fitted line -using Ordinary Least Squares fit.

-
-
Parameters:
-
    -
  • y_column (tuple[str, ValueType] | int) – The column level values of the dependent variable y.

  • -
  • x_column (tuple[str, ValueType] | int) – The column level values of the exogenous variable x.

  • -
  • fitted_series (bool) – If True the fit is added as a new column in the .tsdf -Pandas.DataFrame. Defaults to True.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A dictionary with the coefficient, intercept and rsquared outputs.

-
-
Return type:
-

dict[str, float]

-
-
-
- -
-
-OpenFrame.beta(asset, market, dlta_degr_freedms=1)[source]
-

Market Beta.

-

Calculates Beta as Co-variance of asset & market divided by Variance -of the market.

-

Reference: https://www.investopedia.com/terms/b/beta.asp.

-
-
Parameters:
-
    -
  • asset (tuple[str, ValueType] | int) – The column of the asset.

  • -
  • market (tuple[str, ValueType] | int) – The column of the market against which Beta is measured.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Beta as Co-variance of x & y divided by Variance of x.

-
-
Return type:
-

float

-
-
-
- -
-
-OpenFrame.jensen_alpha(asset, market, riskfree_rate=0.0, dlta_degr_freedms=1)[source]
-

Jensen’s alpha.

-

The Jensen’s measure, or Jensen’s alpha, is a risk-adjusted performance -measure that represents the average return on a portfolio or investment, -above or below that predicted by the capital asset pricing model (CAPM), -given the portfolio’s or investment’s beta and the average market return. -This metric is also commonly referred to as simply alpha.

-

Reference: https://www.investopedia.com/terms/j/jensensmeasure.asp.

-
-
Parameters:
-
    -
  • asset (tuple[str, ValueType] | int) – The column of the asset.

  • -
  • market (tuple[str, ValueType] | int) – The column of the market against which Jensen’s alpha is measured.

  • -
  • riskfree_rate (float) – The return of the zero volatility riskfree asset. -Defaults to 0.0.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Jensen’s alpha.

-
-
Return type:
-

float

-
-
-
- -
-
-OpenFrame.tracking_error_func(base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Tracking Error.

-

Calculates Tracking Error which is the standard deviation of the -difference between the fund and its index returns.

-

Reference: https://www.investopedia.com/terms/t/trackingerror.asp.

-
-
Parameters:
-
    -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Tracking Errors.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-OpenFrame.info_ratio_func(base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Information Ratio.

-

The Information Ratio equals ( fund return less index return ) divided -by the Tracking Error. And the Tracking Error is the standard deviation of -the difference between the fund and its index returns. -The ratio is calculated using the annualized arithmetic mean of returns.

-
-
Parameters:
-
    -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Information Ratios.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-OpenFrame.capture_ratio_func(ratio, base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Capture Ratio.

-

The Up (Down) Capture Ratio is calculated by dividing the CAGR -of the asset during periods that the benchmark returns are positive (negative) -by the CAGR of the benchmark during the same periods. -CaptureRatio.BOTH is the Up ratio divided by the Down ratio. -Source: ‘Capture Ratios: A Popular Method of Measuring Portfolio Performance -in Practice’, Don R. Cox and Delbert C. Goff, Journal of Economics and -Finance Education (Vol 2 Winter 2013).

-

Reference: https://www.economics-finance.org/jefe/volume12-2/11ArticleCox.pdf.

-
-
Parameters:
-
    -
  • ratio (LiteralCaptureRatio) – The ratio to calculate.

  • -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Capture Ratios.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-OpenFrame.multi_factor_linear_regression(dependent_column)[source]
-

Perform a multi-factor linear regression.

-

This function treats one specified column in the DataFrame as the dependent -variable (y) and uses all remaining columns as independent variables (X). -It utilizes a scikit-learn LinearRegression model and returns a DataFrame -with summary output and an OpenTimeSeries of predicted values.

-
-
Parameters:
-
    -
  • dependent_column (tuple[str, ValueType]) – A tuple key to select the column in the -OpenFrame.tsdf.columns to use as the dependent variable.

  • -
  • self (Self)

  • -
-
-
Returns:
-

    -
  • A DataFrame with the R-squared, the intercept and the regression -coefficients

  • -
  • An OpenTimeSeries of predicted values

  • -
-

-
-
Return type:
-

A tuple containing

-
-
Raises:
-
    -
  • KeyError – If the column tuple is not found in the OpenFrame.tsdf.columns.

  • -
  • ValueError – If not all series are returnseries (ValueType.RTRN).

  • -
-
-
-
- -
-
-

Rolling Analysis

-
-
-OpenFrame.rolling_info_ratio(long_column=0, short_column=1, observations=21, periods_in_a_year_fixed=None)[source]
-

Calculate rolling Information Ratio.

-

The Information Ratio equals ( fund return less index return ) divided by -the Tracking Error. And the Tracking Error is the standard deviation of the -difference between the fund and its index returns.

-
-
Parameters:
-
    -
  • long_column (int) – Column of timeseries that is the numerator in the ratio. -Defaults to 0.

  • -
  • short_column (int) – Column of timeseries that is the denominator in the ratio. -Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • periods_in_a_year_fixed (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])] | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Information Ratios.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rolling_beta(asset_column=0, market_column=1, observations=21, dlta_degr_freedms=1)[source]
-

Calculate rolling Market Beta.

-

Calculates Beta as Co-variance of asset & market divided by Variance -of the market.

-

Reference: https://www.investopedia.com/terms/b/beta.asp.

-
-
Parameters:
-
    -
  • asset_column (int) – Column of timeseries that is the asset. Defaults to 0.

  • -
  • market_column (int) – Column of timeseries that is the market. Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Betas.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rolling_corr(first_column=0, second_column=1, observations=21)[source]
-

Calculate rolling Correlation.

-

Calculates correlation between two series. The period with -at least the given number of observations is the first period calculated.

-
-
Parameters:
-
    -
  • first_column (int) – The position as integer of the first timeseries to compare. -Defaults to 0.

  • -
  • second_column (int) – The position as integer of the second timeseries to compare. -Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Correlations.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rolling_return(column=0, observations=21)
-

Calculate rolling returns.

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling returns.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rolling_vol(column=0, observations=21, periods_in_a_year_fixed=None, dlta_degr_freedms=1)
-

Calculate rolling annualized volatilities.

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • dlta_degr_freedms (int) – Variance bias factor (0 or 1).

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling annualized volatilities.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rolling_var_down(column=0, level=0.95, observations=252, interpolation='lower')
-

Calculate rolling annualized downside Value At Risk (VaR).

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • level (float) – Value At Risk level.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation used by DataFrame.quantile.

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling annualized downside VaR.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenFrame.rolling_cvar_down(column=0, level=0.95, observations=252)
-

Calculate rolling annualized downside CVaR.

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • level (float) – Conditional Value At Risk level.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling annualized downside CVaR.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-

Correlation and Risk

-
-
-OpenFrame.correl_matrix
-

Correlation matrix.

-

This property returns the correlation matrix of the time series -in the frame.

-
-
Returns:
-

Correlation matrix of the time series in the frame.

-
-
-
- -
-
-OpenFrame.ewma_risk(lmbda=0.94, day_chunk=11, dlta_degr_freedms=0, first_column=0, second_column=1, corr_scale=2.0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Volatilities and Correlation.

-

Exponentially Weighted Moving Average (EWMA) for Volatilities and -Correlation.

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. Defaults to 11.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • first_column (int) – Column of first timeseries. Defaults to 0.

  • -
  • second_column (int) – Column of second timeseries. Defaults to 1.

  • -
  • corr_scale (float) – Correlation scale factor. Defaults to 2.0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series volatilities and correlation.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-

Data Manipulation

-
-
-OpenFrame.align_index_to_local_cdays(countries=None, markets=None, custom_holidays=None, method='nearest')
-

Align the index of .tsdf with local calendar business days.

-
-
Parameters:
-
    -
  • countries (CountriesType | None) – Country code(s) (ISO 3166-1 alpha-2).

  • -
  • markets (list[str] | str | None) – Market code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Missing holidays that should be added.

  • -
  • method (LiteralPandasReindexMethod) – Method for reindexing when aligning to business days.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The modified object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.resample(freq='BME')[source]
-

Resample the timeseries frequency.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE'] | str) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.resample_to_business_period_ends(freq='BME', method='nearest')[source]
-

Resamples timeseries frequency to the business calendar month end dates.

-

Stubs left in place. Stubs will be aligned to the shortest stub.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE']) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • method (Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None) – Controls the method used to align values across columns. -Defaults to nearest.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.value_nan_handle(method='fill')
-

Handle missing values in a value series.

-
-
Parameters:
-
    -
  • method (LiteralNanMethod) – Method used to handle NaN. Either "fill" (last known) or -"drop".

  • -
  • self (Self)

  • -
-
-
Returns:
-

The modified object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.return_nan_handle(method='fill')
-

Handle missing values in a return series.

-
-
Parameters:
-
    -
  • method (LiteralNanMethod) – Method used to handle NaN. Either "fill" (zero) or -"drop".

  • -
  • self (Self)

  • -
-
-
Returns:
-

The modified object.

-
-
Return type:
-

Self

-
-
-
- -
-
-

Transformations

-
-
-OpenFrame.to_cumret()[source]
-

Convert series of returns into cumulative series of values.

-
-
Returns:
-

An OpenFrame object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.value_to_ret()[source]
-

Convert series of values into series of returns.

-
-
Returns:
-

The returns of the values in the series.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.value_to_diff(periods=1)[source]
-

Convert series of values to series of their period differences.

-
-
Parameters:
-
    -
  • periods (int) – The number of periods between observations over which -difference is calculated. Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.value_to_log()
-

Convert value series to log-weighted series.

-

Equivalent to LN(value[t] / value[t=0]) in Excel.

-
-
Returns:
-

The modified object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.to_drawdown_series()
-

Convert timeseries into a drawdown series.

-
-
Returns:
-

The modified object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenFrame.value_ret_calendar_period(year, month=None)
-

Calculate simple return for a specific calendar period.

-
-
Parameters:
-
    -
  • year (int) – Calendar year of the period to calculate.

  • -
  • month (int | None) – Calendar month of the period to calculate.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Simple return for the period. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-

Analysis Methods

-

Autocorrelation analysis: autocorr (property) and autocorr_func return -lag-N autocorrelation per column. For ACF, PACF, and Ljung-Box tests, use the -constituent OpenTimeSeries objects.

-
-
-OpenFrame.autocorr_func(lag=1, *, squared=False)
-

Calculate autocorrelation at a given lag.

-

Computes the autocorrelation of demeaned returns at the specified lag. -For price series (ValueType.PRICE), returns are derived via pct_change; -for return series (ValueType.RTRN), raw values are demeaned. Use -squared=True for squared-return autocorrelation (e.g. volatility -clustering). Returns nan when the series has too few observations.

-
-
Parameters:
-
    -
  • lag (int) – The lag at which to compute autocorrelation. Defaults to 1.

  • -
  • squared (bool) – If True, compute autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Autocorrelation at the specified lag. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.calc_range(months_offset=None, from_dt=None, to_dt=None)
-

Create a user-defined date range aligned to index.

-
-
Parameters:
-
    -
  • months_offset (int | None) – Number of months offset as a positive integer. Overrides -use of from_dt and to_dt.

  • -
  • from_dt (date | None) – Specific from date.

  • -
  • to_dt (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (earlier, later) representing the start and end date of the -chosen date range aligned to existing index values.

-
-
Raises:
-

DateAlignmentError – If the implied range is outside series bounds.

-
-
Return type:
-

tuple[date, date]

-
-
-
- -
-
-OpenFrame.outliers(threshold=3.0, months_from_last=None, from_date=None, to_date=None)
-

Detect outliers using z-score analysis.

-

Identifies data points where the absolute z-score exceeds the threshold. -For OpenTimeSeries, returns a Series with dates and outlier values. For -OpenFrame, returns a DataFrame with dates and outlier values for each -column.

-
-
Parameters:
-
    -
  • threshold (float) – Z-score threshold; values with |z| > threshold are -outliers.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of outliers. For OpenFrame: DataFrame of -outliers. Empty if none found.

-
-
Return type:
-

For OpenTimeSeries

-
-
-
- -
-
-

Financial Metrics Methods

-
-
-OpenFrame.arithmetic_ret_func(months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Annualized arithmetic mean of returns.

-

Reference: https://www.investopedia.com/terms/a/arithmeticmean.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Annualized arithmetic mean of returns. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.geo_ret_func(months_from_last=None, from_date=None, to_date=None)
-

Compounded Annual Growth Rate (CAGR).

-

Reference: https://www.investopedia.com/terms/c/cagr.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

CAGR. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Raises:
-

InitialValueZeroError – If initial value is zero or there are negative - values.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.value_ret_func(months_from_last=None, from_date=None, to_date=None)
-

Calculate simple return.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Simple return. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Raises:
-

InitialValueZeroError – If initial value is zero.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.vol_func(months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Annualized volatility.

-

Based on pandas.Series.std() (Excel STDEV.S equivalent). -Reference: https://www.investopedia.com/terms/v/volatility.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Annualized volatility. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.lower_partial_moment_func(min_accepted_return=0.0, order=2, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Lower partial moment and downside deviation (order=2).

-

If order is 2 calculates standard deviation of returns below MAR=0. -For general order p, returns (LPM_p)^(1/p).

-
-
Parameters:
-
    -
  • min_accepted_return (float) – Annualized Minimum Accepted Return (MAR).

  • -
  • order (Literal[2, 3]) – Order of partial moment (2 or 3).

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Downside deviation if order is 2; otherwise rooted lower partial -moment. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Raises:
-

ValueError – If order is not 2 or 3.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.ret_vol_ratio_func(riskfree_rate=0.0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Ratio between arithmetic mean of returns and annualized volatility.

-

If riskfree_rate provided, computes the Sharpe ratio as -(arithmetic return - risk-free) / volatility. Assumes zero volatility -for the risk-free asset. Reference: -https://www.investopedia.com/terms/s/sharperatio.asp.

-
-
Parameters:
-
    -
  • riskfree_rate (float) – Return of the zero volatility asset.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Ratio value. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.sortino_ratio_func(riskfree_rate=0.0, min_accepted_return=0.0, order=2, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Sortino ratio or Kappa-3 ratio.

-

Sortino: (return - riskfree_rate) / downside deviation using arithmetic -mean of returns. Kappa-3 when order=3 penalizes larger downside more -than Sortino.

-
-
Parameters:
-
    -
  • riskfree_rate (float) – Return of the zero volatility asset.

  • -
  • min_accepted_return (float) – Annualized Minimum Accepted Return (MAR).

  • -
  • order (Literal[2, 3]) – Order of partial moment (2 for Sortino, 3 for Kappa-3).

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Ratio value. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.omega_ratio_func(min_accepted_return=0.0, months_from_last=None, from_date=None, to_date=None)
-

Omega Ratio.

-

Compares returns above MAR to the total downside risk below MAR. -Reference: https://en.wikipedia.org/wiki/Omega_ratio.

-
-
Parameters:
-
    -
  • min_accepted_return (float) – Annualized Minimum Accepted Return (MAR).

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Omega ratio. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.var_down_func(level=0.95, months_from_last=None, from_date=None, to_date=None, interpolation='lower')
-

Downside Value At Risk (VaR).

-

Equivalent to PERCENTILE.INC(returns, 1-level) in Excel. Reference: -https://www.investopedia.com/terms/v/var.asp.

-
-
Parameters:
-
    -
  • level (float) – The sought VaR level.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation used by DataFrame.quantile.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Downside VaR. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.cvar_down_func(level=0.95, months_from_last=None, from_date=None, to_date=None)
-

Downside Conditional Value At Risk (CVaR).

-

Reference: https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.

-
-
Parameters:
-
    -
  • level (float) – The sought CVaR level.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Downside CVaR. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.worst_func(observations=1, months_from_last=None, from_date=None, to_date=None)
-

Most negative percentage change over a rolling window.

-
-
Parameters:
-
    -
  • observations (int) – Number of observations for the rolling window.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Most negative percentage change. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.max_drawdown_func(months_from_last=None, from_date=None, to_date=None, min_periods=1)
-

Maximum drawdown without any limit on date range.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • min_periods (int) – Smallest number of observations for rolling max.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Maximum drawdown. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.positive_share_func(months_from_last=None, from_date=None, to_date=None)
-

Share of percentage changes greater than zero.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Share of positive returns. Float for OpenTimeSeries, Series[float] -for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.vol_from_var_func(level=0.95, months_from_last=None, from_date=None, to_date=None, interpolation='lower', periods_in_a_year_fixed=None, *, drift_adjust=False)
-

Implied annualized volatility from downside VaR.

-

Assumes normally distributed returns.

-
-
Parameters:
-
    -
  • level (float) – The sought VaR level.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation type used by DataFrame.quantile.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • drift_adjust (bool) – Adjustment to remove the bias implied by the average -return.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Implied annualized volatility. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.skew_func(months_from_last=None, from_date=None, to_date=None)
-

Skew of the return distribution.

-

Reference: https://www.investopedia.com/terms/s/skewness.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Skewness. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.kurtosis_func(months_from_last=None, from_date=None, to_date=None)
-

Kurtosis of the return distribution.

-

Reference: https://www.investopedia.com/terms/k/kurtosis.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Kurtosis. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.z_score_func(months_from_last=None, from_date=None, to_date=None)
-

Z-score of the last return.

-

Computed as (last return - mean return) / std dev of returns. -Reference: https://www.investopedia.com/terms/z/zscore.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Z-score. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenFrame.target_weight_from_var(target_vol=0.175, level=0.95, min_leverage_local=0.0, max_leverage_local=99999.0, months_from_last=None, from_date=None, to_date=None, interpolation='lower', periods_in_a_year_fixed=None, *, drift_adjust=False)
-

Target weight from VaR.

-

Computes a position weight multiplier from the ratio between a VaR implied -volatility and a given target volatility. Multiplier = 1.0 → target met.

-
-
Parameters:
-
    -
  • target_vol (float) – Target volatility.

  • -
  • level (float) – The sought VaR level.

  • -
  • min_leverage_local (float) – Minimum adjustment factor.

  • -
  • max_leverage_local (float) – Maximum adjustment factor.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation type used by DataFrame.quantile.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • drift_adjust (bool) – Adjustment to remove the bias implied by the average -return.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Weight multiplier (or implied volatility if used downstream). Float for -OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-

Visualization

-

The plotting methods generate fully responsive HTML output that automatically adapts to different screen sizes and device orientations. Plots are optimized for both desktop and mobile viewing with separate title containers and responsive CSS styling.

-
-
-OpenFrame.plot_series(mode='lines', title=None, tick_fmt=None, filename=None, directory=None, labels=None, output_type='file', include_plotlyjs='cdn', *, auto_open=True, add_logo=True, show_last=False)
-

Create a Plotly Scatter Figure.

-
-
Parameters:
-
    -
  • mode (LiteralLinePlotMode) – The type of scatter to use.

  • -
  • title (str | None) – A title above the plot.

  • -
  • tick_fmt (str | None) – Tick format for the y-axis, e.g. '%' or '.1%'.

  • -
  • filename (str | None) – Name of the Plotly HTML file.

  • -
  • directory (DirectoryPath | None) – Directory where the Plotly HTML file is saved.

  • -
  • labels (list[str] | None) – Labels to override the column names of self.tsdf.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – How the plotly.js library is included.

  • -
  • auto_open (bool) – Whether to open a browser window with the plot.

  • -
  • add_logo (bool) – If True, a Captor logo is added to the plot.

  • -
  • show_last (bool) – If True, highlight the last point in red with a label.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (figure, output) where output is either a div string or -a file path.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
-OpenFrame.plot_bars(mode='group', title=None, tick_fmt=None, filename=None, directory=None, labels=None, output_type='file', include_plotlyjs='cdn', *, auto_open=True, add_logo=True)
-

Create a Plotly Bar Figure.

-
-
Parameters:
-
    -
  • mode (LiteralBarPlotMode) – The type of bar to use.

  • -
  • title (str | None) – A title above the plot.

  • -
  • tick_fmt (str | None) – Tick format for the y-axis, e.g. '%' or '.1%'.

  • -
  • filename (str | None) – Name of the Plotly HTML file.

  • -
  • directory (DirectoryPath | None) – Directory where the Plotly HTML file is saved.

  • -
  • labels (list[str] | None) – Labels to override the column names of self.tsdf.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – How the plotly.js library is included.

  • -
  • auto_open (bool) – Whether to open a browser window with the plot.

  • -
  • add_logo (bool) – If True, a Captor logo is added to the plot.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (figure, output) where output is either a div string or -a file path.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
-OpenFrame.plot_histogram(plot_type='bars', histnorm='probability', barmode='overlay', xbins_size=None, opacity=0.75, bargap=0.0, bargroupgap=0.0, curve_type='kde', title=None, x_fmt=None, y_fmt=None, filename=None, directory=None, labels=None, output_type='file', include_plotlyjs='cdn', *, cumulative=False, show_rug=False, auto_open=True, add_logo=True)
-

Create a Plotly Histogram Figure.

-
-
Parameters:
-
    -
  • plot_type (LiteralPlotlyHistogramPlotType) – Type of plot, "bars" or "lines".

  • -
  • histnorm (LiteralPlotlyHistogramHistNorm) – Normalization mode.

  • -
  • barmode (LiteralPlotlyHistogramBarMode) – How bar traces are displayed relative to one another.

  • -
  • xbins_size (float | None) – Width of each bin along the x-axis in data units.

  • -
  • opacity (float) – Trace opacity between 0 and 1.

  • -
  • bargap (float) – Gap between bars of adjacent location coordinates.

  • -
  • bargroupgap (float) – Gap between bar groups at the same location coordinate.

  • -
  • curve_type (LiteralPlotlyHistogramCurveType) – Type of distribution curve to overlay on the histogram.

  • -
  • title (str | None) – A title above the plot.

  • -
  • x_fmt (str | None) – Tick format for the x-axis.

  • -
  • y_fmt (str | None) – Tick format for the y-axis.

  • -
  • filename (str | None) – Name of the Plotly HTML file.

  • -
  • directory (DirectoryPath | None) – Directory where the Plotly HTML file is saved.

  • -
  • labels (list[str] | None) – Labels to override the column names of self.tsdf.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – How the plotly.js library is included.

  • -
  • cumulative (bool) – Whether to compute a cumulative histogram.

  • -
  • show_rug (bool) – Whether to draw a rug plot alongside the distribution.

  • -
  • auto_open (bool) – Whether to open a browser window with the plot.

  • -
  • add_logo (bool) – If True, a Captor logo is added to the plot.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (figure, output) where output is either a div string or -a file path.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
-

Export Methods

-
-
-OpenFrame.to_json(what_output, filename, directory=None)
-

Dump timeseries data into a JSON file.

-
-
Parameters:
-
    -
  • what_output (LiteralJsonOutput) – Whether to export raw values or tsdf values.

  • -
  • filename (str | Path) – Filename including extension.

  • -
  • directory (DirectoryPath | Path | str | None) – Folder where the file will be written.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A list of dictionaries with the data of the series.

-
-
Return type:
-

list[dict[str, str | bool | ValueType | list[str] | list[float]]]

-
-
-
- -
-
-OpenFrame.to_xlsx(filename, sheet_title=None, directory=None, *, overwrite=True)
-

Save .tsdf DataFrame to an Excel spreadsheet file.

-
-
Parameters:
-
    -
  • filename (str) – Filename that should include .xlsx.

  • -
  • sheet_title (str | None) – Name of the sheet in the Excel file.

  • -
  • directory (Annotated[Path, PathType(path_type=dir)] | None) – Directory where the Excel file is saved.

  • -
  • overwrite (bool) – Whether to overwrite an existing file.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The Excel file path.

-
-
Raises:
-
-
-
Return type:
-

str

-
-
-
- -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.OpenFrame.html b/docs/build/html/api/generated/openseries.OpenFrame.html deleted file mode 100644 index ee5238d3..00000000 --- a/docs/build/html/api/generated/openseries.OpenFrame.html +++ /dev/null @@ -1,1339 +0,0 @@ - - - - - - - - - openseries.OpenFrame — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.OpenFrame

-
-
-class openseries.OpenFrame(constituents, weights=None)[source]
-

Bases: _CommonModel[Series]

-

OpenFrame objects hold OpenTimeSeries in the list constituents.

-

The intended use is to allow comparisons across these timeseries.

-
-
Parameters:
-
    -
  • constituents (list[OpenTimeSeries]) – List of objects of Class OpenTimeSeries.

  • -
  • weights (list[float] | None) – List of weights in float format. Optional.

  • -
-
-
-
-
-__init__(constituents, weights=None)[source]
-

OpenFrame objects hold OpenTimeSeries in the list constituents.

-

The intended use is to allow comparisons across these timeseries.

-
-
Parameters:
-
    -
  • constituents (list[OpenTimeSeries]) – List of objects of Class OpenTimeSeries.

  • -
  • weights (list[float] | None) – List of weights in float format. Optional.

  • -
  • self (Self)

  • -
-
-
Return type:
-

None

-
-
-
- -

Methods

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

__init__(constituents[, weights])

OpenFrame objects hold OpenTimeSeries in the list constituents.

add_timeseries(new_series)

To add an OpenTimeSeries object.

align_index_to_local_cdays([countries, ...])

Align the index of .tsdf with local calendar business days.

all_properties([properties])

Calculate chosen timeseries properties.

arithmetic_ret_func([months_from_last, ...])

Annualized arithmetic mean of returns.

autocorr_func([lag, squared])

Calculate autocorrelation at a given lag.

beta(asset, market[, dlta_degr_freedms])

Market Beta.

calc_range([months_offset, from_dt, to_dt])

Create a user-defined date range aligned to index.

capture_ratio_func(ratio[, base_column, ...])

Capture Ratio.

construct([_fields_set])

copy(*[, include, exclude, update, deep])

Returns a copy of the model.

cvar_down_func([level, months_from_last, ...])

Downside Conditional Value At Risk (CVaR).

delete_timeseries(lvl_zero_item)

To delete an OpenTimeSeries object.

dict(*[, include, exclude, by_alias, ...])

ewma_risk([lmbda, day_chunk, ...])

Exponentially Weighted Moving Average Volatilities and Correlation.

from_deepcopy()

Create copy of the OpenFrame object.

from_orm(obj)

geo_ret_func([months_from_last, from_date, ...])

Compounded Annual Growth Rate (CAGR).

info_ratio_func([base_column, ...])

Information Ratio.

jensen_alpha(asset, market[, riskfree_rate, ...])

Jensen's alpha.

json(*[, include, exclude, by_alias, ...])

kurtosis_func([months_from_last, from_date, ...])

Kurtosis of the return distribution.

lower_partial_moment_func([...])

Lower partial moment and downside deviation (order=2).

make_portfolio(name[, weight_strat])

Calculate a basket timeseries based on the supplied weights.

max_drawdown_func([months_from_last, ...])

Maximum drawdown without any limit on date range.

merge_series([how])

Merge index of Pandas Dataframes of the constituent OpenTimeSeries.

model_construct([_fields_set])

Creates a new instance of the Model class with validated data.

model_copy(*[, update, deep])

!!! abstract "Usage Documentation"

model_dump(*[, mode, include, exclude, ...])

!!! abstract "Usage Documentation"

model_dump_json(*[, indent, ensure_ascii, ...])

!!! abstract "Usage Documentation"

model_json_schema([by_alias, ref_template, ...])

Generates a JSON schema for a model class.

model_parametrized_name(params)

Compute the class name for parametrizations of generic classes.

model_post_init(context, /)

Override this method to perform additional initialization after __init__ and model_construct.

model_rebuild(*[, force, raise_errors, ...])

Try to rebuild the pydantic-core schema for the model.

model_validate(obj, *[, strict, extra, ...])

Validate a pydantic model instance.

model_validate_json(json_data, *[, strict, ...])

!!! abstract "Usage Documentation"

model_validate_strings(obj, *[, strict, ...])

Validate the given object with string data against the Pydantic model.

multi_factor_linear_regression(dependent_column)

Perform a multi-factor linear regression.

omega_ratio_func([min_accepted_return, ...])

Omega Ratio.

ord_least_squares_fit(y_column, x_column, *)

Ordinary Least Squares fit.

outliers([threshold, months_from_last, ...])

Detect outliers using z-score analysis.

parse_file(path, *[, content_type, ...])

parse_obj(obj)

parse_raw(b, *[, content_type, encoding, ...])

plot_bars([mode, title, tick_fmt, filename, ...])

Create a Plotly Bar Figure.

plot_histogram([plot_type, histnorm, ...])

Create a Plotly Histogram Figure.

plot_series([mode, title, tick_fmt, ...])

Create a Plotly Scatter Figure.

positive_share_func([months_from_last, ...])

Share of percentage changes greater than zero.

rebalanced_portfolio(name[, items, ...])

Create a rebalanced portfolio from the OpenFrame constituents.

relative([long_column, short_column, base_zero])

Calculate cumulative relative return between two series.

resample([freq])

Resample the timeseries frequency.

resample_to_business_period_ends([freq, method])

Resamples timeseries frequency to the business calendar month end dates.

ret_vol_ratio_func([riskfree_rate, ...])

Ratio between arithmetic mean of returns and annualized volatility.

return_nan_handle([method])

Handle missing values in a return series.

rolling_beta([asset_column, market_column, ...])

Calculate rolling Market Beta.

rolling_corr([first_column, second_column, ...])

Calculate rolling Correlation.

rolling_cvar_down([column, level, observations])

Calculate rolling annualized downside CVaR.

rolling_info_ratio([long_column, ...])

Calculate rolling Information Ratio.

rolling_return([column, observations])

Calculate rolling returns.

rolling_var_down([column, level, ...])

Calculate rolling annualized downside Value At Risk (VaR).

rolling_vol([column, observations, ...])

Calculate rolling annualized volatilities.

schema([by_alias, ref_template])

schema_json(*[, by_alias, ref_template])

skew_func([months_from_last, from_date, to_date])

Skew of the return distribution.

sortino_ratio_func([riskfree_rate, ...])

Sortino ratio or Kappa-3 ratio.

target_weight_from_var([target_vol, level, ...])

Target weight from VaR.

to_cumret()

Convert series of returns into cumulative series of values.

to_drawdown_series()

Convert timeseries into a drawdown series.

to_json(what_output, filename[, directory])

Dump timeseries data into a JSON file.

to_xlsx(filename[, sheet_title, directory, ...])

Save .tsdf DataFrame to an Excel spreadsheet file.

tracking_error_func([base_column, ...])

Tracking Error.

trunc_frame([start_cut, end_cut, where])

Truncate DataFrame such that all timeseries have the same time span.

update_forward_refs(**localns)

validate(value)

value_nan_handle([method])

Handle missing values in a value series.

value_ret_calendar_period(year[, month])

Calculate simple return for a specific calendar period.

value_ret_func([months_from_last, ...])

Calculate simple return.

value_to_diff([periods])

Convert series of values to series of their period differences.

value_to_log()

Convert value series to log-weighted series.

value_to_ret()

Convert series of values into series of returns.

var_down_func([level, months_from_last, ...])

Downside Value At Risk (VaR).

vol_from_var_func([level, months_from_last, ...])

Implied annualized volatility from downside VaR.

vol_func([months_from_last, from_date, ...])

Annualized volatility.

worst_func([observations, months_from_last, ...])

Most negative percentage change over a rolling window.

z_score_func([months_from_last, from_date, ...])

Z-score of the last return.

-

Attributes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

arithmetic_ret

Annualized arithmetic mean of returns.

autocorr

Autocorrelation at lag 1.

columns_lvl_one

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

columns_lvl_zero

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

correl_matrix

Correlation matrix.

cvar_down

Downside 95% Conditional Value At Risk "CVaR".

downside_deviation

Downside Deviation.

first_idx

The first date in the timeseries.

first_indices

The first dates in the timeseries of all constituents.

geo_ret

Compounded Annual Growth Rate (CAGR).

item_count

Number of constituents.

kappa3_ratio

Kappa-3 ratio.

kurtosis

Kurtosis of the return distribution.

last_idx

The last date in the timeseries.

last_indices

The last dates in the timeseries of all constituents.

length

Number of observations.

lengths_of_items

Number of observations of all constituents.

max_drawdown

Maximum drawdown without any limit on date range.

max_drawdown_cal_year

Maximum drawdown in a single calendar year.

max_drawdown_date

Date when the maximum drawdown occurred.

model_computed_fields

model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_extra

Get extra fields set during validation.

model_fields

model_fields_set

Returns the set of fields that have been explicitly set on this model instance.

omega_ratio

Omega ratio.

periods_in_a_year

The average number of observations per year.

positive_share

The share of percentage changes that are greater than zero.

ret_vol_ratio

Ratio of annualized arithmetic mean of returns and annualized volatility.

skew

Skew of the return distribution.

sortino_ratio

Sortino ratio.

span_of_days

Number of days from the first date to the last.

span_of_days_all

Number of days from the first date to the last for all items in the frame.

value_ret

Simple return.

var_down

Downside 95% Value At Risk (VaR).

vol

Annualized volatility.

vol_from_var

Implied annualized volatility from Downside 95% Value at Risk.

worst

Most negative percentage change.

worst_month

Most negative month.

yearfrac

Length of series in years assuming 365.25 days per year.

z_score

Z-score.

constituents

weights

markets

tsdf

-
-
-__init__(constituents, weights=None)[source]
-

OpenFrame objects hold OpenTimeSeries in the list constituents.

-

The intended use is to allow comparisons across these timeseries.

-
-
Parameters:
-
    -
  • constituents (list[OpenTimeSeries]) – List of objects of Class OpenTimeSeries.

  • -
  • weights (list[float] | None) – List of weights in float format. Optional.

  • -
  • self (Self)

  • -
-
-
Return type:
-

None

-
-
-
- -
-
-from_deepcopy()[source]
-

Create copy of the OpenFrame object.

-
-
Returns:
-

An OpenFrame object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-merge_series(how='outer')[source]
-

Merge index of Pandas Dataframes of the constituent OpenTimeSeries.

-
-
Parameters:
-
    -
  • how (Literal['outer', 'inner']) – The Pandas merge method. Defaults to “outer”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-all_properties(properties=None)[source]
-

Calculate chosen timeseries properties.

-
-
Parameters:
-
    -
  • properties (list[Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'autocorr', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown', 'max_drawdown_date', 'max_drawdown_cal_year', 'first_indices', 'last_indices', 'lengths_of_items', 'span_of_days_all']] | None) – The properties to calculate. Defaults to calculating all -available. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Properties of the constituent OpenTimeSeries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-property lengths_of_items: Series[int]
-

Number of observations of all constituents.

-
-
Returns:
-

Number of observations of all constituents.

-
-
-
- -
-
-property item_count: int
-

Number of constituents.

-
-
Returns:
-

Number of constituents.

-
-
-
- -
-
-property columns_lvl_zero: list[str]
-

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
Returns:
-

Level 0 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
-
- -
-
-property columns_lvl_one: list[ValueType]
-

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
Returns:
-

Level 1 values of the MultiIndex columns in the .tsdf DataFrame.

-
-
-
- -
-
-property first_indices: Series[dt.date]
-

The first dates in the timeseries of all constituents.

-
-
Returns:
-

The first dates in the timeseries of all constituents.

-
-
-
- -
-
-property last_indices: Series[dt.date]
-

The last dates in the timeseries of all constituents.

-
-
Returns:
-

The last dates in the timeseries of all constituents.

-
-
-
- -
-
-property span_of_days_all: Series[int]
-

Number of days from the first date to the last for all items in the frame.

-
-
Returns:
-

Number of days from the first date to the last for all -items in the frame.

-
-
-
- -
-
-value_to_ret()[source]
-

Convert series of values into series of returns.

-
-
Returns:
-

The returns of the values in the series.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-value_to_diff(periods=1)[source]
-

Convert series of values to series of their period differences.

-
-
Parameters:
-
    -
  • periods (int) – The number of periods between observations over which -difference is calculated. Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-to_cumret()[source]
-

Convert series of returns into cumulative series of values.

-
-
Returns:
-

An OpenFrame object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-resample(freq='BME')[source]
-

Resample the timeseries frequency.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE'] | str) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-resample_to_business_period_ends(freq='BME', method='nearest')[source]
-

Resamples timeseries frequency to the business calendar month end dates.

-

Stubs left in place. Stubs will be aligned to the shortest stub.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE']) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • method (Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None) – Controls the method used to align values across columns. -Defaults to nearest.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-ewma_risk(lmbda=0.94, day_chunk=11, dlta_degr_freedms=0, first_column=0, second_column=1, corr_scale=2.0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Volatilities and Correlation.

-

Exponentially Weighted Moving Average (EWMA) for Volatilities and -Correlation.

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. Defaults to 11.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • first_column (int) – Column of first timeseries. Defaults to 0.

  • -
  • second_column (int) – Column of second timeseries. Defaults to 1.

  • -
  • corr_scale (float) – Correlation scale factor. Defaults to 2.0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series volatilities and correlation.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-property correl_matrix: DataFrame
-

Correlation matrix.

-

This property returns the correlation matrix of the time series -in the frame.

-
-
Returns:
-

Correlation matrix of the time series in the frame.

-
-
-
- -
-
-add_timeseries(new_series)[source]
-

To add an OpenTimeSeries object.

-
-
Parameters:
-
    -
  • new_series (OpenTimeSeries) – The timeseries to add.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-delete_timeseries(lvl_zero_item)[source]
-

To delete an OpenTimeSeries object.

-
-
Parameters:
-
    -
  • lvl_zero_item (str) – The .tsdf column level 0 value of the timeseries to delete.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-trunc_frame(start_cut=None, end_cut=None, where='both')[source]
-

Truncate DataFrame such that all timeseries have the same time span.

-
-
Parameters:
-
    -
  • start_cut (dt.date | None) – New first date. Optional.

  • -
  • end_cut (dt.date | None) – New last date. Optional.

  • -
  • where (LiteralTrunc) – Determines where dataframe is truncated also when start_cut -or end_cut is None. Defaults to both.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenFrame object.

-
-
Return type:
-

Self

-
-
-
- -
-
-relative(long_column=0, short_column=1, *, base_zero=True)[source]
-

Calculate cumulative relative return between two series.

-
-
Parameters:
-
    -
  • long_column (int) – Column number of timeseries bought. Defaults to 0.

  • -
  • short_column (int) – Column number of timeseries sold. Defaults to 1.

  • -
  • base_zero (bool) – If set to False 1.0 is added to allow for a capital base and -to allow a volatility calculation. Defaults to True.

  • -
  • self (Self)

  • -
-
-
Return type:
-

None

-
-
-
- -
-
-tracking_error_func(base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Tracking Error.

-

Calculates Tracking Error which is the standard deviation of the -difference between the fund and its index returns.

-

Reference: https://www.investopedia.com/terms/t/trackingerror.asp.

-
-
Parameters:
-
    -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Tracking Errors.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-info_ratio_func(base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Information Ratio.

-

The Information Ratio equals ( fund return less index return ) divided -by the Tracking Error. And the Tracking Error is the standard deviation of -the difference between the fund and its index returns. -The ratio is calculated using the annualized arithmetic mean of returns.

-
-
Parameters:
-
    -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Information Ratios.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-capture_ratio_func(ratio, base_column=-1, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Capture Ratio.

-

The Up (Down) Capture Ratio is calculated by dividing the CAGR -of the asset during periods that the benchmark returns are positive (negative) -by the CAGR of the benchmark during the same periods. -CaptureRatio.BOTH is the Up ratio divided by the Down ratio. -Source: ‘Capture Ratios: A Popular Method of Measuring Portfolio Performance -in Practice’, Don R. Cox and Delbert C. Goff, Journal of Economics and -Finance Education (Vol 2 Winter 2013).

-

Reference: https://www.economics-finance.org/jefe/volume12-2/11ArticleCox.pdf.

-
-
Parameters:
-
    -
  • ratio (LiteralCaptureRatio) – The ratio to calculate.

  • -
  • base_column (tuple[str, ValueType] | int) – Column of timeseries that is the denominator in the ratio. -Defaults to -1.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Capture Ratios.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-beta(asset, market, dlta_degr_freedms=1)[source]
-

Market Beta.

-

Calculates Beta as Co-variance of asset & market divided by Variance -of the market.

-

Reference: https://www.investopedia.com/terms/b/beta.asp.

-
-
Parameters:
-
    -
  • asset (tuple[str, ValueType] | int) – The column of the asset.

  • -
  • market (tuple[str, ValueType] | int) – The column of the market against which Beta is measured.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Beta as Co-variance of x & y divided by Variance of x.

-
-
Return type:
-

float

-
-
-
- -
-
-ord_least_squares_fit(y_column, x_column, *, fitted_series=True)[source]
-

Ordinary Least Squares fit.

-

Performs a linear regression and adds a new column with a fitted line -using Ordinary Least Squares fit.

-
-
Parameters:
-
    -
  • y_column (tuple[str, ValueType] | int) – The column level values of the dependent variable y.

  • -
  • x_column (tuple[str, ValueType] | int) – The column level values of the exogenous variable x.

  • -
  • fitted_series (bool) – If True the fit is added as a new column in the .tsdf -Pandas.DataFrame. Defaults to True.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A dictionary with the coefficient, intercept and rsquared outputs.

-
-
Return type:
-

dict[str, float]

-
-
-
- -
-
-jensen_alpha(asset, market, riskfree_rate=0.0, dlta_degr_freedms=1)[source]
-

Jensen’s alpha.

-

The Jensen’s measure, or Jensen’s alpha, is a risk-adjusted performance -measure that represents the average return on a portfolio or investment, -above or below that predicted by the capital asset pricing model (CAPM), -given the portfolio’s or investment’s beta and the average market return. -This metric is also commonly referred to as simply alpha.

-

Reference: https://www.investopedia.com/terms/j/jensensmeasure.asp.

-
-
Parameters:
-
    -
  • asset (tuple[str, ValueType] | int) – The column of the asset.

  • -
  • market (tuple[str, ValueType] | int) – The column of the market against which Jensen’s alpha is measured.

  • -
  • riskfree_rate (float) – The return of the zero volatility riskfree asset. -Defaults to 0.0.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Jensen’s alpha.

-
-
Return type:
-

float

-
-
-
- -
-
-make_portfolio(name, weight_strat=None)[source]
-

Calculate a basket timeseries based on the supplied weights.

-
-
Parameters:
-
    -
  • name (str) – Name of the basket timeseries.

  • -
  • weight_strat (Literal['eq_weights', 'inv_vol', 'max_div', 'min_vol_overweight'] | None) – Weight calculation strategies. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A basket timeseries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-rolling_info_ratio(long_column=0, short_column=1, observations=21, periods_in_a_year_fixed=None)[source]
-

Calculate rolling Information Ratio.

-

The Information Ratio equals ( fund return less index return ) divided by -the Tracking Error. And the Tracking Error is the standard deviation of the -difference between the fund and its index returns.

-
-
Parameters:
-
    -
  • long_column (int) – Column of timeseries that is the numerator in the ratio. -Defaults to 0.

  • -
  • short_column (int) – Column of timeseries that is the denominator in the ratio. -Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • periods_in_a_year_fixed (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])] | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Information Ratios.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-rolling_beta(asset_column=0, market_column=1, observations=21, dlta_degr_freedms=1)[source]
-

Calculate rolling Market Beta.

-

Calculates Beta as Co-variance of asset & market divided by Variance -of the market.

-

Reference: https://www.investopedia.com/terms/b/beta.asp.

-
-
Parameters:
-
    -
  • asset_column (int) – Column of timeseries that is the asset. Defaults to 0.

  • -
  • market_column (int) – Column of timeseries that is the market. Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Betas.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-rolling_corr(first_column=0, second_column=1, observations=21)[source]
-

Calculate rolling Correlation.

-

Calculates correlation between two series. The period with -at least the given number of observations is the first period calculated.

-
-
Parameters:
-
    -
  • first_column (int) – The position as integer of the first timeseries to compare. -Defaults to 0.

  • -
  • second_column (int) – The position as integer of the second timeseries to compare. -Defaults to 1.

  • -
  • observations (int) – The length of the rolling window to use is set as number of -observations. Defaults to 21.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Rolling Correlations.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-multi_factor_linear_regression(dependent_column)[source]
-

Perform a multi-factor linear regression.

-

This function treats one specified column in the DataFrame as the dependent -variable (y) and uses all remaining columns as independent variables (X). -It utilizes a scikit-learn LinearRegression model and returns a DataFrame -with summary output and an OpenTimeSeries of predicted values.

-
-
Parameters:
-
    -
  • dependent_column (tuple[str, ValueType]) – A tuple key to select the column in the -OpenFrame.tsdf.columns to use as the dependent variable.

  • -
  • self (Self)

  • -
-
-
Returns:
-

    -
  • A DataFrame with the R-squared, the intercept and the regression -coefficients

  • -
  • An OpenTimeSeries of predicted values

  • -
-

-
-
Return type:
-

A tuple containing

-
-
Raises:
-
    -
  • KeyError – If the column tuple is not found in the OpenFrame.tsdf.columns.

  • -
  • ValueError – If not all series are returnseries (ValueType.RTRN).

  • -
-
-
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-rebalanced_portfolio(name, items=None, bal_weights=None, frequency=1, cash_index=None, *, equal_weights=False, drop_extras=True)[source]
-

Create a rebalanced portfolio from the OpenFrame constituents.

-
-
Parameters:
-
    -
  • name (str) – Name of the portfolio.

  • -
  • items (list[str] | None) – List of items to include in the portfolio. If None, uses all items. -Optional.

  • -
  • bal_weights (list[float] | None) – List of weights for rebalancing. If None, uses frame weights. -Optional.

  • -
  • frequency (int) – Rebalancing frequency. Defaults to 1.

  • -
  • cash_index (OpenTimeSeries | None) – Cash index series for cash component. Optional.

  • -
  • equal_weights (bool) – If True, use equal weights for all items. Defaults to False.

  • -
  • drop_extras (bool) – If True, only return TWR series; if False, return all details. -Defaults to True.

  • -
  • self (Self)

  • -
-
-
Returns:
-

OpenFrame containing the rebalanced portfolio.

-
-
Return type:
-

OpenFrame

-
-
-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.OpenTimeSeries.html b/docs/build/html/api/generated/openseries.OpenTimeSeries.html deleted file mode 100644 index d1d1f25f..00000000 --- a/docs/build/html/api/generated/openseries.OpenTimeSeries.html +++ /dev/null @@ -1,1160 +0,0 @@ - - - - - - - - - openseries.OpenTimeSeries — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.OpenTimeSeries

-
-
-class openseries.OpenTimeSeries(*, constituents=<factory>, weights=None, markets=None, tsdf, timeseries_id, instrument_id, name, valuetype, dates, values, local_ccy, currency, domestic='SEK', countries='SE', isin=None, label=None)[source]
-

Bases: _CommonModel[float]

-

OpenTimeSeries objects are at the core of the openseries package.

-

The intended use is to allow analyses of financial timeseries. -It is only intended for daily or less frequent data samples.

-
-
Parameters:
-
    -
  • timeseries_id (str) – Database identifier of the timeseries.

  • -
  • instrument_id (str) – Database identifier of the instrument associated with -the timeseries.

  • -
  • name (str) – String identifier of the timeseries and/or instrument.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns.

  • -
  • dates (Annotated[list[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$, ascii_only=None)]], MinLen(min_length=1)]) – Dates of the individual timeseries items. -These dates will not be altered by methods.

  • -
  • values (Annotated[list[float], MinLen(min_length=1)]) – The value or return values of the timeseries items. -These values will not be altered by methods.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency.

  • -
  • tsdf (DataFrame) – Pandas object holding dates and values that can be altered via -methods.

  • -
  • currency (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries.

  • -
  • domestic (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the user’s home currency. -Defaults to “SEK”.

  • -
  • countries (Annotated[set[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]], MinLen(min_length=1)] | Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars. -Optional.

  • -
  • isin (str | None) – ISO 6166 identifier code of the associated instrument. Optional.

  • -
  • label (str | None) – Placeholder for a name of the timeseries. Optional.

  • -
  • constituents (list[Any])

  • -
  • weights (list[float] | None)

  • -
-
-
-
-
-__init__(**data)
-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
-
Parameters:
-

data (Any)

-
-
Return type:
-

None

-
-
-
- -

Methods

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

__init__(**data)

Create a new model by parsing and validating input data from keyword arguments.

acf(lags, *[, squared])

Calculate autocorrelation function for specified lags.

align_index_to_local_cdays([countries, ...])

Align the index of .tsdf with local calendar business days.

all_properties([properties])

Calculate chosen properties.

arithmetic_ret_func([months_from_last, ...])

Annualized arithmetic mean of returns.

autocorr_func([lag, squared])

Calculate autocorrelation at a given lag.

calc_range([months_offset, from_dt, to_dt])

Create a user-defined date range aligned to index.

construct([_fields_set])

copy(*[, include, exclude, update, deep])

Returns a copy of the model.

cvar_down_func([level, months_from_last, ...])

Downside Conditional Value At Risk (CVaR).

dict(*[, include, exclude, by_alias, ...])

ewma_var_func([lmbda, day_chunk, level, ...])

Exponentially Weighted Moving Average Model for Value At Risk (VaR).

ewma_vol_func([lmbda, day_chunk, ...])

Exponentially Weighted Moving Average Model for Volatility.

from_1d_rate_to_cumret([days_in_year, divider])

Convert series of 1-day rates into series of cumulative values.

from_arrays(name, dates, values[, ...])

Create series from a list of dates and a list of values.

from_deepcopy()

Create copy of OpenTimeSeries object.

from_df(dframe[, column_nmbr, valuetype, ...])

Create series from a Pandas DataFrame or Series.

from_fixed_rate(rate[, d_range, days, ...])

Create series from values accruing with a given fixed rate return.

from_orm(obj)

geo_ret_func([months_from_last, from_date, ...])

Compounded Annual Growth Rate (CAGR).

json(*[, include, exclude, by_alias, ...])

kurtosis_func([months_from_last, from_date, ...])

Kurtosis of the return distribution.

ljung_box(lags, *[, squared])

Compute Ljung-Box test for autocorrelation.

lower_partial_moment_func([...])

Lower partial moment and downside deviation (order=2).

max_drawdown_func([months_from_last, ...])

Maximum drawdown without any limit on date range.

model_construct([_fields_set])

Creates a new instance of the Model class with validated data.

model_copy(*[, update, deep])

!!! abstract "Usage Documentation"

model_dump(*[, mode, include, exclude, ...])

!!! abstract "Usage Documentation"

model_dump_json(*[, indent, ensure_ascii, ...])

!!! abstract "Usage Documentation"

model_json_schema([by_alias, ref_template, ...])

Generates a JSON schema for a model class.

model_parametrized_name(params)

Compute the class name for parametrizations of generic classes.

model_post_init(context, /)

Override this method to perform additional initialization after __init__ and model_construct.

model_rebuild(*[, force, raise_errors, ...])

Try to rebuild the pydantic-core schema for the model.

model_validate(obj, *[, strict, extra, ...])

Validate a pydantic model instance.

model_validate_json(json_data, *[, strict, ...])

!!! abstract "Usage Documentation"

model_validate_strings(obj, *[, strict, ...])

Validate the given object with string data against the Pydantic model.

omega_ratio_func([min_accepted_return, ...])

Omega Ratio.

outliers([threshold, months_from_last, ...])

Detect outliers using z-score analysis.

pacf(lags, *[, squared])

Calculate partial autocorrelation function for specified lags.

pandas_df()

Populate .tsdf Pandas DataFrame from the .dates and .values lists.

parse_file(path, *[, content_type, ...])

parse_obj(obj)

parse_raw(b, *[, content_type, encoding, ...])

partial_autocorr([lag, squared])

Calculate partial autocorrelation at a given lag.

plot_bars([mode, title, tick_fmt, filename, ...])

Create a Plotly Bar Figure.

plot_histogram([plot_type, histnorm, ...])

Create a Plotly Histogram Figure.

plot_series([mode, title, tick_fmt, ...])

Create a Plotly Scatter Figure.

positive_share_func([months_from_last, ...])

Share of percentage changes greater than zero.

resample([freq])

Resamples the timeseries frequency.

resample_to_business_period_ends([freq, method])

Resamples timeseries frequency to the business calendar month end dates.

ret_vol_ratio_func([riskfree_rate, ...])

Ratio between arithmetic mean of returns and annualized volatility.

return_nan_handle([method])

Handle missing values in a return series.

rolling_cvar_down([column, level, observations])

Calculate rolling annualized downside CVaR.

rolling_return([column, observations])

Calculate rolling returns.

rolling_var_down([column, level, ...])

Calculate rolling annualized downside Value At Risk (VaR).

rolling_vol([column, observations, ...])

Calculate rolling annualized volatilities.

running_adjustment(adjustment[, days_in_year])

Add or subtract a fee from the timeseries return.

schema([by_alias, ref_template])

schema_json(*[, by_alias, ref_template])

set_new_label([lvl_zero, lvl_one, ...])

Set the column labels of the .tsdf Pandas Dataframe.

skew_func([months_from_last, from_date, to_date])

Skew of the return distribution.

sortino_ratio_func([riskfree_rate, ...])

Sortino ratio or Kappa-3 ratio.

target_weight_from_var([target_vol, level, ...])

Target weight from VaR.

to_cumret()

Convert series of returns into cumulative series of values.

to_drawdown_series()

Convert timeseries into a drawdown series.

to_json(what_output, filename[, directory])

Dump timeseries data into a JSON file.

to_xlsx(filename[, sheet_title, directory, ...])

Save .tsdf DataFrame to an Excel spreadsheet file.

update_forward_refs(**localns)

validate(value)

value_nan_handle([method])

Handle missing values in a value series.

value_ret_calendar_period(year[, month])

Calculate simple return for a specific calendar period.

value_ret_func([months_from_last, ...])

Calculate simple return.

value_to_diff([periods])

Convert series of values to series of their period differences.

value_to_log()

Convert value series to log-weighted series.

value_to_ret()

Convert series of values into series of returns.

var_down_func([level, months_from_last, ...])

Downside Value At Risk (VaR).

vol_from_var_func([level, months_from_last, ...])

Implied annualized volatility from downside VaR.

vol_func([months_from_last, from_date, ...])

Annualized volatility.

worst_func([observations, months_from_last, ...])

Most negative percentage change over a rolling window.

z_score_func([months_from_last, from_date, ...])

Z-score of the last return.

-

Attributes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

arithmetic_ret

Annualized arithmetic mean of returns.

autocorr

Autocorrelation at lag 1.

cvar_down

Downside 95% Conditional Value At Risk "CVaR".

downside_deviation

Downside Deviation.

first_idx

The first date in the timeseries.

geo_ret

Compounded Annual Growth Rate (CAGR).

kappa3_ratio

Kappa-3 ratio.

kurtosis

Kurtosis of the return distribution.

last_idx

The last date in the timeseries.

length

Number of observations.

max_drawdown

Maximum drawdown without any limit on date range.

max_drawdown_cal_year

Maximum drawdown in a single calendar year.

max_drawdown_date

Date when the maximum drawdown occurred.

model_computed_fields

model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_extra

Get extra fields set during validation.

model_fields

model_fields_set

Returns the set of fields that have been explicitly set on this model instance.

omega_ratio

Omega ratio.

periods_in_a_year

The average number of observations per year.

positive_share

The share of percentage changes that are greater than zero.

ret_vol_ratio

Ratio of annualized arithmetic mean of returns and annualized volatility.

skew

Skew of the return distribution.

sortino_ratio

Sortino ratio.

span_of_days

Number of days from the first date to the last.

value_ret

Simple return.

var_down

Downside 95% Value At Risk (VaR).

vol

Annualized volatility.

vol_from_var

Implied annualized volatility from Downside 95% Value at Risk.

worst

Most negative percentage change.

worst_month

Most negative month.

yearfrac

Length of series in years assuming 365.25 days per year.

z_score

Z-score.

timeseries_id

instrument_id

name

valuetype

dates

values

local_ccy

tsdf

currency

domestic

countries

isin

label

constituents

weights

markets

-
-
-timeseries_id: str
-
- -
-
-instrument_id: str
-
- -
-
-name: str
-
- -
-
-valuetype: ValueType
-
- -
-
-dates: DateListType
-
- -
-
-values: ValueListType
-
- -
-
-local_ccy: bool
-
- -
-
-tsdf: DataFrame
-
- -
-
-currency: CurrencyStringType
-
- -
-
-domestic: CurrencyStringType
-
- -
-
-countries: CountriesType
-
- -
-
-isin: str | None
-
- -
-
-label: str | None
-
- -
-
-classmethod from_arrays(name, dates, values, valuetype=ValueType.PRICE, timeseries_id='', instrument_id='', isin=None, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from a list of dates and a list of values.

-
-
Parameters:
-
    -
  • name (str) – String identifier of the timeseries and/or instrument.

  • -
  • dates (Annotated[list[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$, ascii_only=None)]], MinLen(min_length=1)]) – List of date strings as ISO 8601 YYYY-MM-DD.

  • -
  • values (Annotated[list[float], MinLen(min_length=1)]) – Array of float values.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • timeseries_id (str) – Database identifier of the timeseries. Optional.

  • -
  • instrument_id (str) – Database identifier of the instrument associated -with the timeseries. Optional.

  • -
  • isin (str | None) – ISO 6166 identifier code of the associated instrument. Optional.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-classmethod from_df(dframe, column_nmbr=0, valuetype=ValueType.PRICE, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from a Pandas DataFrame or Series.

-
-
Parameters:
-
    -
  • dframe (Series | DataFrame | object) – Pandas DataFrame or Series.

  • -
  • column_nmbr (int) – Using iloc[:, column_nmbr] to pick column. Defaults to 0.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

TypeError – If dframe is not a pandas.Series or a - pandas.DataFrame.

-
-
Return type:
-

Self

-
-
-
- -
-
-classmethod from_fixed_rate(rate, d_range=None, days=None, end_dt=None, label='Series', valuetype=ValueType.PRICE, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from values accruing with a given fixed rate return.

-

Providing a date_range of type Pandas DatetimeIndex takes priority over -providing a combination of days and an end date.

-
-
Parameters:
-
    -
  • rate (float) – The accrual rate.

  • -
  • d_range (DatetimeIndex | None) – A given range of dates. Optional.

  • -
  • days (int | None) – Number of days to generate when date_range not provided. Must be -combined with end_dt. Optional.

  • -
  • end_dt (date | None) – End date of date range to generate when date_range not provided. -Must be combined with days. Optional.

  • -
  • label (str) – Placeholder for a name of the timeseries.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – The currency of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

IncorrectArgumentComboError – If d_range is not provided and the - combination of days and end_dt is incomplete.

-
-
Return type:
-

Self

-
-
-
- -
-
-from_deepcopy()[source]
-

Create copy of OpenTimeSeries object.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-pandas_df()[source]
-

Populate .tsdf Pandas DataFrame from the .dates and .values lists.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-all_properties(properties=None)[source]
-

Calculate chosen properties.

-
-
Parameters:
-
    -
  • properties (list[Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown_cal_year', 'max_drawdown', 'max_drawdown_date', 'first_idx', 'last_idx', 'length', 'span_of_days', 'yearfrac', 'periods_in_a_year', 'autocorr', 'partial_autocorr']] | None) – The properties to calculate. Defaults to calculating all -available. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Properties of the OpenTimeSeries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-value_to_ret()[source]
-

Convert series of values into series of returns.

-
-
Returns:
-

The returns of the values in the series.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-value_to_diff(periods=1)[source]
-

Convert series of values to series of their period differences.

-
-
Parameters:
-
    -
  • periods (int) – The number of periods between observations over which difference -is calculated. Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-to_cumret()[source]
-

Convert series of returns into cumulative series of values.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-from_1d_rate_to_cumret(days_in_year=365, divider=1.0)[source]
-

Convert series of 1-day rates into series of cumulative values.

-
-
Parameters:
-
    -
  • days_in_year (int) – Calendar days per year used as divisor. Defaults to 365.

  • -
  • divider (float) – Convenience divider for when the 1-day rate is not scaled -correctly. Defaults to 1.0.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-resample(freq='BME')[source]
-

Resamples the timeseries frequency.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE'] | str) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-resample_to_business_period_ends(freq='BME', method='nearest')[source]
-

Resamples timeseries frequency to the business calendar month end dates.

-

Stubs left in place. Stubs will be aligned to the shortest stub.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE']) – The date offset string that sets the resampled frequency. -Defaults to BME.

  • -
  • method (Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None) – Controls the method used to align values across columns. -Defaults to nearest.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

ResampleDataLossError – If called on a return series (valuetype is - ValueType.RTRN), since summation across sparser frequency would - be required to avoid data loss.

-
-
Return type:
-

Self

-
-
-
- -
-
-ewma_vol_func(lmbda=0.94, day_chunk=11, dlta_degr_freedms=0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Model for Volatility.

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. -Defaults to 11.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. -Overrides use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series EWMA volatility.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-ewma_var_func(lmbda=0.94, day_chunk=11, level=0.95, dlta_degr_freedms=0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Model for Value At Risk (VaR).

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. -Defaults to 11.

  • -
  • level (float) – The sought VaR level. Defaults to 0.95.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. -Overrides use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series EWMA VaR.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-running_adjustment(adjustment, days_in_year=365)[source]
-

Add or subtract a fee from the timeseries return.

-
-
Parameters:
-
    -
  • adjustment (float) – Fee to add or subtract.

  • -
  • days_in_year (int) – The calculation divisor and assumed number of days in a -calendar year. Defaults to 365.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-set_new_label(lvl_zero=None, lvl_one=None, *, delete_lvl_one=False)[source]
-

Set the column labels of the .tsdf Pandas Dataframe.

-
-
Parameters:
-
    -
  • lvl_zero (str | None) – New level zero label. Optional.

  • -
  • lvl_one (ValueType | None) – New level one label. Optional.

  • -
  • delete_lvl_one (bool) – If True the level one label is deleted. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-acf(lags, *, squared=False)[source]
-

Calculate autocorrelation function for specified lags.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, compute ACF from lag 0 to this value (inclusive). -If list, compute ACF at lag 0 plus each lag in the list.

  • -
  • squared (bool) – If True, compute ACF of squared returns. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of autocorrelations indexed by lag.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-partial_autocorr(lag=1, *, squared=False)[source]
-

Calculate partial autocorrelation at a given lag.

-
-
Parameters:
-
    -
  • lag (int) – The lag at which to compute partial autocorrelation. Defaults to 1.

  • -
  • squared (bool) – If True, compute partial autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Partial autocorrelation at the specified lag.

-
-
Return type:
-

float

-
-
-
- -
-
-pacf(lags, *, squared=False)[source]
-

Calculate partial autocorrelation function for specified lags.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, compute PACF from lag 0 to this value (inclusive). -If list, compute PACF at lag 0 plus each lag in the list.

  • -
  • squared (bool) – If True, compute PACF of squared returns. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of partial autocorrelations indexed by lag.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-ljung_box(lags, *, squared=False)[source]
-

Compute Ljung-Box test for autocorrelation.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, use lags 1 through this value. If list, use the given -lags (lag 0 excluded from test).

  • -
  • squared (bool) – If True, test autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Tuple of (statistic, pvalue, lags) where statistic is the Ljung-Box -Q statistic, pvalue is the chi-squared p-value, and lags is the -list of lags used.

-
-
Return type:
-

tuple[float, float, list[int]]

-
-
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.ReturnSimulation.html b/docs/build/html/api/generated/openseries.ReturnSimulation.html deleted file mode 100644 index 5df9d1fc..00000000 --- a/docs/build/html/api/generated/openseries.ReturnSimulation.html +++ /dev/null @@ -1,583 +0,0 @@ - - - - - - - - - openseries.ReturnSimulation — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.ReturnSimulation

-
-
-class openseries.ReturnSimulation(*, number_of_sims, trading_days, trading_days_in_year, mean_annual_return, mean_annual_vol, dframe, jumps_lamda=0.0, jumps_sigma=0.0, jumps_mu=0.0, seed=None)[source]
-

Bases: BaseModel

-

The class ReturnSimulation allows for simulating financial timeseries.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Total number of days to simulate.

  • -
  • trading_days_in_year (Annotated[int, Strict(strict=True), Ge(ge=1), Le(le=366)]) – Number of trading days used to annualize.

  • -
  • mean_annual_return (float) – Mean annual return of the distribution.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean annual standard deviation of the distribution.

  • -
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • -
  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point in time. -Defaults to 0.0.

  • -
  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • -
  • jumps_mu (float) – This is the average jump size. Defaults to 0.0.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
-
-
-
-
-__init__(**data)
-

Create a new model by parsing and validating input data from keyword arguments.

-

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be -validated to form a valid model.

-

self is explicitly positional-only to allow self as a field name.

-
-
Parameters:
-

data (Any)

-
-
Return type:
-

None

-
-
-
- -

Methods

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

__init__(**data)

Create a new model by parsing and validating input data from keyword arguments.

construct([_fields_set])

copy(*[, include, exclude, update, deep])

Returns a copy of the model.

dict(*[, include, exclude, by_alias, ...])

from_gbm(number_of_sims, mean_annual_return, ...)

Create a Geometric Brownian Motion simulation.

from_lognormal(number_of_sims, ...[, ...])

Create a Lognormal distribution simulation.

from_merton_jump_gbm(number_of_sims, ...[, ...])

Create a Merton Jump-Diffusion model simulation.

from_normal(number_of_sims, ...[, ...])

Create a Normal distribution simulation.

from_orm(obj)

json(*[, include, exclude, by_alias, ...])

model_construct([_fields_set])

Creates a new instance of the Model class with validated data.

model_copy(*[, update, deep])

!!! abstract "Usage Documentation"

model_dump(*[, mode, include, exclude, ...])

!!! abstract "Usage Documentation"

model_dump_json(*[, indent, ensure_ascii, ...])

!!! abstract "Usage Documentation"

model_json_schema([by_alias, ref_template, ...])

Generates a JSON schema for a model class.

model_parametrized_name(params)

Compute the class name for parametrizations of generic classes.

model_post_init(context, /)

Override this method to perform additional initialization after __init__ and model_construct.

model_rebuild(*[, force, raise_errors, ...])

Try to rebuild the pydantic-core schema for the model.

model_validate(obj, *[, strict, extra, ...])

Validate a pydantic model instance.

model_validate_json(json_data, *[, strict, ...])

!!! abstract "Usage Documentation"

model_validate_strings(obj, *[, strict, ...])

Validate the given object with string data against the Pydantic model.

parse_file(path, *[, content_type, ...])

parse_obj(obj)

parse_raw(b, *[, content_type, encoding, ...])

schema([by_alias, ref_template])

schema_json(*[, by_alias, ref_template])

to_dataframe(name[, start, end, countries, ...])

Create a pandas.DataFrame from simulation(s).

update_forward_refs(**localns)

validate(value)

-

Attributes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

model_computed_fields

model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_extra

Get extra fields set during validation.

model_fields

model_fields_set

Returns the set of fields that have been explicitly set on this model instance.

realized_mean_return

Annualized arithmetic mean of returns.

realized_vol

Annualized volatility.

results

Simulation data.

number_of_sims

trading_days

trading_days_in_year

mean_annual_return

mean_annual_vol

dframe

jumps_lamda

jumps_sigma

jumps_mu

seed

-
-
-number_of_sims: PositiveInt
-
- -
-
-trading_days: PositiveInt
-
- -
-
-trading_days_in_year: DaysInYearType
-
- -
-
-mean_annual_return: float
-
- -
-
-mean_annual_vol: PositiveFloat
-
- -
-
-dframe: DataFrame
-
- -
-
-jumps_lamda: NonNegativeFloat
-
- -
-
-jumps_sigma: NonNegativeFloat
-
- -
-
-jumps_mu: float
-
- -
-
-seed: int | None
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-property results: DataFrame[source]
-

Simulation data.

-
-
Returns:
-

Simulation data.

-
-
-
- -
-
-property realized_mean_return: float
-

Annualized arithmetic mean of returns.

-
-
Returns:
-

Annualized arithmetic mean of returns.

-
-
-
- -
-
-property realized_vol: float
-

Annualized volatility.

-
-
Returns:
-

Annualized volatility.

-
-
-
- -
-
-classmethod from_normal(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Normal distribution simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Normal distribution simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_lognormal(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Lognormal distribution simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Lognormal distribution simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_gbm(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Geometric Brownian Motion simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Geometric Brownian Motion simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_merton_jump_gbm(number_of_sims, trading_days, mean_annual_return, mean_annual_vol, jumps_lamda, jumps_sigma=0.0, jumps_mu=0.0, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Merton Jump-Diffusion model simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point -in time.

  • -
  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • -
  • jumps_mu (float) – This is the average jump size. Defaults to 0.0.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Merton Jump-Diffusion model simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-to_dataframe(name, start=None, end=None, countries='SE', markets=None)[source]
-

Create a pandas.DataFrame from simulation(s).

-
-
Parameters:
-
    -
  • name (str) – Name label of the serie(s).

  • -
  • start (dt.date | None) – Date when the simulation starts.

  • -
  • end (dt.date | None) – Date when the simulation ends.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The simulation(s) data.

-
-
Return type:
-

DataFrame

-
-
-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.ValueType.html b/docs/build/html/api/generated/openseries.ValueType.html deleted file mode 100644 index 859bfa15..00000000 --- a/docs/build/html/api/generated/openseries.ValueType.html +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - - - openseries.ValueType — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.ValueType

-
-
-class openseries.ValueType(*values)[source]
-

Bases: StrEnum

-

Enum types of OpenTimeSeries to identify the output.

-
-
-__init__()
-
- -

Methods

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

encode([encoding, errors])

Encode the string using the codec registered for encoding.

replace(old, new, /[, count])

Return a copy with all occurrences of substring old replaced by new.

split([sep, maxsplit])

Return a list of the substrings in the string, using sep as the separator string.

rsplit([sep, maxsplit])

Return a list of the substrings in the string, using sep as the separator string.

join(iterable, /)

Concatenate any number of strings.

capitalize()

Return a capitalized version of the string.

casefold()

Return a version of the string suitable for caseless comparisons.

title()

Return a version of the string where each word is titlecased.

center(width[, fillchar])

Return a centered string of length width.

count

Return the number of non-overlapping occurrences of substring sub in string S[start:end].

expandtabs([tabsize])

Return a copy where all tab characters are expanded using spaces.

find

Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end].

partition(sep, /)

Partition the string into three parts using the given separator.

index

Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end].

ljust(width[, fillchar])

Return a left-justified string of length width.

lower()

Return a copy of the string converted to lowercase.

lstrip([chars])

Return a copy of the string with leading whitespace removed.

rfind

Return the highest index in S where substring sub is found, such that sub is contained within S[start:end].

rindex

Return the highest index in S where substring sub is found, such that sub is contained within S[start:end].

rjust(width[, fillchar])

Return a right-justified string of length width.

rstrip([chars])

Return a copy of the string with trailing whitespace removed.

rpartition(sep, /)

Partition the string into three parts using the given separator.

splitlines([keepends])

Return a list of the lines in the string, breaking at line boundaries.

strip([chars])

Return a copy of the string with leading and trailing whitespace removed.

swapcase()

Convert uppercase characters to lowercase and lowercase characters to uppercase.

translate(table, /)

Replace each character in the string using the given translation table.

upper()

Return a copy of the string converted to uppercase.

startswith

Return True if the string starts with the specified prefix, False otherwise.

endswith

Return True if the string ends with the specified suffix, False otherwise.

removeprefix(prefix, /)

Return a str with the given prefix string removed if present.

removesuffix(suffix, /)

Return a str with the given suffix string removed if present.

isascii()

Return True if all characters in the string are ASCII, False otherwise.

islower()

Return True if the string is a lowercase string, False otherwise.

isupper()

Return True if the string is an uppercase string, False otherwise.

istitle()

Return True if the string is a title-cased string, False otherwise.

isspace()

Return True if the string is a whitespace string, False otherwise.

isdecimal()

Return True if the string is a decimal string, False otherwise.

isdigit()

Return True if the string is a digit string, False otherwise.

isnumeric()

Return True if the string is a numeric string, False otherwise.

isalpha()

Return True if the string is an alphabetic string, False otherwise.

isalnum()

Return True if the string is an alpha-numeric string, False otherwise.

isidentifier()

Return True if the string is a valid Python identifier, False otherwise.

isprintable()

Return True if all characters in the string are printable, False otherwise.

zfill(width, /)

Pad a numeric string with zeros on the left, to fill a field of the given width.

format(*args, **kwargs)

Return a formatted version of the string, using substitutions from args and kwargs.

format_map(mapping, /)

Return a formatted version of the string, using substitutions from mapping.

maketrans

Return a translation table usable for str.translate().

__init__()

-

Attributes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

EWMA_VOL

EWMA_VAR

PRICE

RTRN

RELRTRN

ROLLBETA

ROLLCORR

ROLLCVAR

ROLLINFORATIO

ROLLRTRN

ROLLVAR

ROLLVOL

-
-
-EWMA_VOL = 'EWMA volatility'
-
- -
-
-EWMA_VAR = 'EWMA VaR'
-
- -
-
-PRICE = 'Price(Close)'
-
- -
-
-RTRN = 'Return(Total)'
-
- -
-
-RELRTRN = 'Relative return'
-
- -
-
-ROLLBETA = 'Beta'
-
- -
-
-ROLLCORR = 'Rolling correlation'
-
- -
-
-ROLLCVAR = 'Rolling CVaR'
-
- -
-
-ROLLINFORATIO = 'Information Ratio'
-
- -
-
-ROLLRTRN = 'Rolling returns'
-
- -
-
-ROLLVAR = 'Rolling VaR'
-
- -
-
-ROLLVOL = 'Rolling volatility'
-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.constrain_optimized_portfolios.html b/docs/build/html/api/generated/openseries.constrain_optimized_portfolios.html deleted file mode 100644 index 78159e0d..00000000 --- a/docs/build/html/api/generated/openseries.constrain_optimized_portfolios.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - openseries.constrain_optimized_portfolios — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.constrain_optimized_portfolios

-
-
-openseries.constrain_optimized_portfolios(data, serie, portfolioname='Current Portfolio', simulations=10000, curve_points=200, bounds=None, minimize_method='SLSQP')[source]
-

Constrain optimized portfolios to those that improve on the current one.

-
-
Parameters:
-
    -
  • data (OpenFrame) – Portfolio data.

  • -
  • serie (OpenTimeSeries) – A timeseries representing the current portfolio.

  • -
  • portfolioname (str) – Name of the portfolio. Defaults to “Current Portfolio”.

  • -
  • simulations (int) – Number of possible portfolios to simulate. Defaults to 10000.

  • -
  • curve_points (int) – Number of optimal portfolios on the efficient frontier. -Defaults to 200.

  • -
  • bounds (tuple[tuple[float, float], ...] | None) – The range of minimum and maximum allowed allocations for each asset.

  • -
  • minimize_method (LiteralMinimizeMethods) – The method passed into the scipy.minimize function. -Defaults to SLSQP.

  • -
-
-
Returns:
-

The constrained optimal portfolio data.

-
-
Return type:
-

tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.date_fix.html b/docs/build/html/api/generated/openseries.date_fix.html deleted file mode 100644 index e13b47b8..00000000 --- a/docs/build/html/api/generated/openseries.date_fix.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - openseries.date_fix — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.date_fix

-
-
-openseries.date_fix(fixerdate)[source]
-

Parse different date formats into datetime.date.

-
-
Parameters:
-

fixerdate (DateType) – The data item to parse.

-
-
Returns:
-

Parsed date.

-
-
Raises:
-

TypeError – If the provided fixerdate type is not supported.

-
-
Return type:
-

dt.date

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.date_offset_foll.html b/docs/build/html/api/generated/openseries.date_offset_foll.html deleted file mode 100644 index 3193032d..00000000 --- a/docs/build/html/api/generated/openseries.date_offset_foll.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - openseries.date_offset_foll — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.date_offset_foll

-
-
-openseries.date_offset_foll(raw_date, months_offset=12, countries='SE', markets=None, custom_holidays=None, *, adjust=False, following=True)[source]
-

Offset dates according to a given calendar.

-
-
Parameters:
-
    -
  • raw_date (DateType) – The date to offset from.

  • -
  • months_offset (int) – Number of months as integer. Defaults to 12.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
  • adjust (bool) – Determines if offset should adjust for business days. -Defaults to False.

  • -
  • following (bool) – Determines if days should be offset forward (following) or backward. -Defaults to True.

  • -
-
-
Returns:
-

Offset date.

-
-
Return type:
-

dt.date

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.efficient_frontier.html b/docs/build/html/api/generated/openseries.efficient_frontier.html deleted file mode 100644 index 258ac788..00000000 --- a/docs/build/html/api/generated/openseries.efficient_frontier.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - openseries.efficient_frontier — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.efficient_frontier

-
-
-openseries.efficient_frontier(eframe, num_ports=5000, seed=71, bounds=None, frontier_points=200, minimize_method='SLSQP', *, tweak=True)[source]
-

Identify an efficient frontier.

-
-
Parameters:
-
    -
  • eframe (OpenFrame) – Portfolio data.

  • -
  • num_ports (int) – Number of possible portfolios to simulate. Defaults to 5000.

  • -
  • seed (int) – The seed for the random process. Defaults to 71.

  • -
  • bounds (tuple[tuple[float, float], ...] | None) – The range of minimum and maximum allowed allocations for each asset.

  • -
  • frontier_points (int) – Number of points along frontier to optimize. Defaults to 200.

  • -
  • minimize_method (LiteralMinimizeMethods) – The method passed into the scipy.minimize function. -Defaults to SLSQP.

  • -
  • tweak (bool) – Cutting the frontier to exclude multiple points with almost the -same risk. -Defaults to True.

  • -
-
-
Returns:
-

The efficient frontier data, simulation data and optimal portfolio.

-
-
Return type:
-

tuple[DataFrame, DataFrame, NDArray[float64]]

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.generate_calendar_date_range.html b/docs/build/html/api/generated/openseries.generate_calendar_date_range.html deleted file mode 100644 index 3d745642..00000000 --- a/docs/build/html/api/generated/openseries.generate_calendar_date_range.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - - openseries.generate_calendar_date_range — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.generate_calendar_date_range

-
-
-openseries.generate_calendar_date_range(trading_days, start=None, end=None, countries='SE', markets=None, custom_holidays=None)[source]
-

Generate a list of business day calendar dates.

-
-
Parameters:
-
    -
  • trading_days (int) – Number of days to generate. Must be greater than zero.

  • -
  • start (dt.date | None) – Date when the range starts.

  • -
  • end (dt.date | None) – Date when the range ends.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

List of business day calendar dates.

-
-
Return type:
-

list[dt.date]

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.get_previous_business_day_before_today.html b/docs/build/html/api/generated/openseries.get_previous_business_day_before_today.html deleted file mode 100644 index ea2d05de..00000000 --- a/docs/build/html/api/generated/openseries.get_previous_business_day_before_today.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - openseries.get_previous_business_day_before_today — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.get_previous_business_day_before_today

-
-
-openseries.get_previous_business_day_before_today(today=None, countries='SE', markets=None, custom_holidays=None)[source]
-

Bump date backwards to find the previous business day.

-
-
Parameters:
-
    -
  • today (dt.date | None) – Manual input of the day from where the previous business day is found.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

The previous business day.

-
-
Return type:
-

dt.date

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.holiday_calendar.html b/docs/build/html/api/generated/openseries.holiday_calendar.html deleted file mode 100644 index 0c7927d3..00000000 --- a/docs/build/html/api/generated/openseries.holiday_calendar.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - openseries.holiday_calendar — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.holiday_calendar

-
-
-openseries.holiday_calendar(startyear, endyear, countries='SE', markets=None, custom_holidays=None)[source]
-

Generate a business calendar.

-
-
Parameters:
-
    -
  • startyear (int) – First year in date range generated.

  • -
  • endyear (int) – Last year in date range generated.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

Generate a business calendar.

-
-
Raises:
-

CountriesNotStringNorListStrError – If countries is not a supported - ISO 3166-1 alpha-2 string or a list of such strings.

-
-
Return type:
-

busdaycalendar

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.load_plotly_dict.html b/docs/build/html/api/generated/openseries.load_plotly_dict.html deleted file mode 100644 index 80fe475a..00000000 --- a/docs/build/html/api/generated/openseries.load_plotly_dict.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - openseries.load_plotly_dict — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.load_plotly_dict

-
-
-openseries.load_plotly_dict(*, responsive=True)[source]
-

Load Plotly defaults.

-
-
Parameters:
-

responsive (bool) – Flag whether to load as responsive. Defaults to True.

-
-
Returns:
-

A tuple (config_and_layout, logo) -where config_and_layout is the Plotly config and layout template dict, -and logo is the Captor logo dict (may be empty if the remote logo is -unavailable).

-
-
Return type:
-

tuple[PlotlyLayoutType, CaptorLogoType]

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.offset_business_days.html b/docs/build/html/api/generated/openseries.offset_business_days.html deleted file mode 100644 index c1fae54c..00000000 --- a/docs/build/html/api/generated/openseries.offset_business_days.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - openseries.offset_business_days — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.offset_business_days

-
-
-openseries.offset_business_days(ddate, days, countries='SE', markets=None, custom_holidays=None)[source]
-

Bump date by business days.

-

It first adjusts to a valid business day and then bumps with given -number of business days from there.

-
-
Parameters:
-
    -
  • ddate (dt.date) – A starting date that does not have to be a business day.

  • -
  • days (int) – The number of business days to offset from the business day -that is given. -If days is set as anything other than an integer its value is set to zero.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Argument where missing holidays can be added.

  • -
-
-
Returns:
-

The new offset business day.

-
-
Return type:
-

dt.date

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.prepare_plot_data.html b/docs/build/html/api/generated/openseries.prepare_plot_data.html deleted file mode 100644 index e4f76b27..00000000 --- a/docs/build/html/api/generated/openseries.prepare_plot_data.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - openseries.prepare_plot_data — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.prepare_plot_data

-
-
-openseries.prepare_plot_data(assets, current, optimized)[source]
-

Prepare data to be used as point_frame in the sharpeplot function.

-
-
Parameters:
-
    -
  • assets (OpenFrame) – Portfolio data with individual assets and a weighted portfolio.

  • -
  • current (OpenTimeSeries) – The current or initial portfolio based on given weights.

  • -
  • optimized (NDArray[float64]) – Data optimized with the efficient_frontier method.

  • -
-
-
Returns:
-

The data prepared with mean returns, volatility and weights.

-
-
Return type:
-

DataFrame

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.report_html.html b/docs/build/html/api/generated/openseries.report_html.html deleted file mode 100644 index f63266aa..00000000 --- a/docs/build/html/api/generated/openseries.report_html.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - - openseries.report_html — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.report_html

-
-
-openseries.report_html(data, bar_freq='BYE', filename=None, title=None, directory=None, output_type='file', include_plotlyjs='cdn', *, auto_open=False, add_logo=True, vertical_legend=True)[source]
-

Generate a responsive HTML report page with line and bar plots and a table.

-
-
Parameters:
-
    -
  • data (OpenFrame)

  • -
  • bar_freq (LiteralBizDayFreq)

  • -
  • filename (str | None)

  • -
  • title (str | None)

  • -
  • directory (Path | None)

  • -
  • output_type (LiteralPlotlyOutput)

  • -
  • include_plotlyjs (LiteralPlotlyJSlib)

  • -
  • auto_open (bool)

  • -
  • add_logo (bool)

  • -
  • vertical_legend (bool)

  • -
-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.sharpeplot.html b/docs/build/html/api/generated/openseries.sharpeplot.html deleted file mode 100644 index 86d5a3d1..00000000 --- a/docs/build/html/api/generated/openseries.sharpeplot.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - - - openseries.sharpeplot — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.sharpeplot

-
-
-openseries.sharpeplot(sim_frame=None, line_frame=None, point_frame=None, point_frame_mode='markers', filename=None, directory=None, titletext=None, output_type='file', include_plotlyjs='cdn', *, title=True, add_logo=True, auto_open=True)[source]
-

Create scatter plot coloured by Sharpe Ratio.

-
-
Parameters:
-
    -
  • sim_frame (DataFrame | None) – Data from the simulate_portfolios method.

  • -
  • line_frame (DataFrame | None) – Data from the efficient_frontier method.

  • -
  • point_frame (DataFrame | None) – Data to highlight current and efficient portfolios.

  • -
  • point_frame_mode (LiteralLinePlotMode) – Which type of scatter to use. Defaults to markers.

  • -
  • filename (str | None) – Name of the Plotly html file.

  • -
  • directory (DirectoryPath | None) – Directory where Plotly html file is saved.

  • -
  • titletext (str | None) – Text for the plot title.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type. Defaults to “file”.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – Determines how the plotly.js library is included -in the output. -Defaults to “cdn”.

  • -
  • title (bool) – Whether to add standard plot title. Defaults to True.

  • -
  • add_logo (bool) – Whether to add Captor logo. Defaults to True.

  • -
  • auto_open (bool) – Determines whether to open a browser window with the plot. -Defaults to True.

  • -
-
-
Returns:
-

The scatter plot with simulated and optimized results.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.simulate_portfolios.html b/docs/build/html/api/generated/openseries.simulate_portfolios.html deleted file mode 100644 index d05372ce..00000000 --- a/docs/build/html/api/generated/openseries.simulate_portfolios.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - openseries.simulate_portfolios — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.simulate_portfolios

-
-
-openseries.simulate_portfolios(simframe, num_ports, seed)[source]
-

Generate random weights for simulated portfolios.

-
-
Parameters:
-
    -
  • simframe (OpenFrame) – Return data for portfolio constituents.

  • -
  • num_ports (int) – Number of possible portfolios to simulate.

  • -
  • seed (int) – The seed for the random process.

  • -
-
-
Returns:
-

The resulting data.

-
-
Return type:
-

DataFrame

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/generated/openseries.timeseries_chain.html b/docs/build/html/api/generated/openseries.timeseries_chain.html deleted file mode 100644 index 3ac005a6..00000000 --- a/docs/build/html/api/generated/openseries.timeseries_chain.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - openseries.timeseries_chain — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries.timeseries_chain

-
-
-openseries.timeseries_chain(front, back, old_fee=0.0)[source]
-

Chain two timeseries together.

-

The function assumes that the two series have at least one date in common.

-
-
Parameters:
-
    -
  • front (TypeOpenTimeSeries) – Earlier series to chain with.

  • -
  • back (TypeOpenTimeSeries) – Later series to chain with.

  • -
  • old_fee (float) – Fee to apply to earlier series. Defaults to 0.0.

  • -
-
-
Returns:
-

An OpenTimeSeries object or a subclass thereof.

-
-
Return type:
-

TypeOpenTimeSeries

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/openseries.html b/docs/build/html/api/openseries.html deleted file mode 100644 index fc534ca0..00000000 --- a/docs/build/html/api/openseries.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - openseries package — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries package

-

The openseries package provides two main classes for financial time series analysis:

-
    -
  • OpenTimeSeries: For single financial time series analysis

  • -
  • OpenFrame: For multi-asset portfolio analysis and comparison

  • -
-

Both classes inherit from the private _CommonModel class, which in turn inherits from Pydantic’s BaseModel. This inheritance structure provides:

-
    -
  • Data validation through Pydantic’s validation system

  • -
  • Common functionality shared between both classes (risk metrics, plotting, data handling)

  • -
  • Type safety and consistent API design

  • -
-

The _CommonModel class contains all the shared methods and properties that both OpenTimeSeries and OpenFrame use, including risk calculations, plotting capabilities, and data manipulation functions.

-
-

Main Classes

- - - - - - - - - -

openseries.OpenTimeSeries

OpenTimeSeries objects are at the core of the openseries package.

openseries.OpenFrame

OpenFrame objects hold OpenTimeSeries in the list constituents.

-
-
-

Utility Functions

- - - - - - - - - -

openseries.timeseries_chain

Chain two timeseries together.

openseries.report_html

Generate a responsive HTML report page with line and bar plots and a table.

-
-
-

Portfolio Tools

- - - - - - - - - - - - - - - - - - -

openseries.efficient_frontier

Identify an efficient frontier.

openseries.simulate_portfolios

Generate random weights for simulated portfolios.

openseries.constrain_optimized_portfolios

Constrain optimized portfolios to those that improve on the current one.

openseries.prepare_plot_data

Prepare data to be used as point_frame in the sharpeplot function.

openseries.sharpeplot

Create scatter plot coloured by Sharpe Ratio.

-
-
-

Date Utilities

- - - - - - - - - - - - - - - - - - - - - -

openseries.date_fix

Parse different date formats into datetime.date.

openseries.date_offset_foll

Offset dates according to a given calendar.

openseries.generate_calendar_date_range

Generate a list of business day calendar dates.

openseries.get_previous_business_day_before_today

Bump date backwards to find the previous business day.

openseries.holiday_calendar

Generate a business calendar.

openseries.offset_business_days

Bump date by business days.

-
-
-

Simulation

- - - - - - -

openseries.ReturnSimulation

The class ReturnSimulation allows for simulating financial timeseries.

-
-
-

Types and Enums

- - - - - - -

openseries.ValueType

Enum types of OpenTimeSeries to identify the output.

-
-
-

Other Utilities

- - - - - - - - - -

openseries.load_plotly_dict

Load Plotly defaults.

openseries.export_plotly_figure

Export a Plotly figure to a mobile-responsive HTML file or inline div.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/portfoliotools.html b/docs/build/html/api/portfoliotools.html deleted file mode 100644 index 8798ef19..00000000 --- a/docs/build/html/api/portfoliotools.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - - - Portfolio Tools — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Portfolio Tools

-

The portfoliotools module provides functions for portfolio optimization, simulation, and analysis.

-
-

Portfolio Optimization

-
-
-openseries.portfoliotools.efficient_frontier(eframe, num_ports=5000, seed=71, bounds=None, frontier_points=200, minimize_method='SLSQP', *, tweak=True)[source]
-

Identify an efficient frontier.

-
-
Parameters:
-
    -
  • eframe (OpenFrame) – Portfolio data.

  • -
  • num_ports (int) – Number of possible portfolios to simulate. Defaults to 5000.

  • -
  • seed (int) – The seed for the random process. Defaults to 71.

  • -
  • bounds (tuple[tuple[float, float], ...] | None) – The range of minimum and maximum allowed allocations for each asset.

  • -
  • frontier_points (int) – Number of points along frontier to optimize. Defaults to 200.

  • -
  • minimize_method (LiteralMinimizeMethods) – The method passed into the scipy.minimize function. -Defaults to SLSQP.

  • -
  • tweak (bool) – Cutting the frontier to exclude multiple points with almost the -same risk. -Defaults to True.

  • -
-
-
Returns:
-

The efficient frontier data, simulation data and optimal portfolio.

-
-
Return type:
-

tuple[DataFrame, DataFrame, NDArray[float64]]

-
-
-
- -
-
-

Portfolio Simulation

-
-
-openseries.portfoliotools.simulate_portfolios(simframe, num_ports, seed)[source]
-

Generate random weights for simulated portfolios.

-
-
Parameters:
-
    -
  • simframe (OpenFrame) – Return data for portfolio constituents.

  • -
  • num_ports (int) – Number of possible portfolios to simulate.

  • -
  • seed (int) – The seed for the random process.

  • -
-
-
Returns:
-

The resulting data.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-

Portfolio Constraints

-
-
-openseries.portfoliotools.constrain_optimized_portfolios(data, serie, portfolioname='Current Portfolio', simulations=10000, curve_points=200, bounds=None, minimize_method='SLSQP')[source]
-

Constrain optimized portfolios to those that improve on the current one.

-
-
Parameters:
-
    -
  • data (OpenFrame) – Portfolio data.

  • -
  • serie (OpenTimeSeries) – A timeseries representing the current portfolio.

  • -
  • portfolioname (str) – Name of the portfolio. Defaults to “Current Portfolio”.

  • -
  • simulations (int) – Number of possible portfolios to simulate. Defaults to 10000.

  • -
  • curve_points (int) – Number of optimal portfolios on the efficient frontier. -Defaults to 200.

  • -
  • bounds (tuple[tuple[float, float], ...] | None) – The range of minimum and maximum allowed allocations for each asset.

  • -
  • minimize_method (LiteralMinimizeMethods) – The method passed into the scipy.minimize function. -Defaults to SLSQP.

  • -
-
-
Returns:
-

The constrained optimal portfolio data.

-
-
Return type:
-

tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]

-
-
-
- -
-
-

Visualization

-
-
-openseries.portfoliotools.prepare_plot_data(assets, current, optimized)[source]
-

Prepare data to be used as point_frame in the sharpeplot function.

-
-
Parameters:
-
    -
  • assets (OpenFrame) – Portfolio data with individual assets and a weighted portfolio.

  • -
  • current (OpenTimeSeries) – The current or initial portfolio based on given weights.

  • -
  • optimized (NDArray[float64]) – Data optimized with the efficient_frontier method.

  • -
-
-
Returns:
-

The data prepared with mean returns, volatility and weights.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-openseries.portfoliotools.sharpeplot(sim_frame=None, line_frame=None, point_frame=None, point_frame_mode='markers', filename=None, directory=None, titletext=None, output_type='file', include_plotlyjs='cdn', *, title=True, add_logo=True, auto_open=True)[source]
-

Create scatter plot coloured by Sharpe Ratio.

-
-
Parameters:
-
    -
  • sim_frame (DataFrame | None) – Data from the simulate_portfolios method.

  • -
  • line_frame (DataFrame | None) – Data from the efficient_frontier method.

  • -
  • point_frame (DataFrame | None) – Data to highlight current and efficient portfolios.

  • -
  • point_frame_mode (LiteralLinePlotMode) – Which type of scatter to use. Defaults to markers.

  • -
  • filename (str | None) – Name of the Plotly html file.

  • -
  • directory (DirectoryPath | None) – Directory where Plotly html file is saved.

  • -
  • titletext (str | None) – Text for the plot title.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type. Defaults to “file”.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – Determines how the plotly.js library is included -in the output. -Defaults to “cdn”.

  • -
  • title (bool) – Whether to add standard plot title. Defaults to True.

  • -
  • add_logo (bool) – Whether to add Captor logo. Defaults to True.

  • -
  • auto_open (bool) – Determines whether to open a browser window with the plot. -Defaults to True.

  • -
-
-
Returns:
-

The scatter plot with simulated and optimized results.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/report.html b/docs/build/html/api/report.html deleted file mode 100644 index 4e94c57e..00000000 --- a/docs/build/html/api/report.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - - Report Generation — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Report Generation

-
-

HTML Report Function

-
-
-openseries.report.report_html(data, bar_freq='BYE', filename=None, title=None, directory=None, output_type='file', include_plotlyjs='cdn', *, auto_open=False, add_logo=True, vertical_legend=True)[source]
-

Generate a responsive HTML report page with line and bar plots and a table.

-
-
Parameters:
-
    -
  • data (OpenFrame)

  • -
  • bar_freq (LiteralBizDayFreq)

  • -
  • filename (str | None)

  • -
  • title (str | None)

  • -
  • directory (Path | None)

  • -
  • output_type (LiteralPlotlyOutput)

  • -
  • include_plotlyjs (LiteralPlotlyJSlib)

  • -
  • auto_open (bool)

  • -
  • add_logo (bool)

  • -
  • vertical_legend (bool)

  • -
-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -

The report_html function creates comprehensive HTML reports for financial analysis, comparing multiple assets and providing detailed performance metrics, charts, and risk analysis.

-

The generated report includes:

-
    -
  • Interactive line charts showing cumulative returns over time for all assets

  • -
  • Bar charts displaying period returns (annual, quarterly, or monthly depending on data length)

  • -
  • Performance metrics table including: -- Return metrics (CAGR or simple return, Year-to-Date, Month-to-Date) -- Risk metrics (Volatility, Sharpe Ratio, Sortino Ratio) -- Relative performance metrics (Jensen’s Alpha, Information Ratio, Tracking Error, Index Beta) -- Capture Ratio (for periods longer than one year) -- Worst period returns -- Comparison period dates

  • -
-

Important Notes:

-
    -
  • The last asset in the OpenFrame is used as the benchmark for relative performance metrics -(Jensen’s Alpha, Information Ratio, Tracking Error, Index Beta, and Capture Ratio)

  • -
  • For periods shorter than one year, the report uses simple returns instead of CAGR

  • -
  • For periods shorter than a quarter, bar charts show daily returns instead of period returns

  • -
  • Capture Ratio is only included for periods longer than one year

  • -
-
-
-

Responsive Design

-

The report features responsive design with separate layouts optimized for desktop and mobile devices:

-
    -
  • Desktop layout: Charts and tables are displayed side-by-side in a 2x2 grid layout. The table -is rendered as an interactive Plotly table integrated with the charts.

  • -
  • Mobile layout: Content is stacked vertically for better viewing on smaller screens. The table -is rendered as a standard HTML table below the charts for better mobile compatibility.

  • -
-

The HTML output automatically adapts to screen size and device capabilities using CSS media queries -and JavaScript detection. The layout switches at a breakpoint of 960px width or when touch capabilities -are detected.

-
-
-

Return Values

-

The function returns a tuple containing:

-
    -
  • Plotly Figure: The desktop version of the figure object (can be used for further customization -or interactive display)

  • -
  • String output: The type depends on the output_type parameter:

    -
      -
    • When output_type="file" (default): Returns the file path string to the saved HTML file. -The file contains a complete HTML document (with DOCTYPE, html, head, and body tags) -that can be opened directly in a web browser. If auto_open=True, the file will -automatically open in the default web browser.

    • -
    • When output_type="div": Returns a string containing the responsive HTML div section -(includes both desktop and mobile layouts with CSS and JavaScript) that can be embedded -in an existing HTML page. The filename parameter is optional when using this mode and -is only used to generate unique div IDs for the embedded content.

    • -
    -
  • -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/series.html b/docs/build/html/api/series.html deleted file mode 100644 index 295d5474..00000000 --- a/docs/build/html/api/series.html +++ /dev/null @@ -1,2685 +0,0 @@ - - - - - - - - - OpenTimeSeries — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

OpenTimeSeries

-
-
-class openseries.OpenTimeSeries(*, constituents=<factory>, weights=None, markets=None, tsdf, timeseries_id, instrument_id, name, valuetype, dates, values, local_ccy, currency, domestic='SEK', countries='SE', isin=None, label=None)[source]
-

Bases: _CommonModel[float]

-

OpenTimeSeries objects are at the core of the openseries package.

-

The intended use is to allow analyses of financial timeseries. -It is only intended for daily or less frequent data samples.

-
-
Parameters:
-
    -
  • timeseries_id (str) – Database identifier of the timeseries.

  • -
  • instrument_id (str) – Database identifier of the instrument associated with -the timeseries.

  • -
  • name (str) – String identifier of the timeseries and/or instrument.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns.

  • -
  • dates (Annotated[list[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$, ascii_only=None)]], MinLen(min_length=1)]) – Dates of the individual timeseries items. -These dates will not be altered by methods.

  • -
  • values (Annotated[list[float], MinLen(min_length=1)]) – The value or return values of the timeseries items. -These values will not be altered by methods.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency.

  • -
  • tsdf (DataFrame) – Pandas object holding dates and values that can be altered via -methods.

  • -
  • currency (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries.

  • -
  • domestic (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the user’s home currency. -Defaults to “SEK”.

  • -
  • countries (Annotated[set[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]], MinLen(min_length=1)] | Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars. -Optional.

  • -
  • isin (str | None) – ISO 6166 identifier code of the associated instrument. Optional.

  • -
  • label (str | None) – Placeholder for a name of the timeseries. Optional.

  • -
  • constituents (list[Any])

  • -
  • weights (list[float] | None)

  • -
-
-
-
-
-timeseries_id: str
-
- -
-
-instrument_id: str
-
- -
-
-name: str
-
- -
-
-valuetype: ValueType
-
- -
-
-dates: DateListType
-
- -
-
-values: ValueListType
-
- -
-
-local_ccy: bool
-
- -
-
-tsdf: DataFrame
-
- -
-
-currency: CurrencyStringType
-
- -
-
-domestic: CurrencyStringType
-
- -
-
-countries: CountriesType
-
- -
-
-isin: str | None
-
- -
-
-label: str | None
-
- -
-
-classmethod from_arrays(name, dates, values, valuetype=ValueType.PRICE, timeseries_id='', instrument_id='', isin=None, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from a list of dates and a list of values.

-
-
Parameters:
-
    -
  • name (str) – String identifier of the timeseries and/or instrument.

  • -
  • dates (Annotated[list[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$, ascii_only=None)]], MinLen(min_length=1)]) – List of date strings as ISO 8601 YYYY-MM-DD.

  • -
  • values (Annotated[list[float], MinLen(min_length=1)]) – Array of float values.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • timeseries_id (str) – Database identifier of the timeseries. Optional.

  • -
  • instrument_id (str) – Database identifier of the instrument associated -with the timeseries. Optional.

  • -
  • isin (str | None) – ISO 6166 identifier code of the associated instrument. Optional.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-classmethod from_df(dframe, column_nmbr=0, valuetype=ValueType.PRICE, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from a Pandas DataFrame or Series.

-
-
Parameters:
-
    -
  • dframe (Series | DataFrame | object) – Pandas DataFrame or Series.

  • -
  • column_nmbr (int) – Using iloc[:, column_nmbr] to pick column. Defaults to 0.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

TypeError – If dframe is not a pandas.Series or a - pandas.DataFrame.

-
-
Return type:
-

Self

-
-
-
- -
-
-classmethod from_fixed_rate(rate, d_range=None, days=None, end_dt=None, label='Series', valuetype=ValueType.PRICE, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from values accruing with a given fixed rate return.

-

Providing a date_range of type Pandas DatetimeIndex takes priority over -providing a combination of days and an end date.

-
-
Parameters:
-
    -
  • rate (float) – The accrual rate.

  • -
  • d_range (DatetimeIndex | None) – A given range of dates. Optional.

  • -
  • days (int | None) – Number of days to generate when date_range not provided. Must be -combined with end_dt. Optional.

  • -
  • end_dt (date | None) – End date of date range to generate when date_range not provided. -Must be combined with days. Optional.

  • -
  • label (str) – Placeholder for a name of the timeseries.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – The currency of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

IncorrectArgumentComboError – If d_range is not provided and the - combination of days and end_dt is incomplete.

-
-
Return type:
-

Self

-
-
-
- -
-
-from_deepcopy()[source]
-

Create copy of OpenTimeSeries object.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-pandas_df()[source]
-

Populate .tsdf Pandas DataFrame from the .dates and .values lists.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-all_properties(properties=None)[source]
-

Calculate chosen properties.

-
-
Parameters:
-
    -
  • properties (list[Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown_cal_year', 'max_drawdown', 'max_drawdown_date', 'first_idx', 'last_idx', 'length', 'span_of_days', 'yearfrac', 'periods_in_a_year', 'autocorr', 'partial_autocorr']] | None) – The properties to calculate. Defaults to calculating all -available. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Properties of the OpenTimeSeries.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-value_to_ret()[source]
-

Convert series of values into series of returns.

-
-
Returns:
-

The returns of the values in the series.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-value_to_diff(periods=1)[source]
-

Convert series of values to series of their period differences.

-
-
Parameters:
-
    -
  • periods (int) – The number of periods between observations over which difference -is calculated. Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-to_cumret()[source]
-

Convert series of returns into cumulative series of values.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-from_1d_rate_to_cumret(days_in_year=365, divider=1.0)[source]
-

Convert series of 1-day rates into series of cumulative values.

-
-
Parameters:
-
    -
  • days_in_year (int) – Calendar days per year used as divisor. Defaults to 365.

  • -
  • divider (float) – Convenience divider for when the 1-day rate is not scaled -correctly. Defaults to 1.0.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-resample(freq='BME')[source]
-

Resamples the timeseries frequency.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE'] | str) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-resample_to_business_period_ends(freq='BME', method='nearest')[source]
-

Resamples timeseries frequency to the business calendar month end dates.

-

Stubs left in place. Stubs will be aligned to the shortest stub.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE']) – The date offset string that sets the resampled frequency. -Defaults to BME.

  • -
  • method (Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None) – Controls the method used to align values across columns. -Defaults to nearest.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

ResampleDataLossError – If called on a return series (valuetype is - ValueType.RTRN), since summation across sparser frequency would - be required to avoid data loss.

-
-
Return type:
-

Self

-
-
-
- -
-
-ewma_vol_func(lmbda=0.94, day_chunk=11, dlta_degr_freedms=0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Model for Volatility.

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. -Defaults to 11.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. -Overrides use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series EWMA volatility.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-ewma_var_func(lmbda=0.94, day_chunk=11, level=0.95, dlta_degr_freedms=0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Model for Value At Risk (VaR).

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. -Defaults to 11.

  • -
  • level (float) – The sought VaR level. Defaults to 0.95.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. -Overrides use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series EWMA VaR.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-running_adjustment(adjustment, days_in_year=365)[source]
-

Add or subtract a fee from the timeseries return.

-
-
Parameters:
-
    -
  • adjustment (float) – Fee to add or subtract.

  • -
  • days_in_year (int) – The calculation divisor and assumed number of days in a -calendar year. Defaults to 365.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-set_new_label(lvl_zero=None, lvl_one=None, *, delete_lvl_one=False)[source]
-

Set the column labels of the .tsdf Pandas Dataframe.

-
-
Parameters:
-
    -
  • lvl_zero (str | None) – New level zero label. Optional.

  • -
  • lvl_one (ValueType | None) – New level one label. Optional.

  • -
  • delete_lvl_one (bool) – If True the level one label is deleted. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-acf(lags, *, squared=False)[source]
-

Calculate autocorrelation function for specified lags.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, compute ACF from lag 0 to this value (inclusive). -If list, compute ACF at lag 0 plus each lag in the list.

  • -
  • squared (bool) – If True, compute ACF of squared returns. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of autocorrelations indexed by lag.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-partial_autocorr(lag=1, *, squared=False)[source]
-

Calculate partial autocorrelation at a given lag.

-
-
Parameters:
-
    -
  • lag (int) – The lag at which to compute partial autocorrelation. Defaults to 1.

  • -
  • squared (bool) – If True, compute partial autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Partial autocorrelation at the specified lag.

-
-
Return type:
-

float

-
-
-
- -
-
-pacf(lags, *, squared=False)[source]
-

Calculate partial autocorrelation function for specified lags.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, compute PACF from lag 0 to this value (inclusive). -If list, compute PACF at lag 0 plus each lag in the list.

  • -
  • squared (bool) – If True, compute PACF of squared returns. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of partial autocorrelations indexed by lag.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-ljung_box(lags, *, squared=False)[source]
-

Compute Ljung-Box test for autocorrelation.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, use lags 1 through this value. If list, use the given -lags (lag 0 excluded from test).

  • -
  • squared (bool) – If True, test autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Tuple of (statistic, pvalue, lags) where statistic is the Ljung-Box -Q statistic, pvalue is the chi-squared p-value, and lags is the -list of lags used.

-
-
Return type:
-

tuple[float, float, list[int]]

-
-
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
- -

The OpenTimeSeries class is the core component for analyzing individual financial time series. It provides comprehensive functionality for:

-
    -
  • Loading data from various sources (arrays, DataFrames, fixed rates)

  • -
  • Calculating financial metrics and risk measures

  • -
  • Performing time series transformations

  • -
  • Creating visualizations

  • -
  • Exporting results

  • -
-
-

Class Methods for Construction

-
-
-classmethod OpenTimeSeries.from_arrays(name, dates, values, valuetype=ValueType.PRICE, timeseries_id='', instrument_id='', isin=None, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from a list of dates and a list of values.

-
-
Parameters:
-
    -
  • name (str) – String identifier of the timeseries and/or instrument.

  • -
  • dates (Annotated[list[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$, ascii_only=None)]], MinLen(min_length=1)]) – List of date strings as ISO 8601 YYYY-MM-DD.

  • -
  • values (Annotated[list[float], MinLen(min_length=1)]) – Array of float values.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • timeseries_id (str) – Database identifier of the timeseries. Optional.

  • -
  • instrument_id (str) – Database identifier of the instrument associated -with the timeseries. Optional.

  • -
  • isin (str | None) – ISO 6166 identifier code of the associated instrument. Optional.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-classmethod OpenTimeSeries.from_df(dframe, column_nmbr=0, valuetype=ValueType.PRICE, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from a Pandas DataFrame or Series.

-
-
Parameters:
-
    -
  • dframe (Series | DataFrame | object) – Pandas DataFrame or Series.

  • -
  • column_nmbr (int) – Using iloc[:, column_nmbr] to pick column. Defaults to 0.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

TypeError – If dframe is not a pandas.Series or a - pandas.DataFrame.

-
-
Return type:
-

Self

-
-
-
- -
-
-classmethod OpenTimeSeries.from_fixed_rate(rate, d_range=None, days=None, end_dt=None, label='Series', valuetype=ValueType.PRICE, baseccy='SEK', *, local_ccy=True)[source]
-

Create series from values accruing with a given fixed rate return.

-

Providing a date_range of type Pandas DatetimeIndex takes priority over -providing a combination of days and an end date.

-
-
Parameters:
-
    -
  • rate (float) – The accrual rate.

  • -
  • d_range (DatetimeIndex | None) – A given range of dates. Optional.

  • -
  • days (int | None) – Number of days to generate when date_range not provided. Must be -combined with end_dt. Optional.

  • -
  • end_dt (date | None) – End date of date range to generate when date_range not provided. -Must be combined with days. Optional.

  • -
  • label (str) – Placeholder for a name of the timeseries.

  • -
  • valuetype (ValueType) – Identifies if the series is a series of values or returns. -Defaults to ValueType.PRICE.

  • -
  • baseccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – The currency of the timeseries. Defaults to “SEK”.

  • -
  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency. -Defaults to True.

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

IncorrectArgumentComboError – If d_range is not provided and the - combination of days and end_dt is incomplete.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.from_deepcopy()[source]
-

Create copy of OpenTimeSeries object.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-

Properties

-
-

Non-numerical Properties

-
-
-OpenTimeSeries.timeseries_id: str
-
- -
-
-OpenTimeSeries.instrument_id: str
-
- -
-
-OpenTimeSeries.dates: DateListType
-
- -
-
-OpenTimeSeries.values: ValueListType
-
- -
-
-OpenTimeSeries.currency: CurrencyStringType
-
- -
-
-OpenTimeSeries.domestic: CurrencyStringType
-
- -
-
-OpenTimeSeries.local_ccy: bool
-
- -
-
-OpenTimeSeries.name: str
-
- -
-
-OpenTimeSeries.isin: str | None
-
- -
-
-OpenTimeSeries.label: str | None
-
- -
-
-OpenTimeSeries.countries: CountriesType
-
- -
-
-OpenTimeSeries.markets
-
- -
-
-OpenTimeSeries.valuetype: ValueType
-
- -
-
-

Common Properties

-
-
-OpenTimeSeries.first_idx
-

The first date in the timeseries.

-
-
Returns:
-

The first date in the timeseries.

-
-
-
- -
-
-OpenTimeSeries.last_idx
-

The last date in the timeseries.

-
-
Returns:
-

The last date in the timeseries.

-
-
-
- -
-
-OpenTimeSeries.length
-

Number of observations.

-
-
Returns:
-

Number of observations.

-
-
-
- -
-
-OpenTimeSeries.span_of_days
-

Number of days from the first date to the last.

-
-
Returns:
-

Number of days from the first date to the last.

-
-
-
- -
-
-OpenTimeSeries.tsdf: DataFrame
-
- -
-
-OpenTimeSeries.max_drawdown_date
-

Date when the maximum drawdown occurred.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-

Returns:

-
-
datetime.date | pandas.Series[dt.date]

Date when the maximum drawdown occurred

-
-
-
-
- -
-
-OpenTimeSeries.periods_in_a_year
-

The average number of observations per year.

-
-
Returns:
-

The average number of observations per year.

-
-
-
- -
-
-OpenTimeSeries.yearfrac
-

Length of series in years assuming 365.25 days per year.

-
-
Returns:
-

Length of the timeseries in years assuming 365.25 days per year.

-
-
-
- -
-
-

Financial Metrics

-
-
-OpenTimeSeries.all_properties = <function OpenTimeSeries.all_properties>[source]
-
-
Parameters:
-
    -
  • self (Self)

  • -
  • properties (list[Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown_cal_year', 'max_drawdown', 'max_drawdown_date', 'first_idx', 'last_idx', 'length', 'span_of_days', 'yearfrac', 'periods_in_a_year', 'autocorr', 'partial_autocorr']] | None)

  • -
-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenTimeSeries.arithmetic_ret
-

Annualized arithmetic mean of returns.

-

Reference: https://www.investopedia.com/terms/a/arithmeticmean.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Annualized arithmetic mean of returns. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.geo_ret
-

Compounded Annual Growth Rate (CAGR).

-

Reference: https://www.investopedia.com/terms/c/cagr.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Compounded Annual Growth Rate (CAGR). -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.value_ret
-

Simple return.

-
-

Returns:

-
-
SeriesOrFloat_co

Simple return. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.vol
-

Annualized volatility.

-

Based on Pandas .std() which is the equivalent of stdev.s([…]) in MS Excel.

-

Reference: https://www.investopedia.com/terms/v/volatility.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Annualized volatility. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.downside_deviation
-

Downside Deviation.

-

Standard deviation of returns that are below a Minimum Accepted Return -of zero. It is used to calculate the Sortino Ratio.

-

Reference: https://www.investopedia.com/terms/d/downside-deviation.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Downside deviation. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.ret_vol_ratio
-

Ratio of annualized arithmetic mean of returns and annualized volatility.

-
-

Returns:

-
-
SeriesOrFloat_co

Ratio of the annualized arithmetic mean of returns and annualized -volatility. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.sortino_ratio
-

Sortino ratio.

-

Reference: https://www.investopedia.com/terms/s/sortinoratio.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Sortino ratio calculated as the annualized arithmetic mean of returns -/ downside deviation. The ratio implies that the riskfree asset has zero -volatility, and a minimum acceptable return of zero. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.kappa3_ratio
-

Kappa-3 ratio.

-

The Kappa-3 ratio is a generalized downside-risk ratio defined as -annualized arithmetic return divided by the cubic-root of the -lower partial moment of order 3 (with respect to a minimum acceptable -return, MAR). It penalizes larger downside outcomes more heavily than -the Sortino ratio (which uses order 2).

-
-

Returns:

-
-
SeriesOrFloat_co

Kappa-3 ratio calculation with the riskfree rate and. -Minimum Acceptable Return (MAR) both set to zero. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.omega_ratio
-

Omega ratio.

-

Reference: https://en.wikipedia.org/wiki/Omega_ratio.

-
-

Returns:

-
-
SeriesOrFloat_co

Omega ratio calculation. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.var_down
-

Downside 95% Value At Risk (VaR).

-

The equivalent of percentile.inc([…], 1-level) over returns in MS Excel. -https://www.investopedia.com/terms/v/var.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Downside 95% Value At Risk (VaR). -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.cvar_down
-

Downside 95% Conditional Value At Risk “CVaR”.

-

Reference: https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Downside 95% Conditional Value At Risk “CVaR”. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.worst
-

Most negative percentage change.

-
-

Returns:

-
-
SeriesOrFloat_co

Most negative percentage change. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.worst_month
-

Most negative month.

-
-

Returns:

-
-
SeriesOrFloat_co

Most negative month. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.max_drawdown
-

Maximum drawdown without any limit on date range.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Maximum drawdown without any limit on date range. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.max_drawdown_cal_year
-

Maximum drawdown in a single calendar year.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Maximum drawdown in a single calendar year. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.positive_share
-

The share of percentage changes that are greater than zero.

-
-

Returns:

-
-
SeriesOrFloat_co

The share of percentage changes that are greater than zero. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.vol_from_var
-

Implied annualized volatility from Downside 95% Value at Risk.

-

Assumes that returns are normally distributed.

-
-

Returns:

-
-
SeriesOrFloat_co

Implied annualized volatility from the Downside 95% VaR using the -assumption that returns are normally distributed. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.autocorr
-

Autocorrelation at lag 1.

-

Shorthand for autocorr_func(lag=1). Returns the lag-1 autocorrelation -of demeaned returns. For price series, returns are computed via -pct_change; for return series, raw values are used after demeaning.

-
-

Returns:

-
-
SeriesOrFloat_co

Autocorrelation at lag 1. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.skew
-

Skew of the return distribution.

-

Reference: https://www.investopedia.com/terms/s/skewness.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Skew of the return distribution. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.kurtosis
-

Kurtosis of the return distribution.

-

Reference: https://www.investopedia.com/terms/k/kurtosis.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Kurtosis of the return distribution. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-OpenTimeSeries.z_score
-

Z-score.

-

Reference: https://www.investopedia.com/terms/z/zscore.asp.

-
-

Returns:

-
-
SeriesOrFloat_co

Z-score as (last return - mean return) / standard deviation of returns. -Returns float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
-
-
- -
-
-
-

Methods

-
-

Data Manipulation

-
-
-OpenTimeSeries.pandas_df()[source]
-

Populate .tsdf Pandas DataFrame from the .dates and .values lists.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.set_new_label(lvl_zero=None, lvl_one=None, *, delete_lvl_one=False)[source]
-

Set the column labels of the .tsdf Pandas Dataframe.

-
-
Parameters:
-
    -
  • lvl_zero (str | None) – New level zero label. Optional.

  • -
  • lvl_one (ValueType | None) – New level one label. Optional.

  • -
  • delete_lvl_one (bool) – If True the level one label is deleted. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.running_adjustment(adjustment, days_in_year=365)[source]
-

Add or subtract a fee from the timeseries return.

-
-
Parameters:
-
    -
  • adjustment (float) – Fee to add or subtract.

  • -
  • days_in_year (int) – The calculation divisor and assumed number of days in a -calendar year. Defaults to 365.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.from_1d_rate_to_cumret(days_in_year=365, divider=1.0)[source]
-

Convert series of 1-day rates into series of cumulative values.

-
-
Parameters:
-
    -
  • days_in_year (int) – Calendar days per year used as divisor. Defaults to 365.

  • -
  • divider (float) – Convenience divider for when the 1-day rate is not scaled -correctly. Defaults to 1.0.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.align_index_to_local_cdays(countries=None, markets=None, custom_holidays=None, method='nearest')
-

Align the index of .tsdf with local calendar business days.

-
-
Parameters:
-
    -
  • countries (CountriesType | None) – Country code(s) (ISO 3166-1 alpha-2).

  • -
  • markets (list[str] | str | None) – Market code(s) supported by exchange_calendars.

  • -
  • custom_holidays (list[str] | str | None) – Missing holidays that should be added.

  • -
  • method (LiteralPandasReindexMethod) – Method for reindexing when aligning to business days.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The modified object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.resample(freq='BME')[source]
-

Resamples the timeseries frequency.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE'] | str) – The date offset string that sets the resampled frequency. -Defaults to “BME”.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.resample_to_business_period_ends(freq='BME', method='nearest')[source]
-

Resamples timeseries frequency to the business calendar month end dates.

-

Stubs left in place. Stubs will be aligned to the shortest stub.

-
-
Parameters:
-
    -
  • freq (Literal['B', 'BME', 'BQE', 'BYE']) – The date offset string that sets the resampled frequency. -Defaults to BME.

  • -
  • method (Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None) – Controls the method used to align values across columns. -Defaults to nearest.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Raises:
-

ResampleDataLossError – If called on a return series (valuetype is - ValueType.RTRN), since summation across sparser frequency would - be required to avoid data loss.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.value_nan_handle(method='fill')
-

Handle missing values in a value series.

-
-
Parameters:
-
    -
  • method (LiteralNanMethod) – Method used to handle NaN. Either "fill" (last known) or -"drop".

  • -
  • self (Self)

  • -
-
-
Returns:
-

The modified object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.return_nan_handle(method='fill')
-

Handle missing values in a return series.

-
-
Parameters:
-
    -
  • method (LiteralNanMethod) – Method used to handle NaN. Either "fill" (zero) or -"drop".

  • -
  • self (Self)

  • -
-
-
Returns:
-

The modified object.

-
-
Return type:
-

Self

-
-
-
- -
-
-

Transformations

-
-
-OpenTimeSeries.to_cumret()[source]
-

Convert series of returns into cumulative series of values.

-
-
Returns:
-

An OpenTimeSeries object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.value_to_ret()[source]
-

Convert series of values into series of returns.

-
-
Returns:
-

The returns of the values in the series.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.value_to_diff(periods=1)[source]
-

Convert series of values to series of their period differences.

-
-
Parameters:
-
    -
  • periods (int) – The number of periods between observations over which difference -is calculated. Defaults to 1.

  • -
  • self (Self)

  • -
-
-
Returns:
-

An OpenTimeSeries object.

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.value_to_log()
-

Convert value series to log-weighted series.

-

Equivalent to LN(value[t] / value[t=0]) in Excel.

-
-
Returns:
-

The modified object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-OpenTimeSeries.to_drawdown_series()
-

Convert timeseries into a drawdown series.

-
-
Returns:
-

The modified object.

-
-
Parameters:
-

self (Self)

-
-
Return type:
-

Self

-
-
-
- -
-
-

Analysis Methods

-

Autocorrelation analysis: autocorr (property) and autocorr_func provide -lag-N autocorrelation; acf, pacf, partial_autocorr, and ljung_box -support full autocorrelation diagnostics. Available on both OpenTimeSeries and -OpenFrame (except acf, pacf, partial_autocorr, ljung_box which are -OpenTimeSeries-only).

-
-
-OpenTimeSeries.autocorr_func(lag=1, *, squared=False)
-

Calculate autocorrelation at a given lag.

-

Computes the autocorrelation of demeaned returns at the specified lag. -For price series (ValueType.PRICE), returns are derived via pct_change; -for return series (ValueType.RTRN), raw values are demeaned. Use -squared=True for squared-return autocorrelation (e.g. volatility -clustering). Returns nan when the series has too few observations.

-
-
Parameters:
-
    -
  • lag (int) – The lag at which to compute autocorrelation. Defaults to 1.

  • -
  • squared (bool) – If True, compute autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Autocorrelation at the specified lag. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.acf(lags, *, squared=False)[source]
-

Calculate autocorrelation function for specified lags.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, compute ACF from lag 0 to this value (inclusive). -If list, compute ACF at lag 0 plus each lag in the list.

  • -
  • squared (bool) – If True, compute ACF of squared returns. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of autocorrelations indexed by lag.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-OpenTimeSeries.partial_autocorr(lag=1, *, squared=False)[source]
-

Calculate partial autocorrelation at a given lag.

-
-
Parameters:
-
    -
  • lag (int) – The lag at which to compute partial autocorrelation. Defaults to 1.

  • -
  • squared (bool) – If True, compute partial autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Partial autocorrelation at the specified lag.

-
-
Return type:
-

float

-
-
-
- -
-
-OpenTimeSeries.pacf(lags, *, squared=False)[source]
-

Calculate partial autocorrelation function for specified lags.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, compute PACF from lag 0 to this value (inclusive). -If list, compute PACF at lag 0 plus each lag in the list.

  • -
  • squared (bool) – If True, compute PACF of squared returns. Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of partial autocorrelations indexed by lag.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-OpenTimeSeries.ljung_box(lags, *, squared=False)[source]
-

Compute Ljung-Box test for autocorrelation.

-
-
Parameters:
-
    -
  • lags (int | list[int]) – If int, use lags 1 through this value. If list, use the given -lags (lag 0 excluded from test).

  • -
  • squared (bool) – If True, test autocorrelation of squared returns. -Defaults to False.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Tuple of (statistic, pvalue, lags) where statistic is the Ljung-Box -Q statistic, pvalue is the chi-squared p-value, and lags is the -list of lags used.

-
-
Return type:
-

tuple[float, float, list[int]]

-
-
-
- -
-
-OpenTimeSeries.ewma_vol_func(lmbda=0.94, day_chunk=11, dlta_degr_freedms=0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)[source]
-

Exponentially Weighted Moving Average Model for Volatility.

-

Reference: https://www.investopedia.com/articles/07/ewma.asp.

-
-
Parameters:
-
    -
  • lmbda (float) – Scaling factor to determine weighting. Defaults to 0.94.

  • -
  • day_chunk (int) – Sampling the data which is assumed to be daily. -Defaults to 11.

  • -
  • dlta_degr_freedms (int) – Variance bias factor taking the value 0 or 1. -Defaults to 0.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. -Overrides use of from_date and to_date. Optional.

  • -
  • from_date (dt.date | None) – Specific from date. Optional.

  • -
  • to_date (dt.date | None) – Specific to date. Optional.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Allows locking the periods-in-a-year to simplify -test cases and comparisons. Optional.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series EWMA volatility.

-
-
Return type:
-

Series[float]

-
-
-
- -
-
-OpenTimeSeries.value_ret_calendar_period(year, month=None)
-

Calculate simple return for a specific calendar period.

-
-
Parameters:
-
    -
  • year (int) – Calendar year of the period to calculate.

  • -
  • month (int | None) – Calendar month of the period to calculate.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Simple return for the period. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.rolling_return(column=0, observations=21)
-

Calculate rolling returns.

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling returns.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenTimeSeries.rolling_vol(column=0, observations=21, periods_in_a_year_fixed=None, dlta_degr_freedms=1)
-

Calculate rolling annualized volatilities.

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • dlta_degr_freedms (int) – Variance bias factor (0 or 1).

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling annualized volatilities.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenTimeSeries.rolling_var_down(column=0, level=0.95, observations=252, interpolation='lower')
-

Calculate rolling annualized downside Value At Risk (VaR).

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • level (float) – Value At Risk level.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation used by DataFrame.quantile.

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling annualized downside VaR.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenTimeSeries.rolling_cvar_down(column=0, level=0.95, observations=252)
-

Calculate rolling annualized downside CVaR.

-
-
Parameters:
-
    -
  • column (int) – Column position to calculate.

  • -
  • level (float) – Conditional Value At Risk level.

  • -
  • observations (int) – Number of observations in the overlapping window.

  • -
  • self (Self)

  • -
-
-
Returns:
-

DataFrame with rolling annualized downside CVaR.

-
-
Return type:
-

DataFrame

-
-
-
- -
-
-OpenTimeSeries.calc_range(months_offset=None, from_dt=None, to_dt=None)
-

Create a user-defined date range aligned to index.

-
-
Parameters:
-
    -
  • months_offset (int | None) – Number of months offset as a positive integer. Overrides -use of from_dt and to_dt.

  • -
  • from_dt (date | None) – Specific from date.

  • -
  • to_dt (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (earlier, later) representing the start and end date of the -chosen date range aligned to existing index values.

-
-
Raises:
-

DateAlignmentError – If the implied range is outside series bounds.

-
-
Return type:
-

tuple[date, date]

-
-
-
- -
-
-OpenTimeSeries.outliers(threshold=3.0, months_from_last=None, from_date=None, to_date=None)
-

Detect outliers using z-score analysis.

-

Identifies data points where the absolute z-score exceeds the threshold. -For OpenTimeSeries, returns a Series with dates and outlier values. For -OpenFrame, returns a DataFrame with dates and outlier values for each -column.

-
-
Parameters:
-
    -
  • threshold (float) – Z-score threshold; values with |z| > threshold are -outliers.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Series of outliers. For OpenFrame: DataFrame of -outliers. Empty if none found.

-
-
Return type:
-

For OpenTimeSeries

-
-
-
- -
-
-

Financial Metrics Methods

-
-
-OpenTimeSeries.arithmetic_ret_func(months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Annualized arithmetic mean of returns.

-

Reference: https://www.investopedia.com/terms/a/arithmeticmean.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Annualized arithmetic mean of returns. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.geo_ret_func(months_from_last=None, from_date=None, to_date=None)
-

Compounded Annual Growth Rate (CAGR).

-

Reference: https://www.investopedia.com/terms/c/cagr.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

CAGR. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Raises:
-

InitialValueZeroError – If initial value is zero or there are negative - values.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.value_ret_func(months_from_last=None, from_date=None, to_date=None)
-

Calculate simple return.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Simple return. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Raises:
-

InitialValueZeroError – If initial value is zero.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.vol_func(months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Annualized volatility.

-

Based on pandas.Series.std() (Excel STDEV.S equivalent). -Reference: https://www.investopedia.com/terms/v/volatility.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Annualized volatility. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.lower_partial_moment_func(min_accepted_return=0.0, order=2, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Lower partial moment and downside deviation (order=2).

-

If order is 2 calculates standard deviation of returns below MAR=0. -For general order p, returns (LPM_p)^(1/p).

-
-
Parameters:
-
    -
  • min_accepted_return (float) – Annualized Minimum Accepted Return (MAR).

  • -
  • order (Literal[2, 3]) – Order of partial moment (2 or 3).

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Downside deviation if order is 2; otherwise rooted lower partial -moment. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Raises:
-

ValueError – If order is not 2 or 3.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.ret_vol_ratio_func(riskfree_rate=0.0, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Ratio between arithmetic mean of returns and annualized volatility.

-

If riskfree_rate provided, computes the Sharpe ratio as -(arithmetic return - risk-free) / volatility. Assumes zero volatility -for the risk-free asset. Reference: -https://www.investopedia.com/terms/s/sharperatio.asp.

-
-
Parameters:
-
    -
  • riskfree_rate (float) – Return of the zero volatility asset.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Ratio value. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.sortino_ratio_func(riskfree_rate=0.0, min_accepted_return=0.0, order=2, months_from_last=None, from_date=None, to_date=None, periods_in_a_year_fixed=None)
-

Sortino ratio or Kappa-3 ratio.

-

Sortino: (return - riskfree_rate) / downside deviation using arithmetic -mean of returns. Kappa-3 when order=3 penalizes larger downside more -than Sortino.

-
-
Parameters:
-
    -
  • riskfree_rate (float) – Return of the zero volatility asset.

  • -
  • min_accepted_return (float) – Annualized Minimum Accepted Return (MAR).

  • -
  • order (Literal[2, 3]) – Order of partial moment (2 for Sortino, 3 for Kappa-3).

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Ratio value. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.omega_ratio_func(min_accepted_return=0.0, months_from_last=None, from_date=None, to_date=None)
-

Omega Ratio.

-

Compares returns above MAR to the total downside risk below MAR. -Reference: https://en.wikipedia.org/wiki/Omega_ratio.

-
-
Parameters:
-
    -
  • min_accepted_return (float) – Annualized Minimum Accepted Return (MAR).

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Omega ratio. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.var_down_func(level=0.95, months_from_last=None, from_date=None, to_date=None, interpolation='lower')
-

Downside Value At Risk (VaR).

-

Equivalent to PERCENTILE.INC(returns, 1-level) in Excel. Reference: -https://www.investopedia.com/terms/v/var.asp.

-
-
Parameters:
-
    -
  • level (float) – The sought VaR level.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation used by DataFrame.quantile.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Downside VaR. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.cvar_down_func(level=0.95, months_from_last=None, from_date=None, to_date=None)
-

Downside Conditional Value At Risk (CVaR).

-

Reference: https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.

-
-
Parameters:
-
    -
  • level (float) – The sought CVaR level.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Downside CVaR. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.worst_func(observations=1, months_from_last=None, from_date=None, to_date=None)
-

Most negative percentage change over a rolling window.

-
-
Parameters:
-
    -
  • observations (int) – Number of observations for the rolling window.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Most negative percentage change. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.max_drawdown_func(months_from_last=None, from_date=None, to_date=None, min_periods=1)
-

Maximum drawdown without any limit on date range.

-

Reference: https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • min_periods (int) – Smallest number of observations for rolling max.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Maximum drawdown. Float for OpenTimeSeries, Series[float] for -OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.positive_share_func(months_from_last=None, from_date=None, to_date=None)
-

Share of percentage changes greater than zero.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Share of positive returns. Float for OpenTimeSeries, Series[float] -for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.vol_from_var_func(level=0.95, months_from_last=None, from_date=None, to_date=None, interpolation='lower', periods_in_a_year_fixed=None, *, drift_adjust=False)
-

Implied annualized volatility from downside VaR.

-

Assumes normally distributed returns.

-
-
Parameters:
-
    -
  • level (float) – The sought VaR level.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation type used by DataFrame.quantile.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • drift_adjust (bool) – Adjustment to remove the bias implied by the average -return.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Implied annualized volatility. Float for OpenTimeSeries, -Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.skew_func(months_from_last=None, from_date=None, to_date=None)
-

Skew of the return distribution.

-

Reference: https://www.investopedia.com/terms/s/skewness.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Skewness. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.kurtosis_func(months_from_last=None, from_date=None, to_date=None)
-

Kurtosis of the return distribution.

-

Reference: https://www.investopedia.com/terms/k/kurtosis.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Kurtosis. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.z_score_func(months_from_last=None, from_date=None, to_date=None)
-

Z-score of the last return.

-

Computed as (last return - mean return) / std dev of returns. -Reference: https://www.investopedia.com/terms/z/zscore.asp.

-
-
Parameters:
-
    -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (date | None) – Specific from date.

  • -
  • to_date (date | None) – Specific to date.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Z-score. Float for OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-OpenTimeSeries.target_weight_from_var(target_vol=0.175, level=0.95, min_leverage_local=0.0, max_leverage_local=99999.0, months_from_last=None, from_date=None, to_date=None, interpolation='lower', periods_in_a_year_fixed=None, *, drift_adjust=False)
-

Target weight from VaR.

-

Computes a position weight multiplier from the ratio between a VaR implied -volatility and a given target volatility. Multiplier = 1.0 → target met.

-
-
Parameters:
-
    -
  • target_vol (float) – Target volatility.

  • -
  • level (float) – The sought VaR level.

  • -
  • min_leverage_local (float) – Minimum adjustment factor.

  • -
  • max_leverage_local (float) – Maximum adjustment factor.

  • -
  • months_from_last (int | None) – Number of months offset as positive integer. Overrides -use of from_date and to_date.

  • -
  • from_date (dt.date | None) – Specific from date.

  • -
  • to_date (dt.date | None) – Specific to date.

  • -
  • interpolation (LiteralQuantileInterp) – Interpolation type used by DataFrame.quantile.

  • -
  • periods_in_a_year_fixed (DaysInYearType | None) – Lock periods-in-a-year to simplify tests and -comparisons.

  • -
  • drift_adjust (bool) – Adjustment to remove the bias implied by the average -return.

  • -
  • self (Self)

  • -
-
-
Returns:
-

Weight multiplier (or implied volatility if used downstream). Float for -OpenTimeSeries, Series[float] for OpenFrame.

-
-
Return type:
-

SeriesOrFloat_co

-
-
-
- -
-
-

Visualization

-

The plotting methods generate fully responsive HTML output that automatically adapts to different screen sizes and device orientations. Plots are optimized for both desktop and mobile viewing with separate title containers and responsive CSS styling.

-
-
-OpenTimeSeries.plot_series(mode='lines', title=None, tick_fmt=None, filename=None, directory=None, labels=None, output_type='file', include_plotlyjs='cdn', *, auto_open=True, add_logo=True, show_last=False)
-

Create a Plotly Scatter Figure.

-
-
Parameters:
-
    -
  • mode (LiteralLinePlotMode) – The type of scatter to use.

  • -
  • title (str | None) – A title above the plot.

  • -
  • tick_fmt (str | None) – Tick format for the y-axis, e.g. '%' or '.1%'.

  • -
  • filename (str | None) – Name of the Plotly HTML file.

  • -
  • directory (DirectoryPath | None) – Directory where the Plotly HTML file is saved.

  • -
  • labels (list[str] | None) – Labels to override the column names of self.tsdf.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – How the plotly.js library is included.

  • -
  • auto_open (bool) – Whether to open a browser window with the plot.

  • -
  • add_logo (bool) – If True, a Captor logo is added to the plot.

  • -
  • show_last (bool) – If True, highlight the last point in red with a label.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (figure, output) where output is either a div string or -a file path.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
-OpenTimeSeries.plot_bars(mode='group', title=None, tick_fmt=None, filename=None, directory=None, labels=None, output_type='file', include_plotlyjs='cdn', *, auto_open=True, add_logo=True)
-

Create a Plotly Bar Figure.

-
-
Parameters:
-
    -
  • mode (LiteralBarPlotMode) – The type of bar to use.

  • -
  • title (str | None) – A title above the plot.

  • -
  • tick_fmt (str | None) – Tick format for the y-axis, e.g. '%' or '.1%'.

  • -
  • filename (str | None) – Name of the Plotly HTML file.

  • -
  • directory (DirectoryPath | None) – Directory where the Plotly HTML file is saved.

  • -
  • labels (list[str] | None) – Labels to override the column names of self.tsdf.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – How the plotly.js library is included.

  • -
  • auto_open (bool) – Whether to open a browser window with the plot.

  • -
  • add_logo (bool) – If True, a Captor logo is added to the plot.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (figure, output) where output is either a div string or -a file path.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
-OpenTimeSeries.plot_histogram(plot_type='bars', histnorm='probability', barmode='overlay', xbins_size=None, opacity=0.75, bargap=0.0, bargroupgap=0.0, curve_type='kde', title=None, x_fmt=None, y_fmt=None, filename=None, directory=None, labels=None, output_type='file', include_plotlyjs='cdn', *, cumulative=False, show_rug=False, auto_open=True, add_logo=True)
-

Create a Plotly Histogram Figure.

-
-
Parameters:
-
    -
  • plot_type (LiteralPlotlyHistogramPlotType) – Type of plot, "bars" or "lines".

  • -
  • histnorm (LiteralPlotlyHistogramHistNorm) – Normalization mode.

  • -
  • barmode (LiteralPlotlyHistogramBarMode) – How bar traces are displayed relative to one another.

  • -
  • xbins_size (float | None) – Width of each bin along the x-axis in data units.

  • -
  • opacity (float) – Trace opacity between 0 and 1.

  • -
  • bargap (float) – Gap between bars of adjacent location coordinates.

  • -
  • bargroupgap (float) – Gap between bar groups at the same location coordinate.

  • -
  • curve_type (LiteralPlotlyHistogramCurveType) – Type of distribution curve to overlay on the histogram.

  • -
  • title (str | None) – A title above the plot.

  • -
  • x_fmt (str | None) – Tick format for the x-axis.

  • -
  • y_fmt (str | None) – Tick format for the y-axis.

  • -
  • filename (str | None) – Name of the Plotly HTML file.

  • -
  • directory (DirectoryPath | None) – Directory where the Plotly HTML file is saved.

  • -
  • labels (list[str] | None) – Labels to override the column names of self.tsdf.

  • -
  • output_type (LiteralPlotlyOutput) – Determines output type.

  • -
  • include_plotlyjs (LiteralPlotlyJSlib) – How the plotly.js library is included.

  • -
  • cumulative (bool) – Whether to compute a cumulative histogram.

  • -
  • show_rug (bool) – Whether to draw a rug plot alongside the distribution.

  • -
  • auto_open (bool) – Whether to open a browser window with the plot.

  • -
  • add_logo (bool) – If True, a Captor logo is added to the plot.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A tuple (figure, output) where output is either a div string or -a file path.

-
-
Return type:
-

tuple[Figure, str]

-
-
-
- -
-
-

Export Methods

-
-
-OpenTimeSeries.to_json(what_output, filename, directory=None)
-

Dump timeseries data into a JSON file.

-
-
Parameters:
-
    -
  • what_output (LiteralJsonOutput) – Whether to export raw values or tsdf values.

  • -
  • filename (str | Path) – Filename including extension.

  • -
  • directory (DirectoryPath | Path | str | None) – Folder where the file will be written.

  • -
  • self (Self)

  • -
-
-
Returns:
-

A list of dictionaries with the data of the series.

-
-
Return type:
-

list[dict[str, str | bool | ValueType | list[str] | list[float]]]

-
-
-
- -
-
-OpenTimeSeries.to_xlsx(filename, sheet_title=None, directory=None, *, overwrite=True)
-

Save .tsdf DataFrame to an Excel spreadsheet file.

-
-
Parameters:
-
    -
  • filename (str) – Filename that should include .xlsx.

  • -
  • sheet_title (str | None) – Name of the sheet in the Excel file.

  • -
  • directory (Annotated[Path, PathType(path_type=dir)] | None) – Directory where the Excel file is saved.

  • -
  • overwrite (bool) – Whether to overwrite an existing file.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The Excel file path.

-
-
Raises:
-
-
-
Return type:
-

str

-
-
-
- -
-
-
-

Utility Functions

-
-
-openseries.timeseries_chain(front, back, old_fee=0.0)[source]
-

Chain two timeseries together.

-

The function assumes that the two series have at least one date in common.

-
-
Parameters:
-
    -
  • front (TypeOpenTimeSeries) – Earlier series to chain with.

  • -
  • back (TypeOpenTimeSeries) – Later series to chain with.

  • -
  • old_fee (float) – Fee to apply to earlier series. Defaults to 0.0.

  • -
-
-
Returns:
-

An OpenTimeSeries object or a subclass thereof.

-
-
Return type:
-

TypeOpenTimeSeries

-
-
-
- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/simulation.html b/docs/build/html/api/simulation.html deleted file mode 100644 index 13ded9cc..00000000 --- a/docs/build/html/api/simulation.html +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - - - - Simulation — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Simulation

-

The ReturnSimulation class.

-
-
-class openseries.simulation.ReturnSimulation(*, number_of_sims, trading_days, trading_days_in_year, mean_annual_return, mean_annual_vol, dframe, jumps_lamda=0.0, jumps_sigma=0.0, jumps_mu=0.0, seed=None)[source]
-

Bases: BaseModel

-

The class ReturnSimulation allows for simulating financial timeseries.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Total number of days to simulate.

  • -
  • trading_days_in_year (Annotated[int, Strict(strict=True), Ge(ge=1), Le(le=366)]) – Number of trading days used to annualize.

  • -
  • mean_annual_return (float) – Mean annual return of the distribution.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean annual standard deviation of the distribution.

  • -
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • -
  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point in time. -Defaults to 0.0.

  • -
  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • -
  • jumps_mu (float) – This is the average jump size. Defaults to 0.0.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
-
-
-
-
-number_of_sims: PositiveInt
-
- -
-
-trading_days: PositiveInt
-
- -
-
-trading_days_in_year: DaysInYearType
-
- -
-
-mean_annual_return: float
-
- -
-
-mean_annual_vol: PositiveFloat
-
- -
-
-dframe: DataFrame
-
- -
-
-jumps_lamda: NonNegativeFloat
-
- -
-
-jumps_sigma: NonNegativeFloat
-
- -
-
-jumps_mu: float
-
- -
-
-seed: int | None
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-property results: DataFrame[source]
-

Simulation data.

-
-
Returns:
-

Simulation data.

-
-
-
- -
-
-property realized_mean_return: float
-

Annualized arithmetic mean of returns.

-
-
Returns:
-

Annualized arithmetic mean of returns.

-
-
-
- -
-
-property realized_vol: float
-

Annualized volatility.

-
-
Returns:
-

Annualized volatility.

-
-
-
- -
-
-classmethod from_normal(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Normal distribution simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Normal distribution simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_lognormal(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Lognormal distribution simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Lognormal distribution simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_gbm(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Geometric Brownian Motion simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Geometric Brownian Motion simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_merton_jump_gbm(number_of_sims, trading_days, mean_annual_return, mean_annual_vol, jumps_lamda, jumps_sigma=0.0, jumps_mu=0.0, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Merton Jump-Diffusion model simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point -in time.

  • -
  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • -
  • jumps_mu (float) – This is the average jump size. Defaults to 0.0.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Merton Jump-Diffusion model simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-to_dataframe(name, start=None, end=None, countries='SE', markets=None)[source]
-

Create a pandas.DataFrame from simulation(s).

-
-
Parameters:
-
    -
  • name (str) – Name label of the serie(s).

  • -
  • start (dt.date | None) – Date when the simulation starts.

  • -
  • end (dt.date | None) – Date when the simulation ends.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The simulation(s) data.

-
-
Return type:
-

DataFrame

-
-
-
- -
- -
-

ReturnSimulation Class

-
-
-class openseries.simulation.ReturnSimulation(*, number_of_sims, trading_days, trading_days_in_year, mean_annual_return, mean_annual_vol, dframe, jumps_lamda=0.0, jumps_sigma=0.0, jumps_mu=0.0, seed=None)[source]
-

Bases: BaseModel

-

The class ReturnSimulation allows for simulating financial timeseries.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Total number of days to simulate.

  • -
  • trading_days_in_year (Annotated[int, Strict(strict=True), Ge(ge=1), Le(le=366)]) – Number of trading days used to annualize.

  • -
  • mean_annual_return (float) – Mean annual return of the distribution.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean annual standard deviation of the distribution.

  • -
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • -
  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point in time. -Defaults to 0.0.

  • -
  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • -
  • jumps_mu (float) – This is the average jump size. Defaults to 0.0.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
-
-
-
-
-number_of_sims: PositiveInt
-
- -
-
-trading_days: PositiveInt
-
- -
-
-trading_days_in_year: DaysInYearType
-
- -
-
-mean_annual_return: float
-
- -
-
-mean_annual_vol: PositiveFloat
-
- -
-
-dframe: DataFrame
-
- -
-
-jumps_lamda: NonNegativeFloat
-
- -
-
-jumps_sigma: NonNegativeFloat
-
- -
-
-jumps_mu: float
-
- -
-
-seed: int | None
-
- -
-
-model_config = {'arbitrary_types_allowed': True, 'revalidate_instances': 'always', 'validate_assignment': True}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
-
-property results: DataFrame[source]
-

Simulation data.

-
-
Returns:
-

Simulation data.

-
-
-
- -
-
-property realized_mean_return: float
-

Annualized arithmetic mean of returns.

-
-
Returns:
-

Annualized arithmetic mean of returns.

-
-
-
- -
-
-property realized_vol: float
-

Annualized volatility.

-
-
Returns:
-

Annualized volatility.

-
-
-
- -
-
-classmethod from_normal(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Normal distribution simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Normal distribution simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_lognormal(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Lognormal distribution simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Lognormal distribution simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_gbm(number_of_sims, mean_annual_return, mean_annual_vol, trading_days, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Geometric Brownian Motion simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Geometric Brownian Motion simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-classmethod from_merton_jump_gbm(number_of_sims, trading_days, mean_annual_return, mean_annual_vol, jumps_lamda, jumps_sigma=0.0, jumps_mu=0.0, trading_days_in_year=252, seed=None, randomizer=None, ar1_coef=0.0)[source]
-

Create a Merton Jump-Diffusion model simulation.

-
-
Parameters:
-
    -
  • number_of_sims (Annotated[int, Gt(gt=0)]) – Number of simulations to generate.

  • -
  • trading_days (Annotated[int, Gt(gt=0)]) – Number of trading days to simulate.

  • -
  • mean_annual_return (float) – Mean return.

  • -
  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean standard deviation.

  • -
  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point -in time.

  • -
  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • -
  • jumps_mu (float) – This is the average jump size. Defaults to 0.0.

  • -
  • trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. -Defaults to 252.

  • -
  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • -
  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce -autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • -
-
-
Returns:
-

Merton Jump-Diffusion model simulation.

-
-
Return type:
-

ReturnSimulation

-
-
-
- -
-
-to_dataframe(name, start=None, end=None, countries='SE', markets=None)[source]
-

Create a pandas.DataFrame from simulation(s).

-
-
Parameters:
-
    -
  • name (str) – Name label of the serie(s).

  • -
  • start (dt.date | None) – Date when the simulation starts.

  • -
  • end (dt.date | None) – Date when the simulation ends.

  • -
  • countries (CountriesType) – (List of) country code(s) according to ISO 3166-1 alpha-2. -Defaults to “SE”.

  • -
  • markets (list[str] | str | None) – (List of) markets code(s) supported by exchange_calendars.

  • -
  • self (Self)

  • -
-
-
Returns:
-

The simulation(s) data.

-
-
Return type:
-

DataFrame

-
-
-
- -
- -

The ReturnSimulation class is used to create simulated financial time series for testing and analysis purposes. It provides methods to generate realistic return patterns based on statistical distributions.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/api/types.html b/docs/build/html/api/types.html deleted file mode 100644 index 3b1273d8..00000000 --- a/docs/build/html/api/types.html +++ /dev/null @@ -1,812 +0,0 @@ - - - - - - - - - Types and Enums — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Types and Enums

-

Declaring types used throughout the project.

-
-
-class openseries.owntypes.ValueType(*values)[source]
-

Bases: StrEnum

-

Enum types of OpenTimeSeries to identify the output.

-
-
-EWMA_VOL = 'EWMA volatility'
-
- -
-
-EWMA_VAR = 'EWMA VaR'
-
- -
-
-PRICE = 'Price(Close)'
-
- -
-
-RTRN = 'Return(Total)'
-
- -
-
-RELRTRN = 'Relative return'
-
- -
-
-ROLLBETA = 'Beta'
-
- -
-
-ROLLCORR = 'Rolling correlation'
-
- -
-
-ROLLCVAR = 'Rolling CVaR'
-
- -
-
-ROLLINFORATIO = 'Information Ratio'
-
- -
-
-ROLLRTRN = 'Rolling returns'
-
- -
-
-ROLLVAR = 'Rolling VaR'
-
- -
-
-ROLLVOL = 'Rolling volatility'
-
- -
- -
-

Value Types

-
-
-class openseries.owntypes.ValueType(*values)[source]
-

Bases: StrEnum

-

Enum types of OpenTimeSeries to identify the output.

-
-
-EWMA_VOL = 'EWMA volatility'
-
- -
-
-EWMA_VAR = 'EWMA VaR'
-
- -
-
-PRICE = 'Price(Close)'
-
- -
-
-RTRN = 'Return(Total)'
-
- -
-
-RELRTRN = 'Relative return'
-
- -
-
-ROLLBETA = 'Beta'
-
- -
-
-ROLLCORR = 'Rolling correlation'
-
- -
-
-ROLLCVAR = 'Rolling CVaR'
-
- -
-
-ROLLINFORATIO = 'Information Ratio'
-
- -
-
-ROLLRTRN = 'Rolling returns'
-
- -
-
-ROLLVAR = 'Rolling VaR'
-
- -
-
-ROLLVOL = 'Rolling volatility'
-
- -
- -

The ValueType enum identifies the type of values in a time series (prices, returns, etc.).

-
-
-

Type Aliases

-
-
-openseries.owntypes.SeriesOrFloat_co = +SeriesOrFloat_co
-

Type variable.

-

The preferred way to construct a type variable is via the dedicated -syntax for generic functions, classes, and type aliases:

-
class Sequence[T]:  # T is a TypeVar
-    ...
-
-
-

This syntax can also be used to create bound and constrained type -variables:

-
# S is a TypeVar bound to str
-class StrSequence[S: str]:
-    ...
-
-# A is a TypeVar constrained to str or bytes
-class StrOrBytesSequence[A: (str, bytes)]:
-    ...
-
-
-

Type variables can also have defaults:

-
-
-
class IntDefault[T = int]:

-
-
-
-

However, if desired, reusable type variables can also be constructed -manually, like so:

-
T = TypeVar('T')  # Can be anything
-S = TypeVar('S', bound=str)  # Can be any subtype of str
-A = TypeVar('A', str, bytes)  # Must be exactly str or bytes
-D = TypeVar('D', default=int)  # Defaults to int
-
-
-

Type variables exist primarily for the benefit of static type -checkers. They serve as the parameters for generic types as well -as for generic function and type alias definitions.

-

The variance of type variables is inferred by type checkers when they -are created through the type parameter syntax and when -infer_variance=True is passed. Manually created type variables may -be explicitly marked covariant or contravariant by passing -covariant=True or contravariant=True. By default, manually -created type variables are invariant. See PEP 484 and PEP 695 for more -details.

-
- -
-
-openseries.owntypes.CountryStringType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]

-
- -
-
-openseries.owntypes.CountrySetType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[set[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]], MinLen(min_length=1)]

-
- -
-
-openseries.owntypes.CountriesType: TypeAlias = typing.Annotated[set[typing.Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern='^[A-Z]{2}$', ascii_only=None)]], MinLen(min_length=1)] | typing.Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern='^[A-Z]{2}$', ascii_only=None)]
-

Represent a union type

-

E.g. for int | str

-
- -
-
-openseries.owntypes.CurrencyStringType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]

-
- -
-
-openseries.owntypes.DateStringType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]d|3[01])$, ascii_only=None)]

-
- -
-
-openseries.owntypes.DateListType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[list[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=True, min_length=10, max_length=10, pattern=^d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]d|3[01])$, ascii_only=None)]], MinLen(min_length=1)]

-
- -
-
-openseries.owntypes.ValueListType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[list[float], MinLen(min_length=1)]

-
- -
-
-openseries.owntypes.DaysInYearType
-

Runtime representation of an annotated type.

-

At its core ‘Annotated[t, dec1, dec2, …]’ is an alias for the type ‘t’ -with extra metadata. The alias behaves like a normal typing alias. -Instantiating is the same as instantiating the underlying type; binding -it to types is also the same.

-

The metadata itself is stored in a ‘__metadata__’ attribute as a tuple.

-

alias of Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]

-
- -
-
-openseries.owntypes.DateType = str | datetime.date | datetime.datetime | numpy.datetime64 | pandas.Timestamp
-

Represent a union type

-

E.g. for int | str

-
- -
-
-

Literal Types

-
-
-openseries.owntypes.LiteralJsonOutput
-

alias of Literal[‘values’, ‘tsdf’]

-
- -
-
-openseries.owntypes.LiteralTrunc
-

alias of Literal[‘before’, ‘after’, ‘both’]

-
- -
-
-openseries.owntypes.LiteralLinePlotMode = typing.Literal['lines', 'markers', 'lines+markers', 'lines+text', 'markers+text', 'lines+markers+text'] | None
-

Represent a union type

-

E.g. for int | str

-
- -
-
-openseries.owntypes.LiteralHowMerge
-

alias of Literal[‘outer’, ‘inner’]

-
- -
-
-openseries.owntypes.LiteralQuantileInterp
-

alias of Literal[‘linear’, ‘lower’, ‘higher’, ‘midpoint’, ‘nearest’]

-
- -
-
-openseries.owntypes.LiteralBizDayFreq
-

alias of Literal[‘B’, ‘BME’, ‘BQE’, ‘BYE’]

-
- -
-
-openseries.owntypes.LiteralPandasReindexMethod = typing.Literal['pad', 'ffill', 'backfill', 'bfill', 'nearest'] | None
-

Represent a union type

-

E.g. for int | str

-
- -
-
-openseries.owntypes.LiteralNanMethod
-

alias of Literal[‘fill’, ‘drop’]

-
- -
-
-openseries.owntypes.LiteralCaptureRatio
-

alias of Literal[‘up’, ‘down’, ‘both’]

-
- -
-
-openseries.owntypes.LiteralBarPlotMode
-

alias of Literal[‘stack’, ‘group’, ‘overlay’, ‘relative’]

-
- -
-
-openseries.owntypes.LiteralPlotlyOutput
-

alias of Literal[‘file’, ‘div’]

-
- -
-
-openseries.owntypes.LiteralPlotlyJSlib
-

alias of Literal[True, False, ‘cdn’]

-
- -
-
-openseries.owntypes.LiteralPlotlyHistogramPlotType
-

alias of Literal[‘bars’, ‘lines’]

-
- -
-
-openseries.owntypes.LiteralPlotlyHistogramBarMode
-

alias of Literal[‘stack’, ‘group’, ‘overlay’, ‘relative’]

-
- -
-
-openseries.owntypes.LiteralPlotlyHistogramCurveType
-

alias of Literal[‘normal’, ‘kde’]

-
- -
-
-openseries.owntypes.LiteralPlotlyHistogramHistNorm
-

alias of Literal[‘percent’, ‘probability’, ‘density’, ‘probability density’]

-
- -
-
-openseries.owntypes.LiteralPortfolioWeightings
-

alias of Literal[‘eq_weights’, ‘inv_vol’, ‘max_div’, ‘min_vol_overweight’]

-
- -
-
-openseries.owntypes.LiteralMinimizeMethods
-

alias of Literal[‘SLSQP’, ‘Nelder-Mead’, ‘Powell’, ‘CG’, ‘BFGS’, ‘Newton-CG’, ‘L-BFGS-B’, ‘TNC’, ‘COBYLA’, ‘trust-constr’, ‘dogleg’, ‘trust-ncg’, ‘trust-exact’, ‘trust-krylov’]

-
- -
-
-openseries.owntypes.LiteralSeriesProps
-

alias of Literal[‘value_ret’, ‘geo_ret’, ‘arithmetic_ret’, ‘vol’, ‘downside_deviation’, ‘ret_vol_ratio’, ‘sortino_ratio’, ‘kappa3_ratio’, ‘z_score’, ‘skew’, ‘kurtosis’, ‘positive_share’, ‘var_down’, ‘cvar_down’, ‘vol_from_var’, ‘worst’, ‘worst_month’, ‘max_drawdown_cal_year’, ‘max_drawdown’, ‘max_drawdown_date’, ‘first_idx’, ‘last_idx’, ‘length’, ‘span_of_days’, ‘yearfrac’, ‘periods_in_a_year’, ‘autocorr’, ‘partial_autocorr’]

-
- -
-
-openseries.owntypes.LiteralFrameProps
-

alias of Literal[‘value_ret’, ‘geo_ret’, ‘arithmetic_ret’, ‘autocorr’, ‘vol’, ‘downside_deviation’, ‘ret_vol_ratio’, ‘sortino_ratio’, ‘kappa3_ratio’, ‘z_score’, ‘skew’, ‘kurtosis’, ‘positive_share’, ‘var_down’, ‘cvar_down’, ‘vol_from_var’, ‘worst’, ‘worst_month’, ‘max_drawdown’, ‘max_drawdown_date’, ‘max_drawdown_cal_year’, ‘first_indices’, ‘last_indices’, ‘lengths_of_items’, ‘span_of_days_all’]

-
- -
-
-

Validation Classes

-
-
-class openseries.owntypes.Countries(*, countryinput)[source]
-

Bases: BaseModel

-

Declare Countries.

-
-
Parameters:
-

countryinput (Annotated[set[Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)]], MinLen(min_length=1)] | Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=2, max_length=2, pattern=^[A-Z]{2}$, ascii_only=None)])

-
-
-
-
-countryinput: CountriesType
-
- -
-
-model_config = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
- -
-
-class openseries.owntypes.Currency(*, ccy)[source]
-

Bases: BaseModel

-

Declare Currency.

-
-
Parameters:
-

ccy (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)])

-
-
-
-
-ccy: CurrencyStringType
-
- -
-
-model_config = {}
-

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

-
- -
- -
-
-class openseries.owntypes.PropertiesList(iterable=(), /)[source]
-

Bases: list[str]

-

Base class for allowed property arguments definition.

-
-
-allowed_strings: ClassVar[set[str]] = {'arithmetic_ret', 'cvar_down', 'downside_deviation', 'geo_ret', 'kappa3_ratio', 'kurtosis', 'max_drawdown', 'max_drawdown_cal_year', 'max_drawdown_date', 'omega_ratio', 'positive_share', 'ret_vol_ratio', 'skew', 'sortino_ratio', 'value_ret', 'var_down', 'vol', 'vol_from_var', 'worst', 'worst_month', 'z_score'}
-
- -
- -
-
-class openseries.owntypes.OpenTimeSeriesPropertiesList(*args)[source]
-

Bases: PropertiesList

-

Allowed property arguments for the OpenTimeSeries class.

-
-
Parameters:
-

args (LiteralSeriesProps)

-
-
-
-
-allowed_strings: ClassVar[set[str]] = {'arithmetic_ret', 'autocorr', 'cvar_down', 'downside_deviation', 'first_idx', 'geo_ret', 'kappa3_ratio', 'kurtosis', 'last_idx', 'length', 'max_drawdown', 'max_drawdown_cal_year', 'max_drawdown_date', 'omega_ratio', 'partial_autocorr', 'periods_in_a_year', 'positive_share', 'ret_vol_ratio', 'skew', 'sortino_ratio', 'span_of_days', 'value_ret', 'var_down', 'vol', 'vol_from_var', 'worst', 'worst_month', 'yearfrac', 'z_score'}
-
- -
-
-__init__(*args)[source]
-

Property arguments for the OpenTimeSeries class.

-
-
Parameters:
-
    -
  • self (Self)

  • -
  • args (Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown_cal_year', 'max_drawdown', 'max_drawdown_date', 'first_idx', 'last_idx', 'length', 'span_of_days', 'yearfrac', 'periods_in_a_year', 'autocorr', 'partial_autocorr'])

  • -
-
-
Return type:
-

None

-
-
-
- -
- -
-
-class openseries.owntypes.OpenFramePropertiesList(*args)[source]
-

Bases: PropertiesList

-

Allowed property arguments for the OpenFrame class.

-
-
Parameters:
-

args (LiteralFrameProps)

-
-
-
-
-allowed_strings: ClassVar[set[str]] = {'arithmetic_ret', 'autocorr', 'cvar_down', 'downside_deviation', 'first_indices', 'geo_ret', 'kappa3_ratio', 'kurtosis', 'last_indices', 'lengths_of_items', 'max_drawdown', 'max_drawdown_cal_year', 'max_drawdown_date', 'omega_ratio', 'positive_share', 'ret_vol_ratio', 'skew', 'sortino_ratio', 'span_of_days_all', 'value_ret', 'var_down', 'vol', 'vol_from_var', 'worst', 'worst_month', 'z_score'}
-
- -
-
-__init__(*args)[source]
-

Property arguments for the OpenFrame class.

-
-
Parameters:
-
    -
  • self (Self)

  • -
  • args (Literal['value_ret', 'geo_ret', 'arithmetic_ret', 'autocorr', 'vol', 'downside_deviation', 'ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'z_score', 'skew', 'kurtosis', 'positive_share', 'var_down', 'cvar_down', 'vol_from_var', 'worst', 'worst_month', 'max_drawdown', 'max_drawdown_date', 'max_drawdown_cal_year', 'first_indices', 'last_indices', 'lengths_of_items', 'span_of_days_all'])

  • -
-
-
Return type:
-

None

-
-
-
- -
- -
-
-

Custom Exceptions

-
-
-exception openseries.owntypes.MixedValuetypesError[source]
-

Bases: Exception

-

Raised when provided timeseries valuetypes are not the same.

-
- -
-
-exception openseries.owntypes.AtLeastOneFrameError[source]
-

Bases: Exception

-

Raised when none of the possible frame inputs is provided.

-
- -
-
-exception openseries.owntypes.DateAlignmentError[source]
-

Bases: Exception

-

Raised when date input is not aligned with existing range.

-
- -
-
-exception openseries.owntypes.NumberOfItemsAndLabelsNotSameError[source]
-

Bases: Exception

-

Raised when number of labels is not matching the number of timeseries.

-
- -
-
-exception openseries.owntypes.InitialValueZeroError[source]
-

Bases: Exception

-

Raised when a calculation cannot be performed due to initial value(s) zero.

-
- -
-
-exception openseries.owntypes.CountriesNotStringNorListStrError[source]
-

Bases: Exception

-

Raised when countries argument is not provided in correct format.

-
- -
-
-exception openseries.owntypes.MarketsNotStringNorListStrError[source]
-

Bases: Exception

-

Raised when markets argument is not provided in correct format.

-
- -
-
-exception openseries.owntypes.TradingDaysNotAboveZeroError[source]
-

Bases: Exception

-

Raised when trading days argument is not above zero.

-
- -
-
-exception openseries.owntypes.BothStartAndEndError[source]
-

Bases: Exception

-

Raised when both start and end dates are provided.

-
- -
-
-exception openseries.owntypes.NoWeightsError[source]
-

Bases: Exception

-

Raised when no weights are provided to function where necessary.

-
- -
-
-exception openseries.owntypes.LabelsNotUniqueError[source]
-

Bases: Exception

-

Raised when provided label names are not unique.

-
- -
-
-exception openseries.owntypes.RatioInputError[source]
-

Bases: Exception

-

Raised when ratio keyword not provided correctly.

-
- -
-
-exception openseries.owntypes.MergingResultedInEmptyError[source]
-

Bases: Exception

-

Raised when a merge resulted in an empty DataFrame.

-
- -
-
-exception openseries.owntypes.IncorrectArgumentComboError[source]
-

Bases: Exception

-

Raised when correct combination of arguments is not provided.

-
- -
-
-exception openseries.owntypes.PropertiesInputValidationError[source]
-

Bases: Exception

-

Raised when duplicate strings are provided.

-
- -
-
-exception openseries.owntypes.ResampleDataLossError[source]
-

Bases: Exception

-

Raised when user attempts to run resample_to_business_period_ends on returns.

-
- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/development/changelog.html b/docs/build/html/development/changelog.html deleted file mode 100644 index 6568cce8..00000000 --- a/docs/build/html/development/changelog.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - Changelog — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Changelog

-
-

GitHub Releases

-

For details on changes, please visit the GitHub Releases page.

-
-
-

Release Notifications

-

Stay updated on new releases:

-
-
GitHub

Watch the openseries repository for release notifications

-
-
PyPI

Monitor openseries on PyPI for new versions

-
-
Conda-forge

Track updates on conda-forge

-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/development/contributing.html b/docs/build/html/development/contributing.html deleted file mode 100644 index 5f1d3d9a..00000000 --- a/docs/build/html/development/contributing.html +++ /dev/null @@ -1,615 +0,0 @@ - - - - - - - - - Contributing to openseries — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Contributing to openseries

-

We welcome contributions to openseries! This guide will help you get started with contributing to the project.

-
-

Getting Started

-
-

Development Setup

-
    -
  1. Fork the repository on GitHub

  2. -
  3. Clone your fork locally:

  4. -
-
git clone https://github.com/yourusername/openseries.git
-cd openseries
-
-
-
    -
  1. Create the development environment. This installs the pinned uv version -(uv==0.11.21), syncs locked dev and docs dependencies from -uv.lock, and installs pre-commit hooks:

  2. -
-
make install
-
-
-

On Windows:

-
.\make.ps1 make
-
-
-
-
-

Development Workflow

-
    -
  1. Create a new branch for your feature or bug fix:

  2. -
-
git checkout -b feature/your-feature-name
-
-
-
    -
  1. Make your changes

  2. -
  3. Run tests to ensure everything works:

  4. -
-
make test
-
-
-
    -
  1. Run linting and type checking:

  2. -
-
make lint
-
-
-
    -
  1. Commit your changes:

  2. -
-
git add .
-git commit -m "Add your descriptive commit message"
-
-
-
    -
  1. Push to your fork:

  2. -
-
git push origin feature/your-feature-name
-
-
-
    -
  1. Create a pull request on GitHub

  2. -
-
-
-
-

Code Standards

-
-

Code Style

-

openseries uses several tools to maintain code quality:

-
    -
  • Ruff: For linting and code formatting

  • -
  • mypy: For static type checking

  • -
  • pre-commit: For automated checks before commits

  • -
-

The configuration for these tools is in pyproject.toml.

-
-
-

Type Hints

-

All new code should include proper type hints:

-
def calculate_returns(prices: list[float]) -> list[float]:
-     """Calculate simple returns from prices."""
-     returns = []
-     for i in range(1, len(prices)):
-          ret = (prices[i] / prices[i-1]) - 1
-          returns.append(ret)
-     return returns
-
-
-
-
-

Docstrings

-

Use Google-style docstrings for all public functions and classes:

-
def calculate_sharpe_ratio(returns: list[float], risk_free_rate: float = 0.0) -> float:
-     """Calculate the Sharpe ratio.
-
-     Args:
-          returns: List of periodic returns.
-          risk_free_rate: Risk-free rate for the same period. Defaults to 0.0.
-
-     Returns:
-          The Sharpe ratio.
-
-     Raises:
-          ValueError: If returns list is empty.
-
-     Example:
-          >>> returns = [0.01, 0.02, -0.01, 0.03]
-          >>> sharpe = calculate_sharpe_ratio(returns)
-          >>> print(f"Sharpe ratio: {sharpe:.3f}")
-     """
-     if not returns:
-          raise ValueError("Returns list cannot be empty")
-
-     mean_return = sum(returns) / len(returns)
-     std_dev = (sum((r - mean_return) ** 2 for r in returns) / len(returns)) ** 0.5
-
-     if std_dev == 0:
-          return 0.0
-
-     return (mean_return - risk_free_rate) / std_dev
-
-
-
-
-
-

Testing

-
-

Test Structure

-

Tests are located in the tests/ directory and use pytest:

-
tests/
-├── __init__.py
-├── test_series.py
-├── test_frame.py
-├── test_portfoliotools.py
-└── ...
-
-
-
-
-

Writing Tests

-

Write comprehensive tests for new functionality:

-
import pytest
-import pandas as pd
-from pandas.testing import assert_frame_equal
-from openseries import OpenTimeSeries
-
-class TestOpenTimeSeries:
-     """Test cases for OpenTimeSeries class."""
-
-     def test_from_arrays_basic(self):
-          """Test basic creation from arrays."""
-          dates = ['2023-01-01', '2023-01-02', '2023-01-03']
-          values = [100.0, 102.0, 99.0]
-
-          series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test")
-
-          if series.label != "Test":
-                msg = f"Expected name 'Test', got '{series.label}'"
-                raise ValueError(msg)
-          if series.length != 3:
-                msg = f"Expected length 3, got {series.length}"
-                raise ValueError(msg)
-          if series.first_idx != pd.Timestamp('2023-01-01').date():
-                msg = f"Expected first_idx 2023-01-01, got {series.first_idx}"
-                raise ValueError(msg)
-          if series.last_idx != pd.Timestamp('2023-01-03').date():
-                msg = f"Expected last_idx 2023-01-03, got {series.last_idx}"
-                raise ValueError(msg)
-
-     def test_from_arrays_invalid_dates(self):
-          """Test that invalid dates raise appropriate errors."""
-          with pytest.raises(ValueError):
-                OpenTimeSeries.from_arrays(
-                     dates=['invalid-date'],
-                     values=[100.0],
-                     name="Test"
-                )
-
-     def test_calculate_returns(self):
-          """Test return calculation."""
-          dates = ['2023-01-01', '2023-01-02', '2023-01-03']
-          values = [100.0, 102.0, 99.0]
-
-          series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test")
-          series.value_to_ret()  # Modifies original
-
-          expected_returns = [0.02, -0.0294117647]  # Approximate
-          actual_returns = series.values
-
-          if len(actual_returns) != 2:
-                msg = f"Expected 2 returns, got {len(actual_returns)}"
-                raise ValueError(msg)
-          # Use tolerance-based comparison
-          if abs(actual_returns[0] - expected_returns[0]) >= 1e-6:
-                msg = f"First return mismatch: {actual_returns[0]} vs {expected_returns[0]}"
-                raise ValueError(msg)
-          if abs(actual_returns[1] - expected_returns[1]) >= 1e-6:
-                msg = f"Second return mismatch: {actual_returns[1]} vs {expected_returns[1]}"
-                raise ValueError(msg)
-
-
-
-
-

Running Tests

-

Run all tests:

-
make test
-
-
-

Run specific test files:

-
pytest tests/test_series.py
-
-
-

Run tests with coverage:

-
pytest --cov=openseries tests/
-
-
-
-
-

Test Coverage

-

openseries maintains high test coverage (>99%). New code should include comprehensive tests:

-
    -
  • Test normal use cases

  • -
  • Test edge cases

  • -
  • Test error conditions

  • -
  • Test with different data types and sizes

  • -
-
-
-
-

Documentation

-
-

Documentation Standards

-
    -
  • All public APIs must be documented

  • -
  • Include examples in docstrings where helpful

  • -
  • Update relevant documentation files when adding features

  • -
  • Use clear, concise language

  • -
-
-
-

Building Documentation

-

To build documentation locally:

-
cd docs
-make html
-
-
-

The built documentation will be in docs/_build/html/.

-
-
-
-

Contributing Guidelines

-
-

Pull Request Process

-
    -
  1. Fork and Branch: Create a feature branch from master

  2. -
  3. Develop: Make your changes with tests and documentation

  4. -
  5. Test: Ensure all tests pass and coverage remains high

  6. -
  7. Lint: Run linting and fix any issues

  8. -
  9. Document: Update documentation as needed

  10. -
  11. Commit: Use clear, descriptive commit messages

  12. -
  13. Pull Request: Create a PR with a clear description

  14. -
-
-
-

Commit Messages

-

Use clear, descriptive commit messages:

-
Add support for custom business day calendars
-
-- Implement custom calendar functionality in datefixer module
-- Add tests for various calendar configurations
-- Update documentation with examples
-- Fixes #123
-
-
-
-
-

Code Review Process

-

All contributions go through code review:

-
    -
  1. Automated checks must pass (tests, linting, type checking)

  2. -
  3. At least one maintainer review is required

  4. -
  5. Address any feedback or requested changes

  6. -
  7. Once approved, the PR will be merged

  8. -
-
-
-
-

Types of Contributions

-
-

Bug Reports

-

When reporting bugs, please include:

-
    -
  • Clear description of the issue

  • -
  • Steps to reproduce

  • -
  • Expected vs. actual behavior

  • -
  • Environment details (Python version, OS, etc.)

  • -
  • Minimal code example if possible

  • -
-
-
-

Feature Requests

-

For new features:

-
    -
  • Describe the use case and motivation

  • -
  • Provide examples of how it would be used

  • -
  • Consider backward compatibility

  • -
  • Discuss implementation approach if you have ideas

  • -
-
-
-

Code Contributions

-

Areas where contributions are especially welcome:

-
    -
  • New financial metrics: Additional risk measures, performance ratios

  • -
  • Data sources: Integration with new data providers

  • -
  • Visualization: Enhanced plotting capabilities

  • -
  • Performance: Optimization of calculations

  • -
  • Documentation: Examples, tutorials, API documentation

  • -
-
-
-

Documentation Contributions

-

Documentation improvements are always welcome:

-
    -
  • Fix typos or unclear explanations

  • -
  • Add examples to existing documentation

  • -
  • Create new tutorials or guides

  • -
  • Improve API documentation

  • -
-
-
-
-

Development Environment

-
-

IDE Setup

-

For VS Code, recommended extensions:

-
    -
  • Python

  • -
  • Pylance

  • -
  • Ruff

  • -
  • mypy

  • -
-

Recommended settings in .vscode/settings.json:

-
{
-    "python.defaultInterpreterPath": "venv/bin/python",
-    "python.linting.enabled": true,
-    "python.linting.ruffEnabled": true,
-    "python.formatting.provider": "ruff",
-    "python.typeChecking": "strict"
-}
-
-
-
-
-

Debugging

-

For debugging tests:

-
pytest --pdb tests/test_specific.py::test_function
-
-
-

For debugging with VS Code, create .vscode/launch.json:

-
{
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "name": "Python: Current File",
-            "type": "python",
-            "request": "launch",
-            "program": "${file}",
-            "console": "integratedTerminal"
-        },
-        {
-            "name": "Python: Pytest",
-            "type": "python",
-            "request": "launch",
-            "module": "pytest",
-            "args": ["${workspaceFolder}/tests"],
-            "console": "integratedTerminal"
-        }
-    ]
-}
-
-
-
-
-
-

Release Process

-

openseries follows semantic versioning (MAJOR.MINOR.PATCH):

-
    -
  • MAJOR: Breaking changes

  • -
  • MINOR: New features, backward compatible

  • -
  • PATCH: Bug fixes, backward compatible

  • -
-

Releases are managed by maintainers and include:

-
    -
  1. Version bump in pyproject.toml

  2. -
  3. Update CHANGELOG.md

  4. -
  5. Create GitHub release with release notes

  6. -
  7. Publish to PyPI and conda-forge

  8. -
-
-
-

Getting Help

-

If you need help with contributing:

-
    -
  • Check existing issues and discussions on GitHub

  • -
  • Ask questions in GitHub Discussions

  • -
  • Reach out to maintainers

  • -
-
-
-

Community Guidelines

-

openseries is committed to providing a welcoming and inclusive environment:

-
    -
  • Be respectful and constructive in all interactions

  • -
  • Focus on what is best for the community

  • -
  • Show empathy towards other community members

  • -
  • Welcome newcomers and help them get started

  • -
-

Thank you for contributing to openseries!

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/examples/custom_reports.html b/docs/build/html/examples/custom_reports.html deleted file mode 100644 index 7e524f1b..00000000 --- a/docs/build/html/examples/custom_reports.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - - Reporting — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Reporting

-

This example demonstrates how to create analysis reports using openseries and the built-in report functionality.

-
-

Using the Built-in HTML Report

-
import yfinance as yf
-from openseries import OpenTimeSeries, OpenFrame, report_html
-import pandas as pd
-
-# Load sample data for comparison
-tickers = ["AAPL", "MSFT", "GOOGL", "SPY"]
-names = ["Apple", "Microsoft", "Google", "S&P 500"]
-
-series_list = []
-for ticker, name in zip(tickers, names):
-     data = yf.Ticker(ticker).history(period="3y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=name)
-     series_list.append(series)
-
-# Create frame for report
-comparison_frame = OpenFrame(constituents=series_list)
-
-# Generate HTML report
-# The last asset in the frame is used as the benchmark
-figure, filepath = report_html(
-     data=comparison_frame,
-     output_type="file",
-     filename="stock_comparison_report.html"
-)
-
-# filepath contains the path to the saved HTML file
-print(f"Report saved to: {filepath}")
-
-# The figure object can be used for further customization if needed
-# figure.show()  # Display the figure interactively
-
-
-
-
-

Embedding Reports in Existing HTML Pages

-

When you need to embed a report in an existing HTML page, use output_type="div":

-
# Generate HTML div section for embedding
-figure, html_div = report_html(
-     data=comparison_frame,
-     output_type="div"
-)
-
-# html_div contains the responsive HTML div section
-# that can be embedded in your existing HTML page
-# It includes both desktop and mobile layouts with CSS and JavaScript
-
-# Example: Save to a custom HTML template
-html_template = f"""
-<!DOCTYPE html>
-<html>
-<head>
-    <title>My Custom Report</title>
-</head>
-<body>
-    <h1>Portfolio Analysis Report</h1>
-    {html_div}
-</body>
-</html>
-"""
-
-with open("custom_report.html", "w", encoding="utf-8") as f:
-    f.write(html_template)
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/examples/multi_asset.html b/docs/build/html/examples/multi_asset.html deleted file mode 100644 index 486e9f00..00000000 --- a/docs/build/html/examples/multi_asset.html +++ /dev/null @@ -1,467 +0,0 @@ - - - - - - - - - Multi-Asset Analysis — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Multi-Asset Analysis

-

This example shows how to analyze multiple assets simultaneously using OpenFrame.

-
-

Setting Up Multi-Asset Analysis

-
import yfinance as yf
-from openseries import OpenTimeSeries, OpenFrame
-
-# Define asset universe
-assets = {
-     "AAPL": "Apple Inc.",
-     "GOOGL": "Alphabet Inc.",
-     "MSFT": "Microsoft Corp.",
-     "AMZN": "Amazon.com Inc.",
-     "TSLA": "Tesla Inc.",
-     "NVDA": "NVIDIA Corp.",
-     "META": "Meta Platforms Inc.",
-     "NFLX": "Netflix Inc."
-}
-
-# Download data for all assets
-series_list = []
-for ticker, name in assets.items():
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="3y")
-     series = OpenTimeSeries.from_df(
-          dframe=data['Close']
-     )
-     series.set_new_label(lvl_zero=name)
-     series_list.append(series)
-     print(f"Loaded {name}: {series.length} observations")
-
-# Create OpenFrame
-tech_stocks = OpenFrame(constituents=series_list)
-print(f"\nCreated frame with {tech_stocks.item_count} assets")
-print(f"Common period: {tech_stocks.first_idx} to {tech_stocks.last_idx}")
-
-
-
-
-

Comparative Analysis

-
# Get metrics for all assets
-all_metrics = tech_stocks.all_properties()
-print("=== COMPARATIVE METRICS ===")
-print(all_metrics)
-
-# Focus on key metrics
-key_metrics = all_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']]
-key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown']
-
-# Convert to percentages for better readability
-percentage_metrics = key_metrics.copy()
-percentage_metrics.loc[['Annual Return', 'Volatility', 'Max Drawdown']] *= 100
-
-print("\n=== KEY METRICS COMPARISON ===")
-print(percentage_metrics.round(2))
-
-
-
-
-

Ranking Analysis

-
# Rank assets by different criteria using openseries metrics
-# Get key metrics for ranking
-returns = all_metrics.loc['Geometric return']
-volatilities = all_metrics.loc['Volatility']
-sharpe_ratios = all_metrics.loc['Return vol ratio']
-drawdowns = all_metrics.loc['Max drawdown']
-
-print("\n=== ASSET RANKINGS ===")
-print("Ranked by Return (highest first):")
-for i, (asset, ret) in enumerate(returns.sort_values(ascending=False).items(), 1):
-    print(f"  {i}. {asset}: {ret:.2%}")
-
-print("\nRanked by Volatility (lowest first):")
-for i, (asset, vol) in enumerate(volatilities.sort_values(ascending=True).items(), 1):
-    print(f"  {i}. {asset}: {vol:.2%}")
-
-print("\nRanked by Sharpe Ratio (highest first):")
-for i, (asset, sharpe) in enumerate(sharpe_ratios.sort_values(ascending=False).items(), 1):
-    print(f"  {i}. {asset}: {sharpe:.2f}")
-
-print("\nRanked by Max Drawdown (least negative first):")
-for i, (asset, dd) in enumerate(drawdowns.sort_values(ascending=False).items(), 1):
-    print(f"  {i}. {asset}: {dd:.2%}")
-
-
-
-
-

Correlation Analysis

-
# Calculate correlation matrix
-correlation_matrix = tech_stocks.correl_matrix
-print("\n=== CORRELATION MATRIX ===")
-print(correlation_matrix.round(3))
-
-# Find most and least correlated pairs
-corr_pairs = []
-for i in range(len(correlation_matrix.columns)):
-     for j in range(i+1, len(correlation_matrix.columns)):
-          asset1 = correlation_matrix.columns[i]
-          asset2 = correlation_matrix.columns[j]
-          corr = correlation_matrix.iloc[i, j]
-          corr_pairs.append((asset1, asset2, corr))
-
-# Sort by correlation
-corr_pairs.sort(key=lambda x: x[2], reverse=True)
-
-print("\n=== HIGHEST CORRELATIONS ===")
-for asset1, asset2, corr in corr_pairs[:5]:
-     print(f"{asset1} - {asset2}: {corr:.3f}")
-
-print("\n=== LOWEST CORRELATIONS ===")
-for asset1, asset2, corr in corr_pairs[-5:]:
-     print(f"{asset1} - {asset2}: {corr:.3f}")
-
-
-
-
-

Risk-Return Analysis

-
# Analyze risk-return using openseries metrics
-returns = all_metrics.loc['Geometric return']
-volatilities = all_metrics.loc['Volatility']
-sharpe_ratios = all_metrics.loc['Return vol ratio']
-
-print("\n=== RISK-RETURN ANALYSIS ===")
-for asset in returns.index:
-    ret_pct = returns[asset] * 100
-    vol_pct = volatilities[asset] * 100
-    sharpe = sharpe_ratios[asset]
-    print(f"{asset}: Return={ret_pct:.2f}%, Volatility={vol_pct:.2f}%, Sharpe={sharpe:.2f}")
-
-# Identify efficient assets (high return per unit risk)
-# Calculate 75th percentile threshold manually
-sorted_sharpes = sorted(sharpe_ratios.values, reverse=True)
-threshold_idx = int(len(sorted_sharpes) * 0.25)
-efficient_threshold = sorted_sharpes[threshold_idx] if threshold_idx < len(sorted_sharpes) else sorted_sharpes[-1]
-
-print(f"\n=== MOST EFFICIENT ASSETS (Sharpe >= {efficient_threshold:.2f}) ===")
-for asset, sharpe in sharpe_ratios.items():
-    if sharpe >= efficient_threshold:
-        print(f"{asset}: {sharpe:.2f}")
-
-
-
-
-

Sector/Style Analysis

-
# Group assets by characteristics (example grouping)
-asset_groups = {
-     'Mega Cap': ['Apple Inc.', 'Microsoft Corp.', 'Alphabet Inc.', 'Amazon.com Inc.'],
-     'Growth': ['Tesla Inc.', 'NVIDIA Corp.', 'Netflix Inc.'],
-     'Social Media': ['Meta Platforms Inc.']
-}
-
-print("\n=== GROUP ANALYSIS ===")
-for group_name, group_assets in asset_groups.items():
-     # Filter assets that exist in our data
-     group_series = [s for s in tech_stocks.constituents if s.label in group_assets]
-
-     if group_series:
-          group_frame = OpenFrame(constituents=group_series)
-          group_metrics = group_frame.all_properties()
-
-          avg_return = group_metrics.loc['Geometric return'].mean()
-          avg_vol = group_metrics.loc['Volatility'].mean()
-          avg_sharpe = group_metrics.loc['Return vol ratio'].mean()
-
-          print(f"\n{group_name} ({len(group_series)} assets):")
-          print(f"  Average Return: {avg_return:.2%}")
-          print(f"  Average Volatility: {avg_vol:.2%}")
-          print(f"  Average Sharpe: {avg_sharpe:.2f}")
-
-
-
-
-

Time Series Analysis

-
# Rolling correlation analysis
-# Pick two assets for detailed analysis
-apple = next(s for s in tech_stocks.constituents if "Apple" in s.label)
-microsoft = next(s for s in tech_stocks.constituents if "Microsoft" in s.label)
-
-pair_frame = OpenFrame(constituents=[apple, microsoft])
-rolling_corr = pair_frame.rolling_corr(observations=252)  # 1-year rolling
-
-print(f"\n=== ROLLING CORRELATION: {apple.label} vs {microsoft.label} ===")
-print(f"Current correlation: {rolling_corr.iloc[-1, 0]:.3f}")
-print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}")
-print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}")
-
-
-
-
-

Performance Attribution

-
# Create equal-weighted portfolio for attribution
-portfolio_df = tech_stocks.make_portfolio(name="Tech Portfolio", weight_strat="eq_weights")
-portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-print(f"\n=== PORTFOLIO vs INDIVIDUAL ASSETS ===")
-print(f"Portfolio Return: {portfolio.geo_ret:.2%}")
-print(f"Portfolio Volatility: {portfolio.vol:.2%}")
-print(f"Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}")
-
-# Compare with individual assets using OpenFrame
-asset_metrics = tech_stocks.all_properties()
-individual_returns = asset_metrics.loc['Geometric return'].values
-individual_vols = asset_metrics.loc['Volatility'].values
-
-print(f"\nDiversification benefit:")
-equal_weights = [1/tech_stocks.item_count] * tech_stocks.item_count
-# Calculate weighted average manually
-weighted_avg_return = sum(ret * w for ret, w in zip(individual_returns, equal_weights))
-weighted_avg_vol = sum(vol * w for vol, w in zip(individual_vols, equal_weights))
-print(f"  Weighted avg return: {weighted_avg_return:.2%}")
-print(f"  Portfolio return: {portfolio.geo_ret:.2%}")
-print(f"  Weighted avg volatility: {weighted_avg_vol:.2%}")
-print(f"  Portfolio volatility: {portfolio.vol:.2%}")
-print(f"  Volatility reduction: {(weighted_avg_vol - portfolio.vol):.2%}")
-
-
-
-
-

Stress Testing

-
# Identify worst market days (modifies original)
-market_proxy = tech_stocks.constituents[0]  # Use first asset as market proxy
-market_proxy.value_to_ret()
-market_data = market_proxy.tsdf
-# Find worst 5% of days
-worst_threshold = market_data.quantile(0.05)
-worst_days = market_data[market_data <= worst_threshold]
-
-print(f"\n=== STRESS TEST ANALYSIS ===")
-print(f"Market stress threshold: {worst_threshold:.2%}")
-print(f"Number of stress days: {len(worst_days)}")
-
-# Analyze each asset's performance during stress
-print("\nAsset performance during market stress:")
-for series in tech_stocks.constituents:
-     series.value_to_ret()  # Modifies original
-     asset_data = series.tsdf
-     # Get returns on stress days
-     stress_returns = asset_data.loc[worst_days.index]
-     avg_stress_return = stress_returns.mean()
-
-     print(f"  {series.label}: {avg_stress_return:.2%}")
-
-
-
-
-

Export Multi-Asset Results

-
# Export using openseries native methods
-# Export frame data
-tech_stocks.to_xlsx('multi_asset_analysis.xlsx')
-
-# Note: For comprehensive Excel export with multiple sheets,
-# you can use the DataFrame returned by all_properties() and correl_matrix
-# which are pandas DataFrames and support to_excel() method
-print("\nMulti-asset analysis exported to 'multi_asset_analysis.xlsx'")
-
-
-
-
-

Complete Multi-Asset Analysis Workflow

-

Here’s how to perform a complete multi-asset analysis using openseries methods directly:

-
# Example: Analyze tech stocks using openseries methods
-tech_tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
-
-# Load data using openseries methods
-series_list = []
-for ticker in tech_tickers:
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="3y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=ticker)
-     series_list.append(series)
-
-if not series_list:
-     print("No data loaded")
-else:
-     # Create frame using openseries
-     frame = OpenFrame(constituents=series_list)
-
-     # Analysis using openseries properties and methods
-     print(f"=== MULTI-ASSET ANALYSIS ===")
-     print(f"Assets: {frame.item_count}")
-     print(f"Period: {frame.first_idx} to {frame.last_idx}")
-
-     # Key metrics using openseries all_properties method
-     key_metrics = frame.all_properties(
-          properties=['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']
-     )
-
-     print("\nKey Metrics:")
-     print((key_metrics * 100).round(2))  # Convert to percentages
-
-     # Correlations using openseries correl_matrix property
-     correlations = frame.correl_matrix
-     avg_correlation = correlations.mean().mean()
-     print(f"\nAverage correlation: {avg_correlation:.3f}")
-
-     # Create portfolio using openseries make_portfolio method
-     portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
-     portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-     print(f"\nEqual-weight portfolio:")
-     print(f"  Return: {portfolio.geo_ret:.2%}")
-     print(f"  Volatility: {portfolio.vol:.2%}")
-     print(f"  Sharpe: {portfolio.ret_vol_ratio:.2f}")
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/examples/portfolio_optimization.html b/docs/build/html/examples/portfolio_optimization.html deleted file mode 100644 index e54137bc..00000000 --- a/docs/build/html/examples/portfolio_optimization.html +++ /dev/null @@ -1,709 +0,0 @@ - - - - - - - - - Portfolio Optimization — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Portfolio Optimization

-

This example demonstrates various portfolio optimization techniques using openseries, including both theoretical approaches and real-world applications with actual fund data.

-
-

Basic Portfolio Optimization Setup

-
import yfinance as yf
-from openseries import OpenTimeSeries, OpenFrame
-from openseries import efficient_frontier, simulate_portfolios
-
-# Define investment universe
-universe = {
-     "VTI": "Total Stock Market",
-     "VEA": "Developed Markets",
-     "VWO": "Emerging Markets",
-     "BND": "Total Bond Market",
-     "VNQ": "Real Estate",
-     "VDE": "Energy",
-     "VGT": "Technology",
-     "VHT": "Healthcare"
-}
-
-# Load data
-assets = []
-for ticker, name in universe.items():
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="5y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=name)
-     assets.append(series)
-     print(f"Loaded {name}")
-
-# Create investment universe frame
-investment_universe = OpenFrame(constituents=assets)
-print(f"\nInvestment universe: {investment_universe.item_count} assets")
-print(f"Period: {investment_universe.first_idx} to {investment_universe.last_idx}")
-
-
-
-
-

Mean-Variance Optimization

-
# Calculate efficient frontier
-# This may fail with various exceptions
-frontier_df, simulated_df, optimal_portfolio = efficient_frontier(
-     eframe=investment_universe,
-     num_ports=100,
-     seed=42
-)
-
-print("=== EFFICIENT FRONTIER RESULTS ===")
-print(f"Generated {len(frontier_df)} efficient portfolios")
-print(f"Simulated {len(simulated_df)} random portfolios")
-
-# Find key portfolios
-returns = frontier_df['ret']
-volatilities = frontier_df['stdev']
-sharpe_ratios = returns / volatilities
-
-# Maximum Sharpe ratio portfolio
-max_sharpe_idx = sharpe_ratios.idxmax()
-max_sharpe_weights = optimal_portfolio[-len(investment_universe.constituents):]
-
-print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===")
-print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}")
-print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}")
-print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}")
-
-print("\nOptimal Weights:")
-for i, weight in enumerate(max_sharpe_weights):
-     asset_name = investment_universe.constituents[i].label
-     if weight > 0.01:  # Only show weights > 1%
-          print(f"  {asset_name}: {weight:.1%}")
-
-# Minimum volatility portfolio
-min_vol_idx = volatilities.idxmin()
-min_vol_weights = frontier_df.iloc[min_vol_idx][investment_universe.columns_lvl_zero].values
-
-print(f"\n=== MINIMUM VOLATILITY PORTFOLIO ===")
-print(f"Expected Return: {min_vol_row['ret']:.2%}")
-print(f"Volatility: {min_vol_row['stdev']:.2%}")
-print(f"Sharpe Ratio: {sharpe_ratios.iloc[min_vol_idx]:.2f}")
-
-print("\nMinimum Volatility Weights:")
-for col in investment_universe.columns_lvl_zero:
-     weight = min_vol_row[col]
-     if weight > 0.01:
-          print(f"  {col}: {weight:.1%}")
-
-
-
-
-

Monte Carlo Portfolio Simulation

-
# Generate random portfolios
-# This may fail with various exceptions
-simulation_results = simulate_portfolios(
-     simframe=investment_universe,
-     num_ports=50000,
-     seed=42
-)
-
-print(f"\n=== MONTE CARLO SIMULATION ===")
-print(f"Simulated {len(simulation_results)} random portfolios")
-
-sim_returns = simulation_results['ret'].values
-sim_volatilities = simulation_results['stdev'].values
-sim_sharpe_ratios = sim_returns / sim_volatilities
-
-# Statistics of simulated portfolios
-print(f"\nSimulation Statistics:")
-print(f"Return range: {sim_returns.min():.2%} to {sim_returns.max():.2%}")
-print(f"Volatility range: {sim_volatilities.min():.2%} to {sim_volatilities.max():.2%}")
-print(f"Sharpe range: {sim_sharpe_ratios.min():.2f} to {sim_sharpe_ratios.max():.2f}")
-
-# Best portfolios from simulation
-sorted_indices = sorted(range(len(sim_sharpe_ratios)), key=lambda i: sim_sharpe_ratios.iloc[i], reverse=True)
-top_sharpe_indices = sorted_indices[:5]
-
-print(f"\n=== TOP 5 SIMULATED PORTFOLIOS ===")
-for i, idx in enumerate(reversed(top_sharpe_indices)):
-     print(f"\nRank {i+1}:")
-     print(f"  Return: {sim_returns[idx]:.2%}")
-     print(f"  Volatility: {sim_volatilities[idx]:.2%}")
-     print(f"  Sharpe: {sim_sharpe_ratios[idx]:.2f}")
-
-     weights = simulation_results.iloc[idx][investment_universe.columns_lvl_zero].values
-     print("  Weights:")
-     for j, weight in enumerate(weights):
-          if weight > 0.05:  # Only show weights > 5%
-                asset_name = investment_universe.constituents[j].label
-                print(f"    {asset_name}: {weight:.1%}")
-
-
-
-
-

Risk-Based Portfolio Strategies

-
-

Equal Weight Portfolio

-
# Equal weight portfolio using native weight_strat
-equal_weight_portfolio_df = investment_universe.make_portfolio(
-     name="Equal Weight",
-     weight_strat="eq_weights"
-)
-equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df)
-
-print(f"\n=== EQUAL WEIGHT PORTFOLIO ===")
-print(f"Return: {equal_weight_portfolio.geo_ret:.2%}")
-print(f"Volatility: {equal_weight_portfolio.vol:.2%}")
-print(f"Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Inverse Volatility Portfolio

-
# Inverse volatility weighting using native weight_strat
-inv_vol_portfolio_df = investment_universe.make_portfolio(
-     name="Inverse Volatility",
-     weight_strat="inv_vol"
-)
-inv_vol_portfolio = OpenTimeSeries.from_df(dframe=inv_vol_portfolio_df)
-
-print(f"\n=== INVERSE VOLATILITY PORTFOLIO ===")
-print(f"Return: {inv_vol_portfolio.geo_ret:.2%}")
-print(f"Volatility: {inv_vol_portfolio.vol:.2%}")
-print(f"Sharpe: {inv_vol_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Maximum Diversification Portfolio

-

The maximum diversification strategy aims to maximize portfolio diversification by optimizing the correlation structure. This strategy can encounter numerical issues in certain scenarios:

-
# Maximum diversification portfolio using native weight_strat
-# This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError
-max_div_portfolio_df = investment_universe.make_portfolio(
-     name="Maximum Diversification",
-     weight_strat="max_div"
-)
-max_div_portfolio = OpenTimeSeries.from_df(dframe=max_div_portfolio_df)
-
-print(f"\n=== MAXIMUM DIVERSIFICATION PORTFOLIO ===")
-print(f"Return: {max_div_portfolio.geo_ret:.2%}")
-print(f"Volatility: {max_div_portfolio.vol:.2%}")
-print(f"Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-
-

Minimum Volatility Overweight Portfolio

-
# Minimum volatility overweight portfolio using native weight_strat
-min_vol_portfolio_df = investment_universe.make_portfolio(
-     name="Min Vol Overweight",
-     weight_strat="min_vol_overweight"
-)
-min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df)
-
-print(f"\n=== MINIMUM VOLATILITY OVERWEIGHT PORTFOLIO ===")
-print(f"Return: {min_vol_portfolio.geo_ret:.2%}")
-print(f"Volatility: {min_vol_portfolio.vol:.2%}")
-print(f"Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Portfolio Comparison

-
# Compare all portfolio strategies
-portfolios = [
-     equal_weight_portfolio,
-     inv_vol_portfolio,
-     max_div_portfolio,
-     min_vol_portfolio
-]
-
-# Add optimized portfolios if available
-if 'max_sharpe_weights' in locals():
-     investment_universe.weights = max_sharpe_weights.tolist()
-     max_sharpe_portfolio_df = investment_universe.make_portfolio(
-          name="Max Sharpe (Optimized)"
-     )
-     max_sharpe_portfolio = OpenTimeSeries.from_df(dframe=max_sharpe_portfolio_df)
-     portfolios.append(max_sharpe_portfolio)
-
-if 'min_vol_weights' in locals():
-     investment_universe.weights = min_vol_weights.tolist()
-     min_vol_portfolio_df = investment_universe.make_portfolio(
-          name="Min Vol (Optimized)"
-     )
-     min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df)
-     portfolios.append(min_vol_portfolio)
-
-# Create comparison frame
-comparison_frame = OpenFrame(constituents=portfolios)
-comparison_metrics = comparison_frame.all_properties()
-
-# Display key metrics
-key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']]
-key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown']
-
-print(f"\n=== PORTFOLIO STRATEGY COMPARISON ===")
-print((key_metrics * 100).round(2))  # Convert to percentages
-
-
-
-

Weight Strategy Details

-

The openseries library provides several built-in weight strategies for portfolio construction:

-
-
Equal Weights (``eq_weights``)
    -
  • Assigns equal weight to all assets

  • -
  • Most robust strategy, always works

  • -
  • Good baseline for comparison

  • -
-
-
Inverse Volatility (``inv_vol``)
    -
  • Weights assets inversely to their volatility

  • -
  • Lower volatility assets get higher weights

  • -
  • Generally stable and reliable

  • -
-
-
Maximum Diversification (``max_div``)
    -
  • Optimizes correlation structure for maximum diversification

  • -
  • Can encounter numerical issues with certain data patterns

  • -
  • May produce negative weights in some scenarios

  • -
  • Raises MaxDiversificationNaNError for numerical issues

  • -
  • Raises MaxDiversificationNegativeWeightsError for negative weights

  • -
-
-
Minimum Volatility Overweight (``min_vol_overweight``)
    -
  • Overweights the least volatile asset (60% weight)

  • -
  • Distributes remaining 40% equally among other assets

  • -
  • Based on the low volatility anomaly

  • -
-
-
Exception Handling

When using the maximum diversification strategy, it’s recommended to handle potential exceptions:

-
from openseries.owntypes import (
-    MaxDiversificationNaNError,
-    MaxDiversificationNegativeWeightsError
-)
-
-# This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError
-portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div")
-
-
-
-
-
-
-
-

Backtesting Framework

-
# Define strategies to backtest using native weight_strat
-strategies = {
-     'Equal Weight': 'eq_weights',
-     'Inverse Volatility': 'inv_vol',
-     'Max Diversification': 'max_div',
-     'Min Vol Overweight': 'min_vol_overweight'
-}
-
-# Run backtest using native strategies
-backtest_results = {}
-for strategy_name, weight_strat in strategies.items():
-     # This may fail with MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError, or other exceptions
-     portfolio_df = investment_universe.make_portfolio(
-          name=strategy_name,
-          weight_strat=weight_strat
-     )
-     portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-     backtest_results[strategy_name] = {
-          'return': portfolio.geo_ret,
-          'volatility': portfolio.vol,
-          'sharpe': portfolio.ret_vol_ratio,
-          'max_drawdown': portfolio.max_drawdown,
-          'calmar': portfolio.geo_ret / abs(portfolio.max_drawdown) if portfolio.max_drawdown != 0 else float('nan')
-     }
-
-print(f"\n=== BACKTEST RESULTS ===")
-for strategy_name, metrics in backtest_results.items():
-    print(f"\n{strategy_name}:")
-    print(f"  Return: {metrics['return']:.4f}")
-    print(f"  Volatility: {metrics['volatility']:.4f}")
-    print(f"  Sharpe: {metrics['sharpe']:.4f}")
-    print(f"  Max Drawdown: {metrics['max_drawdown']:.4f}")
-    print(f"  Calmar: {metrics['calmar']:.4f}")
-
-# Rank strategies
-sorted_strategies = sorted(backtest_results.items(), key=lambda x: x[1]['sharpe'], reverse=True)
-best_strategy = sorted_strategies[0][0]
-
-print(f"\nBest performing strategy: {best_strategy}")
-print(f"Sharpe ratio: {sorted_strategies[0][1]['sharpe']:.3f}")
-
-
-
-
-

Export Optimization Results

-
# Export using openseries native methods
-# Export frame data
-investment_universe.to_xlsx('portfolio_optimization_results.xlsx')
-
-# Note: For comprehensive Excel export with multiple sheets,
-# the DataFrames returned by all_properties() and correl_matrix
-# are pandas DataFrames and support to_excel() method
-print("\nOptimization results exported to 'portfolio_optimization_results.xlsx'")
-
-
-
-
-

Real-World Fund Portfolio Optimization

-

This section demonstrates portfolio optimization using actual fund data from professional fund managers, showing how optimization techniques apply in practice.

-
-

Using Real Fund Data for Optimization

-

Here’s how to work with real fund data using openseries methods directly:

-
from requests import get as requests_get
-from openseries import (
-     OpenTimeSeries, OpenFrame, ValueType,
-     efficient_frontier, prepare_plot_data, sharpeplot,
-     load_plotly_dict, get_previous_business_day_before_today
-)
-
-# Define fund universe for optimization
-fund_universe_isins = [
-     "SE0015243886",  # Global High Yield
-     "SE0011337195",  # Global Equity
-     "SE0011670843",  # Global Bond
-     "SE0017832280",  # Alternative Strategy
-     "SE0017832330",  # Multi-Asset Strategy
-]
-
-# Load fund data using openseries methods
-response = requests_get(url="https://api.captor.se/public/api/nav", timeout=10)
-response.raise_for_status()
-
-series_list = []
-result = response.json()
-
-for data in result:
-     if data["isin"] in fund_universe_isins:
-          series = OpenTimeSeries.from_arrays(
-                name=data["longName"],
-                isin=data["isin"],
-                baseccy=data["currency"],
-                dates=data["dates"],
-                values=data["navPerUnit"],
-                valuetype=ValueType.PRICE,
-          )
-          series_list.append(series)
-
-# Create fund universe using openseries OpenFrame
-fund_universe = OpenFrame(constituents=series_list)
-
-# Process data using openseries methods
-fund_universe = fund_universe.value_nan_handle().trunc_frame().to_cumret()
-
-print(f"Fund universe created with {fund_universe.item_count} funds")
-print(f"Analysis period: {fund_universe.first_idx} to {fund_universe.last_idx}")
-
-
-
-
-

Advanced Optimization with Real Data

-
# Set optimization parameters
-simulations = 10000
-frontier_points = 50
-seed = 55
-
-# Create current portfolio (equal weights)
-current_portfolio_df = fund_universe.make_portfolio(
-     name="Current Portfolio",
-     weight_strat="eq_weights",
-)
-current_portfolio = OpenTimeSeries.from_df(dframe=current_portfolio_df)
-
-# Calculate efficient frontier
-frontier, simulated_portfolios, optimal_portfolio = efficient_frontier(
-     eframe=fund_universe,
-     num_ports=simulations,
-     seed=seed,
-     frontier_points=frontier_points,
-)
-
-# Prepare visualization data
-plot_data = prepare_plot_data(
-     assets=fund_universe,
-     current=current_portfolio,
-     optimized=optimal_portfolio,
-)
-
-# Load plotly configuration
-figdict, _ = load_plotly_dict()
-
-# Create efficient frontier plot
-optimization_plot, _ = sharpeplot(
-     sim_frame=simulated_portfolios,
-     line_frame=frontier,
-     point_frame=plot_data,
-     point_frame_mode="markers+text",
-     title="Real Fund Portfolio Optimization",
-     add_logo=False,
-     auto_open=False,
-     output_type="div",
-)
-optimization_plot = optimization_plot.update_layout(width=1200, height=700)
-
-# Display the optimization results
-optimization_plot.show(config=figdict["config"])
-
-
-
-
-

Performance Comparison Analysis

-
# Compare different portfolio strategies
-strategies = {}
-
-# Equal weight portfolio
-equal_weight_portfolio_df = fund_universe.make_portfolio(
-     name="Equal Weight", weight_strat="eq_weights"
-)
-equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df)
-strategies['Equal Weight'] = equal_weight_portfolio
-
-# Optimal portfolio from efficient frontier
-fund_universe.weights = optimal_portfolio[-fund_universe.item_count:].tolist()
-optimal_portfolio_df = fund_universe.make_portfolio(name="Optimal Portfolio")
-optimal_portfolio_series = OpenTimeSeries.from_df(dframe=optimal_portfolio_df)
-strategies['Optimal Portfolio'] = optimal_portfolio_series
-
-# Create comparison frame
-comparison_frame = OpenFrame(constituents=list(strategies.values()))
-comparison_metrics = comparison_frame.all_properties()
-
-# Display key metrics
-key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']]
-key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown']
-
-print("=== PORTFOLIO STRATEGY COMPARISON ===")
-print((key_metrics * 100).round(2))
-
-# Calculate improvement metrics
-improvement = {
-     'Return Improvement': (optimal_portfolio_series.geo_ret - equal_weight_portfolio.geo_ret) * 100,
-     'Volatility Change': (optimal_portfolio_series.vol - equal_weight_portfolio.vol) * 100,
-     'Sharpe Improvement': optimal_portfolio_series.ret_vol_ratio - equal_weight_portfolio.ret_vol_ratio,
-}
-
-print("\n=== OPTIMIZATION IMPROVEMENTS ===")
-for metric, value in improvement.items():
-     print(f"{metric}: {value:+.2f}")
-
-
-
-
-
-

Complete Optimization Workflow

-

Here’s how to perform portfolio optimization using openseries methods directly:

-
# Example: Optimize ETF portfolio using openseries methods
-etf_tickers = ["VTI", "VEA", "VWO", "BND", "VNQ"]
-
-# Load data using openseries methods
-assets = []
-for ticker in etf_tickers:
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="5y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=ticker)
-     assets.append(series)
-
-if len(assets) < 2:
-     print("Need at least 2 assets for optimization")
-else:
-     frame = OpenFrame(constituents=assets)
-
-     # Use openseries native weight strategies
-     strategies = {
-          'Equal Weight': 'eq_weights',
-          'Inverse Volatility': 'inv_vol',
-          'Max Diversification': 'max_div',
-          'Min Vol Overweight': 'min_vol_overweight'
-     }
-
-     # Create portfolios using openseries make_portfolio method
-     results = {}
-     for name, weight_strat in strategies.items():
-          # This may fail with MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError, or other exceptions
-          portfolio_df = frame.make_portfolio(name=name, weight_strat=weight_strat)
-          portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-          results[name] = {
-                'Return': portfolio.geo_ret,
-                'Volatility': portfolio.vol,
-                'Sharpe': portfolio.ret_vol_ratio,
-                'Max Drawdown': portfolio.max_drawdown
-          }
-
-     print("=== PORTFOLIO OPTIMIZATION RESULTS ===")
-     for name, metrics in results.items():
-         print(f"\n{name}:")
-         print(f"  Return: {metrics['Return']*100:.2f}%")
-         print(f"  Volatility: {metrics['Volatility']*100:.2f}%")
-         print(f"  Sharpe: {metrics['Sharpe']:.2f}")
-         print(f"  Max Drawdown: {metrics['Max Drawdown']*100:.2f}%")
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/examples/single_asset.html b/docs/build/html/examples/single_asset.html deleted file mode 100644 index deb03019..00000000 --- a/docs/build/html/examples/single_asset.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - Single Asset Analysis — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Single Asset Analysis

-

This example demonstrates comprehensive analysis of a single financial asset using openseries.

-
-

Basic Setup

-
import yfinance as yf
-from openseries import OpenTimeSeries
-import numpy as np
-
-# Download Apple stock data
-ticker = yf.Ticker("AAPL")
-data = ticker.history(period="5y")
-
-# Create OpenTimeSeries
-apple = OpenTimeSeries.from_df(
-     dframe=data['Close']
-)
-
-# Set descriptive label
-apple.set_new_label(lvl_zero="Apple Inc. (AAPL)")
-
-print(f"Loaded {apple.length} observations")
-print(f"Date range: {apple.first_idx} to {apple.last_idx}")
-
-
-
-
-

Performance Analysis

-
# Basic performance metrics
-print("=== PERFORMANCE METRICS ===")
-print(f"Total Return: {apple.value_ret:.2%}")
-print(f"Annualized Return: {apple.geo_ret:.2%}")
-print(f"Annualized Volatility: {apple.vol:.2%}")
-print(f"Sharpe Ratio: {apple.ret_vol_ratio:.2f}")
-
-# Get all metrics at once
-all_metrics = apple.all_properties()
-print("\n=== ALL METRICS ===")
-print(all_metrics)
-
-
-
-
-

Risk Analysis

-
# Risk metrics
-print("=== RISK ANALYSIS ===")
-print(f"Maximum Drawdown: {apple.max_drawdown:.2%}")
-print(f"Max Drawdown Date: {apple.max_drawdown_date}")
-print(f"95% VaR (daily): {apple.var_down:.2%}")
-print(f"95% CVaR (daily): {apple.cvar_down:.2%}")
-print(f"Worst Single Day: {apple.worst:.2%}")
-print(f"Sortino Ratio: {apple.sortino_ratio:.2f}")
-
-
-
-
-

Time Series Transformations

-
# Convert to returns (modifies original)
-apple.value_to_ret()
-print(f"Returns series length: {apple.length}")
-
-# Create drawdown series (modifies original)
-apple.to_drawdown_series()
-
-# Convert to log returns (modifies original)
-apple.value_to_log()
-
-# Resample to monthly (modifies original)
-apple.resample_to_business_period_ends(freq="BME")
-print(f"Monthly data points: {apple.length}")
-
-
-
-
-

Rolling Analysis

-
# Rolling volatility (1-year window)
-rolling_vol = apple.rolling_vol(observations=252)
-print(f"Current 1Y volatility: {rolling_vol.iloc[-1, 0]:.2%}")
-print(f"Average 1Y volatility: {rolling_vol.mean().iloc[0]:.2%}")
-
-# Rolling returns (30-day)
-rolling_returns = apple.rolling_return(observations=30)
-
-# Rolling VaR
-rolling_var = apple.rolling_var_down(observations=252)
-
-
-
-
-

Visualization

-
# Plot price series
-fig, _ = apple.plot_series()
-
-# Plot returns histogram
-fig, _ = apple_returns.plot_histogram()
-
-# Plot drawdown series
-fig, _ = apple_drawdowns.plot_series()
-
-
-
-
-

Calendar Analysis

-
# Annual returns by calendar year
-years = [2019, 2020, 2021, 2022, 2023, 2024]
-
-print("=== CALENDAR YEAR RETURNS ===")
-for year in years:
-     # This may fail if no data exists for the year
-     year_return = apple.value_ret_calendar_period(year=year)
-     print(f"{year}: {year_return:.2%}")
-
-
-
-
-

Export Results

-
# Export to Excel
-apple.to_xlsx("apple_analysis.xlsx")
-
-# Export metrics to CSV
-all_metrics.to_csv("apple_metrics.csv")
-
-# Export to JSON
-apple.to_json("apple_data.json")
-
-
-
-
-

Complete Analysis Workflow

-

Here’s how to perform comprehensive single asset analysis using openseries methods directly:

-
import yfinance as yf
-from openseries import OpenTimeSeries
-
-# Example: Analyze Apple stock using openseries methods
-ticker_symbol = "AAPL"
-
-# Download data using openseries methods
-ticker = yf.Ticker(ticker_symbol)
-data = ticker.history(period="5y")
-
-# Create series using openseries from_df method
-series = OpenTimeSeries.from_df(
-     dframe=data['Close'],
-     name=ticker_symbol
-)
-
-# Analysis using openseries properties and methods
-print(f"=== {ticker_symbol} ANALYSIS ===")
-print(f"Period: {series.first_idx} to {series.last_idx}")
-print(f"Observations: {series.length}")
-
-# Key metrics using openseries properties
-metrics = {
-     'Total Return': f"{series.value_ret:.2%}",
-     'Annual Return': f"{series.geo_ret:.2%}",
-     'Volatility': f"{series.vol:.2%}",
-     'Sharpe Ratio': f"{series.ret_vol_ratio:.2f}",
-     'Max Drawdown': f"{series.max_drawdown:.2%}",
-     '95% VaR': f"{series.var_down:.2%}",
-     'Skewness': f"{series.skew:.2f}",
-     'Kurtosis': f"{series.kurtosis:.2f}"
-}
-
-for metric, value in metrics.items():
-     print(f"{metric}: {value}")
-
-# Export results using openseries to_xlsx method
-filename = f"{ticker_symbol.lower()}_analysis.xlsx"
-series.to_xlsx(filename)
-print(f"\nResults exported to {filename}")
-
-# Example: Analyze multiple assets
-tickers = ["AAPL", "TSLA", "MSFT"]
-for ticker_symbol in tickers:
-     ticker = yf.Ticker(ticker_symbol)
-     data = ticker.history(period="2y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=ticker_symbol)
-
-     print(f"\n{ticker_symbol}:")
-     print(f"  Return: {series.geo_ret:.2%}")
-     print(f"  Volatility: {series.vol:.2%}")
-     print(f"  Sharpe: {series.ret_vol_ratio:.2f}")
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html deleted file mode 100644 index 017e4442..00000000 --- a/docs/build/html/genindex.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - Index — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Index

- -
- -
- - -
-
-
- -
- -
-

© Copyright Captor Fund Management AB.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/index.html b/docs/build/html/index.html deleted file mode 100644 index 7f4e055e..00000000 --- a/docs/build/html/index.html +++ /dev/null @@ -1,467 +0,0 @@ - - - - - - - - - openseries Documentation — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

openseries Documentation

-PyPI version - -Conda Version - -Platform -Python version - -GitHub Action Test Suite - -codecov - -GitHub License - -

openseries is a Python library for analyzing financial time series data. It provides tools to work with single assets or groups of assets, designed specifically for daily or less frequent data.

-

The library is built around two main classes:

-
    -
  • OpenTimeSeries: For managing and analyzing individual time series

  • -
  • OpenFrame: For managing groups of time series and portfolio analysis

  • -
-
-

Key Features

-
    -
  • Financial Analysis: Comprehensive set of financial metrics and ratios

  • -
  • Risk Management: VaR, CVaR, drawdown analysis, and risk-adjusted returns

  • -
  • Portfolio Tools: Portfolio optimization, rebalancing, and performance attribution

  • -
  • Visualization: Interactive plots using Plotly

  • -
  • Data Handling: Robust date handling and business day calendars

  • -
  • Type Safety: Built with Pydantic for data validation and type safety

  • -
-
-
-

Quick Start

-

Install openseries using pip:

-
pip install openseries
-
-
-

Or using conda:

-
conda install -c conda-forge openseries
-
-
-

Here’s a simple example to get you started:

-
from openseries import OpenTimeSeries
-import yfinance as yf
-
-# Download data
-ticker = yf.Ticker("^GSPC")
-history = ticker.history(period="5y")
-
-# Create OpenTimeSeries
-series = OpenTimeSeries.from_df(dframe=history.loc[:, "Close"])
-series.set_new_label(lvl_zero="S&P 500")
-
-# Calculate key metrics
-print(f"Annual Return: {series.geo_ret:.2%}")
-print(f"Volatility: {series.vol:.2%}")
-print(f"Sharpe Ratio: {series.ret_vol_ratio:.2f}")
-print(f"Max Drawdown: {series.max_drawdown:.2%}")
-
-# Create interactive plot
-series.plot_series()
-
-
-
-
-

Documentation Contents

- - - -
-

Important Notes

- -
-
-
-

Python Version Support

- - -
-
-
-

Indices and tables

- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv deleted file mode 100644 index 5b0caf34..00000000 Binary files a/docs/build/html/objects.inv and /dev/null differ diff --git a/docs/build/html/search.html b/docs/build/html/search.html deleted file mode 100644 index 338f5cb3..00000000 --- a/docs/build/html/search.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - Search — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
-
- -
- -
-

© Copyright Captor Fund Management AB.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js deleted file mode 100644 index 07f01724..00000000 --- a/docs/build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"alltitles":{"API Consistency Notes":[[27,null]],"API Reference":[[35,null]],"Adding and Removing Series":[[41,"adding-and-removing-series"]],"Advanced Features":[[36,null]],"Advanced Optimization with Real Data":[[32,"advanced-optimization-with-real-data"]],"Advanced Risk Metrics":[[37,"advanced-risk-metrics"]],"Advanced Weight Strategies":[[38,"advanced-weight-strategies"]],"Analysis Methods":[[1,"analysis-methods"],[24,"analysis-methods"],[40,"analysis-methods"]],"Analysis Workflow":[[40,"analysis-workflow"]],"Annualization":[[40,"annualization"]],"Architecture Overview":[[40,"architecture-overview"]],"Asset Analysis":[[38,"asset-analysis"]],"Backtesting Framework":[[32,"backtesting-framework"]],"Basic Financial Analysis":[[37,null]],"Basic Financial Metrics":[[43,"basic-financial-metrics"]],"Basic Performance Metrics":[[37,"basic-performance-metrics"]],"Basic Portfolio Optimization Setup":[[32,"basic-portfolio-optimization-setup"]],"Basic Rebalanced Portfolio Setup":[[33,"basic-rebalanced-portfolio-setup"]],"Basic Risk Metrics":[[39,"basic-risk-metrics"]],"Basic Setup":[[34,"basic-setup"]],"Benefits of export_plotly_figure":[[36,"benefits-of-export-plotly-figure"]],"Best Practices":[[27,"best-practices"],[40,"best-practices"]],"Bug Reports":[[29,"bug-reports"]],"Building Documentation":[[29,"building-documentation"]],"Business Day Alignment":[[41,"business-day-alignment"]],"Business Day Calendars":[[40,"business-day-calendars"]],"CSV Data":[[41,"csv-data"]],"Calendar Analysis":[[34,"calendar-analysis"]],"Calendar Year Returns":[[37,"calendar-year-returns"]],"Cash Management Analysis":[[33,"cash-management-analysis"]],"Changelog":[[28,null]],"Class Methods for Construction":[[1,"class-methods-for-construction"],[24,"class-methods-for-construction"]],"Code Contributions":[[29,"code-contributions"]],"Code Review Process":[[29,"code-review-process"]],"Code Standards":[[29,"code-standards"]],"Code Style":[[29,"code-style"]],"Commit Messages":[[29,"commit-messages"]],"Common Issues":[[42,"common-issues"]],"Common Issues and Solutions":[[27,"common-issues-and-solutions"]],"Common Patterns":[[43,"common-patterns"]],"Common Properties":[[1,"common-properties"],[24,"common-properties"]],"Community Guidelines":[[29,"community-guidelines"]],"Comparative Analysis":[[31,"comparative-analysis"]],"Comparison with Benchmark":[[37,"comparison-with-benchmark"]],"Complete Analysis Workflow":[[34,"complete-analysis-workflow"]],"Complete Multi-Asset Analysis Workflow":[[31,"complete-multi-asset-analysis-workflow"]],"Complete Optimization Workflow":[[32,"complete-optimization-workflow"]],"Comprehensive Report":[[37,"comprehensive-report"]],"Conditional Value at Risk (CVaR)":[[39,"conditional-value-at-risk-cvar"]],"Consistency Checks":[[40,"consistency-checks"]],"Contributing Guidelines":[[29,"contributing-guidelines"]],"Contributing to openseries":[[29,null]],"Core Concepts":[[40,null]],"Core Dependencies":[[42,"core-dependencies"]],"Core Properties":[[40,"core-properties"]],"Correlation Analysis":[[31,"correlation-analysis"],[38,"correlation-analysis"]],"Correlation and Risk":[[1,"correlation-and-risk"]],"Creating Custom Plots":[[36,"creating-custom-plots"]],"Creating OpenFrame":[[41,"creating-openframe"]],"Creating Visualizations":[[43,"creating-visualizations"]],"Custom Exceptions":[[26,"custom-exceptions"]],"Custom Weight Portfolio":[[38,"custom-weight-portfolio"]],"Daily Rebalancing vs Theoretical Portfolio":[[33,"daily-rebalancing-vs-theoretical-portfolio"]],"Data Alignment":[[40,"data-alignment"]],"Data Export and Import":[[41,"data-export-and-import"]],"Data Handling":[[41,null]],"Data Immutability":[[40,"data-immutability"]],"Data Loading":[[40,"data-loading"]],"Data Manipulation":[[1,"data-manipulation"],[24,"data-manipulation"]],"Data Quality Checks":[[41,"data-quality-checks"]],"Data Transformations":[[41,"data-transformations"],[43,"data-transformations"]],"Data Validation":[[40,"data-validation"],[41,"data-validation"]],"Date Format Validation":[[41,"date-format-validation"]],"Date Handling":[[40,"date-handling"]],"Date Handling Functions":[[0,"date-handling-functions"]],"Date Utilities":[[0,null],[21,"date-utilities"]],"Debugging":[[29,"debugging"]],"Dependencies":[[42,"dependencies"]],"Detailed Portfolio Analysis":[[33,"detailed-portfolio-analysis"]],"Development":[[35,null]],"Development Environment":[[29,"development-environment"]],"Development Installation":[[42,"development-installation"]],"Development Setup":[[29,"development-setup"]],"Development Workflow":[[29,"development-workflow"]],"Different Rebalancing Frequencies":[[33,"different-rebalancing-frequencies"]],"Distribution Analysis":[[37,"distribution-analysis"]],"Docstrings":[[29,"docstrings"]],"Documentation":[[29,"documentation"]],"Documentation Contents":[[35,"documentation-contents"]],"Documentation Contributions":[[29,"documentation-contributions"]],"Documentation Standards":[[29,"documentation-standards"]],"Drawdown Analysis":[[37,"drawdown-analysis"]],"Dropping Missing Data":[[41,"dropping-missing-data"]],"Efficient Data Loading":[[41,"efficient-data-loading"]],"Efficient Frontier":[[38,"efficient-frontier"]],"Embedding Reports in Existing HTML Pages":[[30,"embedding-reports-in-existing-html-pages"]],"Equal Weight Portfolio":[[32,"equal-weight-portfolio"],[38,"equal-weight-portfolio"]],"Equal Weight vs Custom Weight Strategies":[[33,"equal-weight-vs-custom-weight-strategies"]],"Examples":[[35,null]],"Excel Export":[[41,"excel-export"]],"Export Methods":[[1,"export-methods"],[24,"export-methods"],[40,"export-methods"]],"Export Multi-Asset Results":[[31,"export-multi-asset-results"]],"Export Optimization Results":[[32,"export-optimization-results"]],"Export Results":[[34,"export-results"]],"Export Risk Report":[[39,"export-risk-report"]],"Exporting Custom Plotly Figures":[[36,"exporting-custom-plotly-figures"]],"Exporting Results":[[43,"exporting-results"]],"Factor Analysis and Regression":[[36,"factor-analysis-and-regression"]],"Feature Requests":[[29,"feature-requests"]],"File and Network Support":[[42,"file-and-network-support"]],"Financial Calculations":[[40,"financial-calculations"]],"Financial Metrics":[[1,"financial-metrics"],[24,"financial-metrics"]],"Financial Metrics Methods":[[1,"financial-metrics-methods"],[24,"financial-metrics-methods"]],"Financial and Date Utilities":[[42,"financial-and-date-utilities"]],"Frame Management":[[1,"frame-management"]],"Frame-specific Properties":[[1,"frame-specific-properties"]],"From Arrays":[[41,"from-arrays"]],"From Fixed Rate":[[41,"from-fixed-rate"]],"From pandas DataFrame/Series":[[41,"from-pandas-dataframe-series"]],"Function Parameter Names":[[27,"function-parameter-names"]],"Function Return Values":[[27,"function-return-values"]],"Get All Metrics at Once":[[43,"get-all-metrics-at-once"]],"Getting Help":[[29,"getting-help"],[42,"getting-help"]],"Getting Started":[[29,"getting-started"]],"GitHub Releases":[[28,"github-releases"]],"HTML Report Function":[[23,"html-report-function"]],"Handling Different Date Ranges":[[41,"handling-different-date-ranges"]],"Handling Missing Data":[[41,"handling-missing-data"]],"Historical Stress Testing":[[39,"historical-stress-testing"]],"IDE Setup":[[29,"ide-setup"]],"Important API Notes":[[27,"important-api-notes"]],"Important Notes":[[35,null]],"Indices and tables":[[35,"indices-and-tables"]],"Inline HTML Output":[[36,"inline-html-output"]],"Installation":[[42,null]],"Installing from source":[[42,"installing-from-source"]],"Installing openseries":[[42,"installing-openseries"]],"Inverse Volatility Portfolio":[[32,"inverse-volatility-portfolio"]],"Issue: \u201cDo not run resample_to_business_period_ends on return series\u201d":[[27,"issue-do-not-run-resample-to-business-period-ends-on-return-series"]],"Issue: \u201cTypeError: unsupported format string passed to Series.__format__\u201d":[[27,"issue-typeerror-unsupported-format-string-passed-to-series-format"]],"Issue: \u201cTypeError: \u2018DataFrame\u2019 object is not callable\u201d":[[27,"issue-typeerror-dataframe-object-is-not-callable"]],"JSON Export":[[41,"json-export"]],"Key Concepts to Remember":[[43,"key-concepts-to-remember"]],"Key Features":[[35,"key-features"]],"Length Consistency":[[41,"length-consistency"]],"Linux":[[42,"linux"]],"Literal Types":[[26,"literal-types"]],"Loading Data":[[41,"loading-data"]],"Loading Data from External Sources":[[43,"loading-data-from-external-sources"]],"Main Classes":[[21,"main-classes"]],"Managing Multiple Series":[[40,"managing-multiple-series"]],"Maximum Diversification Portfolio":[[32,"maximum-diversification-portfolio"]],"Maximum Diversification Strategy":[[38,"maximum-diversification-strategy"]],"Mean-Variance Optimization":[[32,"mean-variance-optimization"]],"Memory Management":[[40,"memory-management"]],"Memory Usage":[[41,"memory-usage"]],"Method Categories":[[40,"method-categories"]],"Method Chaining vs Object Creation":[[27,"method-chaining-vs-object-creation"]],"Method Parameter Names":[[27,"method-parameter-names"]],"Methods":[[1,"methods"],[24,"methods"]],"Metric Names in DataFrames":[[27,"metric-names-in-dataframes"]],"Minimum Volatility Overweight Portfolio":[[32,"minimum-volatility-overweight-portfolio"]],"Minimum Volatility Overweight Strategy":[[38,"minimum-volatility-overweight-strategy"]],"Monte Carlo Portfolio Simulation":[[32,"monte-carlo-portfolio-simulation"],[38,"monte-carlo-portfolio-simulation"]],"Monte Carlo Risk Simulation":[[39,"monte-carlo-risk-simulation"]],"Monthly and Annual Analysis":[[37,"monthly-and-annual-analysis"]],"Multi-Asset Analysis":[[31,null]],"Multi-Factor Model Analysis":[[36,"multi-factor-model-analysis"]],"Mutation and data layers":[[40,"mutation-and-data-layers"]],"NaN Handling Strategies":[[41,"nan-handling-strategies"]],"Next Steps":[[43,"next-steps"]],"Non-numerical Properties":[[24,"non-numerical-properties"]],"OpenFrame":[[1,null]],"OpenTimeSeries":[[24,null]],"Optional Dependencies":[[42,"optional-dependencies"]],"Other Utilities":[[21,"other-utilities"]],"Outlier Detection":[[41,"outlier-detection"]],"Performance Analysis":[[34,"performance-analysis"]],"Performance Attribution":[[31,"performance-attribution"],[33,"performance-attribution"],[38,"performance-attribution"]],"Performance Comparison Analysis":[[32,"performance-comparison-analysis"]],"Performance Considerations":[[41,"performance-considerations"]],"Platform-Specific Notes":[[42,"platform-specific-notes"]],"Portfolio Analysis":[[1,"portfolio-analysis"],[38,null],[43,"portfolio-analysis"]],"Portfolio Comparison":[[32,"portfolio-comparison"],[38,"portfolio-comparison"]],"Portfolio Constraints":[[22,"portfolio-constraints"]],"Portfolio Construction":[[40,"portfolio-construction"]],"Portfolio Creation":[[27,"portfolio-creation"]],"Portfolio Optimization":[[22,"portfolio-optimization"],[32,null],[38,"portfolio-optimization"]],"Portfolio Simulation":[[22,"portfolio-simulation"]],"Portfolio Tools":[[21,"portfolio-tools"],[22,null]],"Price and Return Conversions":[[41,"price-and-return-conversions"]],"Properties":[[1,"properties"],[24,"properties"]],"Properties vs Methods":[[27,"properties-vs-methods"],[40,"properties-vs-methods"]],"Pull Request Process":[[29,"pull-request-process"]],"Python Version Support":[[35,"python-version-support"]],"Quick Start":[[35,"quick-start"]],"Quick Start Guide":[[43,null]],"Ranking Analysis":[[31,"ranking-analysis"]],"Real-World Application Example":[[33,"real-world-application-example"]],"Real-World Fund Portfolio Optimization":[[32,"real-world-fund-portfolio-optimization"]],"Rebalanced Portfolio Simulation":[[33,null]],"Rebalancing Analysis":[[38,"rebalancing-analysis"]],"Release Notifications":[[28,"release-notifications"]],"Release Process":[[29,"release-process"]],"Report Generation":[[23,null]],"Reporting":[[30,null]],"Resampling":[[40,"resampling"],[41,"resampling"]],"Responsive Design":[[23,"responsive-design"]],"Return Calculations":[[40,"return-calculations"]],"Return Values":[[23,"return-values"]],"ReturnSimulation Class":[[25,"returnsimulation-class"]],"Returns:":[[1,"returns"],[1,"id1"],[1,"id2"],[1,"id3"],[1,"id4"],[1,"id5"],[1,"id6"],[1,"id7"],[1,"id8"],[1,"id9"],[1,"id10"],[1,"id11"],[1,"id12"],[1,"id13"],[1,"id14"],[1,"id15"],[1,"id16"],[1,"id17"],[1,"id18"],[1,"id19"],[1,"id20"],[1,"id21"],[24,"returns"],[24,"id1"],[24,"id2"],[24,"id3"],[24,"id4"],[24,"id5"],[24,"id6"],[24,"id7"],[24,"id8"],[24,"id9"],[24,"id10"],[24,"id11"],[24,"id12"],[24,"id13"],[24,"id14"],[24,"id15"],[24,"id16"],[24,"id17"],[24,"id18"],[24,"id19"],[24,"id20"],[24,"id21"]],"Risk Analysis":[[34,"risk-analysis"],[37,"risk-analysis"]],"Risk Attribution":[[38,"risk-attribution"]],"Risk Decomposition":[[39,"risk-decomposition"]],"Risk Limits and Controls":[[39,"risk-limits-and-controls"]],"Risk Management":[[39,null]],"Risk Metrics":[[40,"risk-metrics"]],"Risk Monitoring Dashboard":[[39,"risk-monitoring-dashboard"]],"Risk Parity Portfolio":[[38,"risk-parity-portfolio"]],"Risk-Adjusted Performance":[[39,"risk-adjusted-performance"]],"Risk-Adjusted Returns":[[37,"risk-adjusted-returns"]],"Risk-Based Portfolio Strategies":[[32,"risk-based-portfolio-strategies"]],"Risk-Return Analysis":[[31,"risk-return-analysis"]],"Rolling Analysis":[[1,"rolling-analysis"],[34,"rolling-analysis"],[37,"rolling-analysis"]],"Rolling Factor Analysis":[[36,"rolling-factor-analysis"]],"Rolling Portfolio Analysis":[[38,"rolling-portfolio-analysis"]],"Rolling Risk Analysis":[[39,"rolling-risk-analysis"]],"Running Tests":[[29,"running-tests"]],"Scenario Analysis":[[39,"scenario-analysis"]],"Sector/Style Analysis":[[31,"sector-style-analysis"]],"Setting Up":[[37,"setting-up"]],"Setting Up Multi-Asset Analysis":[[31,"setting-up-multi-asset-analysis"]],"Setting Up Risk Analysis":[[39,"setting-up-risk-analysis"]],"Setting Up the Data":[[38,"setting-up-the-data"]],"Simple Portfolio Construction":[[38,"simple-portfolio-construction"]],"Simulation":[[21,"simulation"],[25,null]],"Single Asset Analysis":[[34,null]],"Statistical Analysis":[[1,"statistical-analysis"]],"Strategy Comparison with Error Handling":[[38,"strategy-comparison-with-error-handling"]],"Stress Testing":[[31,"stress-testing"],[38,"stress-testing"],[39,"stress-testing"]],"Subset Portfolio Analysis":[[33,"subset-portfolio-analysis"]],"Summary Report":[[38,"summary-report"]],"Summary and Best Practices":[[33,"summary-and-best-practices"]],"Summary and Interpretation":[[37,"summary-and-interpretation"]],"System Requirements":[[42,"system-requirements"]],"Test Coverage":[[29,"test-coverage"]],"Test Structure":[[29,"test-structure"]],"Testing":[[29,"testing"]],"The OpenFrame Class":[[40,"the-openframe-class"]],"The OpenTimeSeries Class":[[40,"the-opentimeseries-class"]],"Time Series Analysis":[[31,"time-series-analysis"]],"Time Series Transformations":[[34,"time-series-transformations"]],"Transaction Cost Analysis":[[33,"transaction-cost-analysis"]],"Transformation Methods":[[40,"transformation-methods"]],"Transformations":[[1,"transformations"],[24,"transformations"]],"Troubleshooting":[[42,"troubleshooting"]],"Tutorials":[[35,null]],"Type Aliases":[[26,"type-aliases"]],"Type Hints":[[29,"type-hints"]],"Type Safety":[[40,"type-safety"]],"Types and Enums":[[21,"types-and-enums"],[26,null]],"Types of Contributions":[[29,"types-of-contributions"]],"Understanding Rebalanced Portfolio Simulation":[[33,"understanding-rebalanced-portfolio-simulation"]],"User Guide":[[35,null]],"Using Real Fund Data for Optimization":[[32,"using-real-fund-data-for-optimization"]],"Using conda":[[42,"using-conda"]],"Using pip (recommended)":[[42,"using-pip-recommended"]],"Using the Built-in HTML Report":[[30,"using-the-built-in-html-report"]],"Using with Plotly Express":[[36,"using-with-plotly-express"]],"Utility Functions":[[21,"utility-functions"],[24,"utility-functions"]],"Validation Classes":[[26,"validation-classes"]],"Validation Methods":[[41,"validation-methods"]],"Value Types":[[26,"value-types"],[40,"value-types"]],"Value Validation":[[41,"value-validation"]],"Value at Risk (VaR) Analysis":[[39,"value-at-risk-var-analysis"]],"ValueType Specification":[[27,"valuetype-specification"]],"Verifying Installation":[[42,"verifying-installation"]],"Visualization":[[1,"visualization"],[22,"visualization"],[24,"visualization"],[34,"visualization"],[37,"visualization"]],"Weight Strategy Details":[[32,"weight-strategy-details"]],"Windows":[[42,"windows"]],"Working with Business Days":[[43,"working-with-business-days"]],"Working with Multiple Assets":[[41,"working-with-multiple-assets"]],"Working with Multiple Assets (OpenFrame)":[[43,"working-with-multiple-assets-openframe"]],"Working with Real Data Sources":[[41,"working-with-real-data-sources"]],"Writing Tests":[[29,"writing-tests"]],"Yahoo Finance Integration":[[41,"yahoo-finance-integration"]],"Your First OpenTimeSeries":[[43,"your-first-opentimeseries"]],"macOS":[[42,"macos"]],"openseries Documentation":[[35,null]],"openseries package":[[21,null]],"openseries.OpenFrame":[[2,null]],"openseries.OpenTimeSeries":[[3,null]],"openseries.ReturnSimulation":[[4,null]],"openseries.ValueType":[[5,null]],"openseries.constrain_optimized_portfolios":[[6,null]],"openseries.date_fix":[[7,null]],"openseries.date_offset_foll":[[8,null]],"openseries.efficient_frontier":[[9,null]],"openseries.export_plotly_figure":[[10,null]],"openseries.generate_calendar_date_range":[[11,null]],"openseries.get_previous_business_day_before_today":[[12,null]],"openseries.holiday_calendar":[[13,null]],"openseries.load_plotly_dict":[[14,null]],"openseries.offset_business_days":[[15,null]],"openseries.prepare_plot_data":[[16,null]],"openseries.report_html":[[17,null]],"openseries.sharpeplot":[[18,null]],"openseries.simulate_portfolios":[[19,null]],"openseries.timeseries_chain":[[20,null]]},"docnames":["api/datefixer","api/frame","api/generated/openseries.OpenFrame","api/generated/openseries.OpenTimeSeries","api/generated/openseries.ReturnSimulation","api/generated/openseries.ValueType","api/generated/openseries.constrain_optimized_portfolios","api/generated/openseries.date_fix","api/generated/openseries.date_offset_foll","api/generated/openseries.efficient_frontier","api/generated/openseries.export_plotly_figure","api/generated/openseries.generate_calendar_date_range","api/generated/openseries.get_previous_business_day_before_today","api/generated/openseries.holiday_calendar","api/generated/openseries.load_plotly_dict","api/generated/openseries.offset_business_days","api/generated/openseries.prepare_plot_data","api/generated/openseries.report_html","api/generated/openseries.sharpeplot","api/generated/openseries.simulate_portfolios","api/generated/openseries.timeseries_chain","api/openseries","api/portfoliotools","api/report","api/series","api/simulation","api/types","api_consistency","development/changelog","development/contributing","examples/custom_reports","examples/multi_asset","examples/portfolio_optimization","examples/rebalanced_portfolio","examples/single_asset","index","tutorials/advanced_features","tutorials/basic_analysis","tutorials/portfolio_analysis","tutorials/risk_management","user_guide/core_concepts","user_guide/data_handling","user_guide/installation","user_guide/quickstart"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1},"filenames":["api/datefixer.rst","api/frame.rst","api/generated/openseries.OpenFrame.rst","api/generated/openseries.OpenTimeSeries.rst","api/generated/openseries.ReturnSimulation.rst","api/generated/openseries.ValueType.rst","api/generated/openseries.constrain_optimized_portfolios.rst","api/generated/openseries.date_fix.rst","api/generated/openseries.date_offset_foll.rst","api/generated/openseries.efficient_frontier.rst","api/generated/openseries.export_plotly_figure.rst","api/generated/openseries.generate_calendar_date_range.rst","api/generated/openseries.get_previous_business_day_before_today.rst","api/generated/openseries.holiday_calendar.rst","api/generated/openseries.load_plotly_dict.rst","api/generated/openseries.offset_business_days.rst","api/generated/openseries.prepare_plot_data.rst","api/generated/openseries.report_html.rst","api/generated/openseries.sharpeplot.rst","api/generated/openseries.simulate_portfolios.rst","api/generated/openseries.timeseries_chain.rst","api/openseries.rst","api/portfoliotools.rst","api/report.rst","api/series.rst","api/simulation.rst","api/types.rst","api_consistency.rst","development/changelog.rst","development/contributing.rst","examples/custom_reports.rst","examples/multi_asset.rst","examples/portfolio_optimization.rst","examples/rebalanced_portfolio.rst","examples/single_asset.rst","index.rst","tutorials/advanced_features.rst","tutorials/basic_analysis.rst","tutorials/portfolio_analysis.rst","tutorials/risk_management.rst","user_guide/core_concepts.rst","user_guide/data_handling.rst","user_guide/installation.rst","user_guide/quickstart.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"0f":39,"10i":37,"11articlecox":[1,2],"1e":29,"1f":[37,40],"1y":[34,41],"2f":[31,32,33,34,35,37,38,39,40,43],"2x2":23,"2y":[34,41,43],"3b":43,"3f":[29,31,32,36,38,39],"3y":[30,31,33,36,39],"4f":[32,33,36,38,39],"5th":39,"5y":[32,34,35,37,38],"75th":31,"960px":23,"A":[0,1,2,3,6,14,15,22,24,26,41],"ALL":34,"AT":39,"All":29,"An":[1,2,3,20,24],"And":[1,2],"At":[1,3,24,26,29],"BOTH":[1,2],"Be":29,"Both":[21,40],"By":26,"Down":[1,2],"For":[1,21,23,24,28,29,31,32,35,36,39,41,42,43],"Here":[31,32,33,34,35,43],"How":[1,10,24],"If":[0,1,2,3,7,10,13,15,23,24,29,40,42],"It":[0,1,2,3,15,24,25,30,33,35],"MOST":31,"Most":[1,24,32,42],"My":[10,27,30],"NOT":27,"No":[27,31,39,42],"On":[29,40,42],"Or":[27,35],"Other":35,"Some":27,"The":[0,1,2,3,4,6,7,8,9,12,15,16,18,19,20,21,22,23,24,25,26,27,29,30,32,33,35,36,38,41,42],"These":[3,24,27],"They":[26,27],"This":[1,2,4,10,21,25,26,27,29,30,31,32,33,34,36,37,38,39,40,41,42,43],"To":[1,2,27,29,42],"Up":[1,2,35],"We":29,"When":[10,23,27,29,30,32,38,41],"Which":[18,22],"With":[27,41],"You":[36,37,38,42],"Your":35,"_":[32,34],"__init__":[1,2,3,4,5,26,29],"__metadata__":26,"__version__":42,"_analysi":34,"_build":29,"_commonmodel":[1,2,3,21,24],"aapl":[30,31,34,36,39,41],"ab":[29,32,33,37,38,39],"abov":[1,2,24,26],"absolut":[1,24,33,41],"acceler":42,"accept":[1,24],"access":27,"accord":[0,3,4,8,11,12,13,15,24,25],"account":33,"accru":[3,24],"accrual":[3,24,33],"acf":[1,3,24],"acquisit":42,"across":[1,2,3,24,33,40],"activ":[33,39,42],"actual":[29,32,33,37,40],"actual_length":41,"actual_return":29,"adapt":[1,23,24,36,37],"add":[0,1,2,3,8,11,12,13,15,18,22,24,29,32,36,38,39],"add_logo":[1,17,18,22,23,24,32],"add_timeseri":[1,2,41,43],"add_trac":36,"addit":[29,38,42],"address":29,"adj_r_squar":36,"adjac":[1,24],"adjust":[0,1,2,3,8,15,24,35,36],"advanc":35,"advantag":36,"agg":38,"aggreg":38,"aim":32,"alert":39,"alia":26,"alias":35,"align":[1,2,3,24,26,38,43],"align_index_to_local_cday":[1,24,33,40,41,43],"all_asset":39,"all_metr":[27,31,34,37,43],"all_portfolio":38,"all_properti":[1,2,3,24,27,31,32,33,34,37,38,39,40,41,43],"all_seri":38,"alloc":[6,9,22,43],"allow":[1,2,3,4,6,9,22,24,25,26,36],"allowed_str":26,"almost":[9,22],"along":[1,9,22,24],"alongsid":[1,24],"alpha":[0,1,2,3,4,8,11,12,13,15,23,24,25],"alphabet":[31,39],"alreadi":[],"also":[1,2,26,27,36,38,40,42],"alter":[3,24],"altern":[32,39,43],"alway":[1,2,3,4,24,25,29,32,40],"amazon":31,"among":32,"amzn":[31,41],"analys":[3,24],"analysi":[0,21,22,23,25,30,35,41,42],"analysis_fram":36,"analyz":[24,31,33,34,35,37,38,39,43],"ani":[1,3,4,24,26,29,36,37,38],"annot":[1,2,3,4,24,25,26],"annual":[1,2,4,23,24,25,31,32,33,34,35,38,39,41,42,43],"annual_return":[37,40],"annual_vol":40,"anomali":32,"anoth":[1,24],"anyth":[0,15,26],"api":[21,29,32,40,43],"append":[29,30,31,32,33,36,38,39,41,43],"appl":[30,31,34,36,39,41,42],"apple_analysi":34,"apple_data":34,"apple_drawdown":34,"apple_metr":34,"apple_return":34,"appli":[20,24,32,40],"applic":[32,35,36],"approach":[29,32,33,39],"appropri":[29,41],"approv":29,"approxim":[29,33],"ar1_coef":[4,25],"arbitrary_types_allow":[1,2,3,4,24,25],"architectur":35,"area":29,"aren":36,"arg":[26,29],"argument":[0,3,4,8,11,12,13,15,26,27],"arithmet":[1,2,4,24,25,37],"arithmetic_ret":[1,2,3,24,26,37],"arithmetic_ret_func":[1,24],"arithmetic_return":37,"arithmeticmean":[1,24],"around":[35,40],"array":[3,24,29,40],"articl":[1,2,3,24],"ascend":31,"ascii_on":[3,24,26],"ask":29,"asp":[1,2,3,24],"assert_frame_equ":29,"assess":[37,39],"asset":[1,2,6,9,16,21,22,23,24,27,30,32,33,35,36,37,39,40],"asset1":[31,38],"asset2":[31,38],"asset_a":36,"asset_b":36,"asset_column":[1,2],"asset_comparison":36,"asset_data":31,"asset_group":31,"asset_metr":[31,38,39],"asset_mov":39,"asset_nam":[32,33,38],"asset_param":33,"asset_perform":33,"asset_return":38,"asset_vol":[38,39],"assign":32,"associ":[3,24],"assum":[1,2,3,20,24,37,38,39,41],"assumpt":[1,24],"asymmetri":37,"atleastoneframeerror":26,"attempt":26,"attent":27,"attribut":[1,2,3,4,5,26,35,43],"auto":[10,36],"auto_open":[1,10,17,18,22,23,24,32,36],"autocorr":[1,2,3,24,26],"autocorr_func":[1,24],"autocorrel":[1,3,4,24,25],"autom":29,"automat":[1,23,24,33,36,40,41,42,43],"autoregress":[4,25],"avail":[1,2,3,24,32,33,36,38,39,40,42],"averag":[1,2,3,4,24,25,31,33,34,36,38,39],"avg":31,"avg_corr":38,"avg_correl":[31,38],"avg_return":31,"avg_sharp":31,"avg_stress_return":31,"avg_vol":31,"avoid":[3,24,27],"axi":[1,24,38],"b":[1,2,3,24,26,29,41],"back":[20,24,41],"backfil":[1,2,3,24,26],"backtest":[33,35],"backtest_result":32,"backward":[0,8,12,29],"bal_weight":[1,2,33,38],"bar":[1,17,23,24,26,36,37,43],"bar_freq":[17,23],"bargap":[1,24],"bargroupgap":[1,24],"barmod":[1,24],"base":[1,2,3,4,5,16,22,24,25,26,29,35,37,39,43],"base_column":[1,2],"base_zero":[1,2],"basecci":[3,24,32],"baselin":32,"basemodel":[4,21,25,26,40],"basic":[29,35,38,40],"basket":[1,2],"batch":[1,41],"befor":[26,27,29,40],"behav":26,"behavior":29,"behind":40,"benchmark":[1,2,23,30,35],"benefit":[26,31,38],"best":[29,32,35,38,39],"best_idx":39,"best_strategi":32,"beta":[1,2,5,23,26,36,40],"better":[23,31,42],"beyond":[38,39],"bfgs":26,"bfill":[1,2,3,24,26],"bias":[1,2,3,24],"bin":[1,24,29,42],"bind":26,"bme":[1,2,3,24,26,34,37,40,41,43],"bnd":32,"bodi":[23,30],"bond":[32,33,36,37,38],"bond_data":37,"bond_tick":37,"bool":[0,1,2,3,8,9,10,14,17,18,22,23,24],"boolean":[3,24],"bothstartandenderror":26,"bottleneck":42,"bought":[1,2],"bound":[1,6,9,22,24,26],"box":[1,3,24],"bqe":[1,2,3,24,26,40,41],"branch":29,"breach":39,"break":[29,37],"breakpoint":23,"brownian":[4,25],"browser":[1,10,18,22,23,24,37,43],"build":[40,42],"built":[27,29,32,35,40,41,43],"bump":[0,12,15,29],"busdaycalendar":[0,13],"busi":[0,1,2,3,8,11,12,13,15,24,29,35],"buy":33,"buysell_qti":[33,38],"bye":[1,2,3,17,23,24,26,37,41],"byte":26,"c":[1,2,24,35,41,42],"cagr":[1,2,23,24,37,43],"calc_rang":[1,24],"calcul":[0,1,2,3,21,24,26,29,31,32,33,35,36,37,38,39,43],"calculate_return":29,"calculate_sharpe_ratio":29,"calendar":[0,1,2,3,8,11,13,24,29,35,41,42,43],"call":[3,24,27],"calmar":32,"can":[0,3,8,10,11,12,13,15,23,24,26,27,30,31,32,33,36,37,38,40,42,43],"cap":[31,36],"capabl":[21,23,29,43],"capit":[1,2],"capm":[1,2],"captor":[1,14,18,22,24,32],"captorab":42,"captorlogotyp":14,"captur":[1,2,23],"capture_ratio_func":[1,2],"captureratio":[1,2],"carlo":35,"case":[1,2,3,24,29,39,43],"cash":[1,2,35],"cash_analysi":33,"cash_index":[1,2],"cash_pct":33,"cash_posit":33,"cash_seri":33,"categori":35,"caught":40,"cci":26,"cd":[29,42],"cdn":[1,10,17,18,22,23,24,26,36],"certain":32,"cg":26,"chain":[20,24,40],"chang":[1,10,24,28,29,32,36,37,38,39,40,41],"changelog":[29,35],"characterist":[31,37,38,39],"chart":[23,36,37,43],"check":[27,29,35,39,42],"checker":26,"checkout":29,"chi":[3,24],"choic":40,"chosen":[1,2,3,24],"ci":42,"class":[2,3,4,5,29,35,43],"classmethod":[3,4,24,25],"classvar":26,"clean":[40,41],"clear":29,"clone":[29,42],"close":[5,26,27,30,31,32,33,34,35,36,37,38,39,40,41,43],"closer":33,"cluster":[1,24],"co":[1,2],"cobyla":26,"code":[0,1,3,4,8,11,12,13,15,24,25,42],"coeffici":[1,2,4,25,36],"col":[32,36],"collect":[1,40],"colour":[18,22],"column":[1,2,3,24,31,38,39,40,41],"column_nmbr":[3,24,40],"columns_lvl_on":[1,2],"columns_lvl_zero":[1,2,32,40],"com":[1,2,3,24,29,31,42],"combin":[3,24,26],"comment":39,"commit":42,"commod":[33,38],"common":[0,2,20,21,31,35,38,40,41],"compar":[1,2,23,24,32,33,35,37,38,39,40,43],"comparison":[1,2,3,21,23,24,29,30,31,33,35,36,39,43],"comparison_fram":[30,32,33,37,38,39],"comparison_metr":[32,33,37,38],"comparison_seri":33,"compat":[23,29,42],"compil":42,"complet":[23,35,38,39,43],"compon":[1,2,24],"compound":[1,24],"comprehens":[23,24,29,31,32,33,34,35,38,39,41,43],"comput":[1,3,24,41,42],"concaten":40,"concentr":39,"concept":35,"concis":29,"conda":[28,29,35],"condit":[1,24,29,35,37,40],"conditional_value_at_risk":[1,24],"confid":[37,39,40],"confidence_level":39,"config":[1,2,3,4,10,14,24,25,26,32],"config_and_layout":14,"configdict":[1,2,3,4,24,25,26],"configur":[1,2,3,4,24,25,26,29,32],"conflict":42,"conform":[1,2,3,4,24,25,26],"consecut":39,"consid":[29,33,40,41,42],"consider":35,"consist":[21,35,36],"consol":29,"constitu":[1,2,3,19,22,24,30,31,32,33,36,37,38,39,40,41,43],"constr":26,"constrain":[6,22,26],"constrain_optimized_portfolio":22,"constraint":35,"construct":[26,29,32,35,43],"contain":[1,2,10,21,23,24,30,33,36],"content":23,"contravari":26,"contrib":38,"contribut":[33,35,38,39,42],"control":[1,2,3,24,35],"conveni":[3,24],"convert":[1,2,3,10,24,27,31,32,34,37,38,39,40,41,42,43],"coordin":[1,24],"copi":[1,2,3,24,31,40,43],"core":[3,24,26,35],"corp":[31,39],"corr":[31,38],"corr_pair":31,"corr_scal":[1,2],"correct":[3,24,26,27,42],"correl":[2,5,26,32,35,36,37,39,43],"correl_matrix":[1,2,27,31,32,37,38,39,43],"correlation_matrix":[31,37,38,39],"cost":[35,38],"count":[38,39],"countri":[0,1,3,4,8,11,12,13,15,24,25,26,33,40,41,43],"countriesnotstringnorliststrerror":[0,13,26],"countriestyp":[0,1,3,4,8,11,12,13,15,24,25,26],"countryinput":26,"countrysettyp":26,"countrystringtyp":26,"cov":[29,42],"covari":26,"cover":[36,41],"coverag":42,"cox":[1,2],"crash":39,"creat":[1,2,3,4,18,22,23,24,25,26,27,29,30,31,32,33,34,35,37,38,39,40,42],"creation":29,"criteria":31,"crucial":38,"css":[1,10,23,24,30,36],"csv":34,"cubic":[1,24],"cumprod":38,"cumsum":36,"cumul":[1,2,3,23,24,38,40,41,42,43],"cumulative_contrib":38,"currenc":[3,24,26,32],"currencystringtyp":[3,24,26],"current":[6,16,18,22,29,31,32,34,36,37,39],"current_d":39,"current_metr":39,"current_portfolio":32,"current_portfolio_df":32,"curv":[1,24],"curve_point":[6,22],"curve_typ":[1,24],"custom":[23,27,29,30,35,40,41,43],"custom_dashboard":36,"custom_df":43,"custom_fram":43,"custom_holiday":[0,1,8,11,12,13,15,24],"custom_portfolio":[38,43],"custom_report":30,"custom_weight":[33,38],"custom_weight_portfolio":33,"cut":[9,22],"cvar":[1,5,24,26,34,35,37,40,43],"cvar_90":37,"cvar_95":[37,40],"cvar_99":37,"cvar_down":[1,2,3,24,26,34,37,39,40,43],"cvar_down_func":[1,24,37,39],"cvar_valu":39,"d":[1,3,4,24,25,26,37,39],"d_rang":[3,24],"daili":[1,2,3,23,24,34,35,37,38,39,40,41,42,43],"daily_var_95":39,"dashboard":[35,36],"data":[0,2,3,4,6,7,9,10,16,17,18,19,21,22,23,25,27,29,30,31,33,34,35,36,37,39,42],"data_typ":33,"databas":[3,24],"datafram":[1,2,3,4,9,16,18,19,22,24,25,26,31,32,36,39,40],"dataset":[40,41,42],"date":[1,2,3,4,7,8,11,12,13,15,20,23,24,25,26,29,32,33,34,35,36,37,38,39,43],"date_fix":0,"date_list":40,"date_offset_fol":0,"date_rang":[3,24,36,41],"datealignmenterror":[1,24,26],"datefix":[0,29],"datelisttyp":[3,24,26],"datestringtyp":26,"datetim":[0,1,7,24,26,33,37,39,40,41,42,43],"datetime64":26,"datetimeindex":[3,24,41],"datetyp":[0,7,8,26],"dateutil":42,"day":[0,1,2,3,4,8,11,12,15,24,25,26,29,31,33,34,35,37,38,39],"day_chunk":[1,2,3,24],"days_in_year":[3,24],"daysinyeartyp":[1,2,3,4,24,25,26],"dbc":38,"dd":[3,24,31,33,38],"ddate":[0,15],"dec1":26,"dec2":26,"declar":26,"decomposit":35,"dedic":26,"deepcopi":40,"def":29,"default":[0,1,2,3,4,6,8,9,10,11,12,13,14,15,18,20,22,23,24,25,26,27,29,36],"defaultinterpreterpath":29,"defin":[1,24,31,32,38,39],"definit":26,"del":40,"delbert":[1,2],"delet":[1,2,3,24],"delete_lvl_on":[3,24],"delete_timeseri":[1,2,41],"demean":[1,24],"demonstr":[30,32,33,34,36,37,38,39],"denomin":[1,2],"densiti":26,"depend":[1,2,23,29,35,36],"dependent_column":[1,2],"dependent_variable_idx":36,"deriv":[1,10,24],"describ":29,"descript":[29,34,37,40,43],"design":[21,27,35,40],"desir":26,"desktop":[1,23,24,30,36],"detail":[1,2,23,26,28,29,31,35,38,43],"detailed_portfolio":[33,38],"detect":[1,23,24,40],"determin":[0,1,2,3,8,18,22,24],"dev":[1,24,29],"develop":32,"deviat":[1,2,4,24,25,37,39,40],"devic":[1,23,24,36],"df":[36,41],"dframe":[3,4,24,25,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"diagnost":24,"dict":[1,2,14,24],"dictionari":[1,2,3,4,10,24,25,26],"differ":[0,1,2,3,7,24,27,29,31,32,35,36,37,38,39,40],"diffus":[4,25],"dir":[1,24],"direct":[23,31,32,34,36],"directori":[1,17,18,22,23,24,29],"directorypath":[1,18,22,24],"discuss":29,"display":[1,10,23,24,27,30,32,43],"displaymodebar":36,"distribut":[1,4,24,25,32,35,36,39,42,43],"div":[1,10,23,24,26,30,32,36,40],"div_id":10,"diversif":40,"diversifi":[38,39],"divid":[1,2,3,24],"divisor":[3,24],"dlta_degr_freedm":[1,2,3,24],"doc":29,"doctyp":[23,30],"document":[23,36,42,43],"doe":[0,1,15,24,40],"doesn":27,"dogleg":26,"domest":[3,24],"don":[1,2,27],"download":[31,33,34,35,37,38,39,41,43],"downsid":[1,24,37,39,40],"downside_devi":[1,2,3,24,26,37,39],"downside_vol":37,"downstream":[1,24],"draw":[1,24],"drawdown":[1,24,27,31,32,33,34,35,36,38,39,40,43],"drift":33,"drift_adjust":[1,24],"drop":[1,24,26],"drop_extra":[1,2,33,38],"dropna":41,"dt":[0,1,2,3,4,7,8,11,12,15,24,25,33,40,42,43],"due":26,"dummi":36,"dump":[1,24],"duplic":26,"dure":[1,2,31,38],"e":[1,24,26,40,42],"eaf":38,"earli":41,"earlier":[1,20,24],"early_seri":41,"easiest":42,"econom":[1,2],"edg":29,"educ":[1,2],"eem":38,"efa":[36,38],"effect":[27,40,41],"effici":[6,9,18,22,31,32],"efficient_fronti":[16,18,22,27,32,38],"efficient_threshold":31,"efram":[9,22,27,32,38],"either":[1,24],"elif":[33,37,39],"els":[31,32,33,37,39],"emb":30,"embed":[23,35,36],"emerg":[32,38],"empathi":29,"empti":[1,14,24,26,29],"en":[1,24],"enabl":29,"encod":30,"encount":[32,42],"end":[0,1,2,3,4,11,24,25,26,33,40,41,42,43],"end_cut":[1,2],"end_dat":[33,41],"end_dt":[3,24],"endyear":[0,13],"energi":32,"enforc":41,"engin":37,"enhanc":29,"ensur":[29,40,42,43],"entir":41,"enum":[5,35,40,43],"enumer":[31,32,33,36,38,39],"environ":42,"eq_weight":[1,2,26,27,31,32,33,38,39,40,43],"equal":[1,2,31,35,39,40,43],"equal_weight":[1,2,31,33,38,39],"equal_weight_portfolio":[32,33,38],"equal_weight_portfolio_df":[32,38],"equiti":[32,33],"equival":[1,24],"error":[1,2,23,29,33,40,41,42],"especi":29,"estat":32,"etc":[26,29,43],"etf":[32,39],"etf_tick":32,"evalu":[33,39],"event":[38,39],"everi":40,"everyth":29,"ewma":[1,2,3,5,24,26],"ewma_risk":[1,2],"ewma_var":[5,26],"ewma_var_func":[3,24],"ewma_vol":[5,26],"ewma_vol_func":[3,24],"exact":26,"examin":[33,37],"exampl":[10,29,30,31,32,34,36,40,42,43],"exceed":[1,24],"excel":[1,24,31,32,34,37,38,39,42,43],"excel_writ":37,"excelwrit":39,"except":[24,32,35,36,38],"exchang":42,"exchange_calendar":[0,1,3,4,8,11,12,13,15,24,25],"exclud":[3,9,22,24,33],"execut":33,"exist":[1,23,24,26,29,31,34,35,37,40],"exogen":[1,2],"expect":[27,29,32,37,38,39,40,41],"expected_length":41,"expected_return":29,"explain":40,"explan":29,"explicit":[3,4,26,27,40],"explor":43,"exponenti":[1,2,3,24],"export":[10,35,38],"extend":[36,38],"extens":[1,24,29],"extern":35,"extra":26,"extract":[33,39],"extrem":[37,39],"extreme_events_1pct":39,"extreme_events_5pct":39,"f":[27,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"factor":[1,2,3,24,35],"factor_nam":36,"factor_seri":36,"factor_tick":36,"factori":[3,24],"fail":[31,32,33,34,36,37,38,39,40,41],"failur":38,"fall":40,"fals":[0,1,2,3,8,10,17,23,24,26,31,32,33,36,38,39],"fama":36,"faster":42,"fat":[37,39],"favor":40,"featur":23,"fee":[3,20,24],"feedback":29,"ffill":[1,2,3,24,26],"field":[3,4],"fieldinfo":[1,2,4,25,26],"fig":[10,34,36],"fig_config":[10,36],"figdict":32,"figur":[1,10,17,18,22,23,24,30,35],"fiilenam":40,"file":[1,10,17,18,22,23,24,26,29,30,36,38,40],"fileexistserror":[1,24],"filenam":[1,10,17,18,22,23,24,30,34,36,37,41,43],"filepath":30,"fill":[1,24,26,40,41,43],"filter":[27,31],"filterwarn":39,"final":[33,38],"final_contrib":38,"final_twr":33,"financ":[1,2,42],"financi":[0,3,4,21,23,25,29,34,35,36],"find":[0,12,31,32,38],"finit":41,"first":[0,1,2,13,15,24,29,31,35,37,38,39,40],"first_column":[1,2],"first_idx":[1,3,24,26,29,31,32,33,34,37,38,39,40,41,43],"first_indic":[1,2,26],"fit":[1,2],"fitted_seri":[1,2],"fix":[3,24,29],"fixerd":[0,7],"flag":[3,14,24],"flight":39,"float":[1,2,3,4,6,9,20,22,24,25,26,29,32,40],"float64":[9,16,22],"focus":[29,31,38,40],"folder":[1,24],"follow":[0,8,29,40,42],"forg":[28,29,35,42],"fork":29,"form":[3,4],"format":[0,1,2,7,24,26,29,36,40,42],"formatted_export":41,"formula":40,"forward":[0,8,41,43],"found":[0,1,2,12,24,41],"foundat":[37,39,43],"frame":[2,26,27,30,31,32,33,36,37,38,39,40,41,43],"frame_outli":41,"frame_valu":41,"framework":[35,38,42],"free":[1,24,29],"french":36,"freq":[1,2,3,24,33,34,37,38,40,41,43],"frequenc":[1,2,3,24,35,38,40,41],"frequency_nam":[33,38],"frequent":[3,24,35],"fresh":[42,43],"friction":33,"from_1d_rate_to_cumret":[3,24],"from_array":[3,24,29,32,37,40,41],"from_dat":[1,2,3,24],"from_deepcopi":[1,2,3,24,27,37,40,43],"from_df":[3,24,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"from_dt":[1,24],"from_fixed_r":[3,24,41],"from_gbm":[4,25],"from_lognorm":[4,25,33,40,42,43],"from_merton_jump_gbm":[4,25],"from_norm":[4,25],"front":[20,24],"frontier":[6,9,22,32],"frontier_df":[27,32,38],"frontier_point":[9,22,32],"full":[10,24,33,40,41],"full_portfolio":33,"fulli":[1,24],"function":[1,2,3,6,9,10,16,20,22,26,29,30,35,36,39],"fund":[1,2,33,35],"fund_univers":32,"fund_universe_isin":32,"fundament":[37,39,40],"g":[1,24,26,40],"gap":[1,24,41],"gb":[40,41,43],"ge":[1,2,4,25,26],"general":[1,24,32],"generat":[0,1,3,4,11,13,17,19,22,24,25,30,32,33,35,36,37,38,39,41],"generate_calendar_date_rang":0,"generic":26,"geo_ret":[1,2,3,24,26,27,31,32,33,34,35,37,38,39,40,42,43],"geo_ret_func":[1,24],"geometr":[4,25,27,31,33,38,40],"get":[27,31,32,33,34,35,37,38,39],"get_previous_business_day_before_today":[0,32],"git":[29,42],"github":[29,42],"give":43,"given":[0,1,2,3,8,15,16,22,24],"gld":[33,38,39],"global":32,"go":[10,29,36],"goff":[1,2],"gold":[33,38,39],"good":[32,37],"googl":[29,30,31,39,41],"got":[29,41],"grace":38,"graph":36,"graph_object":[10,36],"greater":[0,1,11,24],"grid":23,"group":[1,24,26,31,35],"group_asset":31,"group_fram":31,"group_metr":31,"group_nam":31,"group_seri":31,"growth":[1,24,31],"gspc":[35,36,37,38,43],"gt":[4,25],"guid":[29,41],"h1":30,"handl":[1,10,21,24,27,32,33,35,36,43],"happen":[4,25],"head":[23,30],"header":36,"healthcar":32,"heavi":37,"heavili":[1,24],"height":[32,36],"help":[27,40,41],"high":[29,31,32,33,38,39],"higher":[26,32,33,39,42],"highest":31,"highlight":[1,18,22,24],"histnorm":[1,24],"histogram":[1,24,34,36,37,43],"histori":[30,31,32,33,34,35,36,37,38,39,41,43],"hold":[1,2,3,4,24,25],"holiday":[0,1,8,11,12,13,15,24,42],"holiday_calendar":0,"home":[3,24],"hook":[29,42],"horizon":39,"howev":26,"html":[1,10,17,18,22,24,29,35],"html_div":[30,36],"html_templat":30,"html_util":10,"http":42,"https":[1,2,3,24,29,32,42],"iana":42,"id":23,"idea":29,"ideal":36,"identifi":[1,3,5,9,22,24,26,31,38,39,40,43],"idx":[32,38],"idxmax":[32,38],"idxmin":32,"ignor":[10,36,39],"iloc":[3,24,27,31,32,33,34,36,37,38,39],"impact":[38,39],"implement":[29,39],"impli":[1,24,37],"implicit":33,"import":[10,23,29,30,31,32,33,34,36,37,38,39,40,42,43],"importerror":42,"improv":[6,22,29,32],"inc":[1,24,31,34,39,41],"includ":[1,2,10,18,21,22,23,24,29,30,32,33,36,38,39,42],"include_plotlyj":[1,10,17,18,22,23,24,36],"inclus":[3,24,29],"incomplet":[3,24],"incorrect":27,"incorrectargumentcomboerror":[3,24,26],"independ":[1,2,39],"index":[1,2,3,23,24,31,32,33,35,37,38,39,41,43],"index_col":41,"indic":[3,24],"individu":[3,16,22,24,31,33,35,36,38,39,40,43],"individual_return":31,"individual_vol":31,"induc":[4,25],"industri":40,"infer":26,"infer_vari":26,"info_ratio_func":[1,2],"inform":[1,2,5,23,26,27,33,43],"inherit":[21,40],"initi":[1,4,16,22,24,25,26],"initialvaluezeroerror":[1,24,26],"inlin":10,"inner":[1,2,26,40],"inplac":41,"input":[0,3,4,12,26],"instal":[29,35,43],"instanti":26,"instead":[23,27,42],"instrument":[3,24],"instrument_id":[3,24],"int":[0,1,2,3,4,6,8,9,11,13,15,19,22,24,25,26,31,39],"intdefault":26,"integ":[0,1,2,3,8,15,24],"integr":[23,29,36,40,43],"integratedtermin":29,"intend":[1,2,3,24],"intens":41,"interact":[23,29,30,35,37,42,43],"intercept":[1,2,36],"interest":[33,36,39],"intermedi":40,"intermediate_seri":40,"internal":[27,36],"internat":[36,38],"interpol":[1,24],"interpret":35,"intersect":[40,41],"inv_vol":[1,2,26,32,38,40],"inv_vol_portfolio":32,"inv_vol_portfolio_df":32,"invalid":[29,31,32,33,36,38,39,40,41],"invalid_seri":[40,41],"invari":26,"invers":[38,40],"invesco":39,"invest":[1,2,32,33,37],"investment_univers":[32,33],"investopedia":[1,2,3,24],"ishar":39,"isin":[3,24,32],"iso":[0,1,3,4,8,11,12,13,15,24,25,40,41],"issu":[29,32,40,41],"item":[0,1,2,3,7,24,27,31,32,33,34,36,38,39],"item_count":[1,2,31,32,33,38,39,40],"item_idx":41,"iter":26,"ixic":43,"j":[1,2,31,32,38],"javascript":[10,23,30,36],"jefe":[1,2],"jensen":[1,2,23],"jensen_alpha":[1,2],"jensensmeasur":[1,2],"join":[38,40],"journal":[1,2],"jp":41,"js":[1,10,18,22,24],"json":[1,24,29,32,34,40,43],"jump":[4,25],"jumps_lamda":[4,25],"jumps_mu":[4,25],"jumps_sigma":[4,25],"just":43,"k":[1,24],"kappa":[1,24,37,39],"kappa3_ratio":[1,2,3,24,26,37,39],"kde":[1,24,26],"key":[1,2,31,32,33,34,38],"key_metr":[27,31,32,38],"keyerror":[1,2],"keyword":[3,4,26],"known":[1,24],"krylov":26,"kurtosi":[1,2,3,24,26,34,37,39,43],"kurtosis_func":[1,24],"l":26,"label":[1,3,4,24,25,26,29,31,32,33,34,36,37,38,39,40,43],"labelsnotuniqueerror":26,"lag":[1,3,4,24,25],"lambda":[31,32,38],"languag":29,"larg":[39,40,41,42],"large_seri":41,"larger":[1,24,37],"last":[0,1,2,13,23,24,30,36,37,39,40],"last_idx":[1,3,24,26,29,31,32,33,34,37,38,39,40,41,43],"last_indic":[1,2,26],"late":41,"late_seri":41,"later":[1,20,24],"latest":42,"launch":29,"layer":35,"layout":[14,23,30,36],"le":[1,2,4,25,26],"learn":[1,2,42,43],"least":[1,2,20,24,29,31,32,38],"left":[1,2,3,24,40],"len":[29,31,32,33,37,38,39,41],"length":[1,2,3,23,24,26,29,31,33,34,37,38,39,40,42,43],"lengths_of_item":[1,2,26,40],"less":[1,2,3,24,35],"let":[33,37,38,39,43],"level":[1,2,3,24,33,37,39],"librari":[1,18,22,24,32,35,36,37,40,42],"like":[26,43],"limit":[1,24,35],"line":[1,2,17,23,24,26,36],"line_fram":[18,22,32],"linear":[1,2,26],"linearregress":[1,2],"link":10,"lint":[29,42],"list":[0,1,2,3,4,8,11,12,13,15,24,25,26,29,32,40,41],"liter":[1,2,3,24,35],"literalbarplotmod":[1,24,26],"literalbizdayfreq":[17,23,26],"literalcaptureratio":[1,2,26],"literalframeprop":26,"literalhowmerg":26,"literaljsonoutput":[1,24,26],"literallineplotmod":[1,18,22,24,26],"literalminimizemethod":[6,9,22,26],"literalnanmethod":[1,24,26],"literalpandasreindexmethod":[1,24,26],"literalplotlyhistogrambarmod":[1,24,26],"literalplotlyhistogramcurvetyp":[1,24,26],"literalplotlyhistogramhistnorm":[1,24,26],"literalplotlyhistogramplottyp":[1,24,26],"literalplotlyjslib":[1,10,17,18,22,23,24,26],"literalplotlyoutput":[1,10,17,18,22,23,24,26],"literalportfolioweight":26,"literalquantileinterp":[1,24,26],"literalseriesprop":26,"literaltrunc":[1,2,26],"ljung":[1,3,24],"ljung_box":[3,24],"lmbda":[1,2,3,24],"ln":[1,24,40],"load":[14,24,30,31,32,33,34,35,36,37,38,39],"load_plotly_dict":32,"loc":[27,31,32,33,35,38,39],"local":[1,3,24,29,32,39],"local_cci":[3,24],"locat":[1,24,29],"lock":[1,2,3,24,29,42],"log":[1,24,34,40,41,43],"lognorm":[4,25,43],"logo":[1,10,14,18,22,24,36],"logo_url":10,"long":33,"long_column":[1,2],"longer":23,"longnam":32,"lookback":39,"lookback_d":39,"loss":[3,24,37,39],"low":[32,39],"lower":[1,24,26,32,33,34],"lower_partial_moment_func":[1,24],"lowest":31,"lpm_p":[1,24],"lvl_one":[3,24],"lvl_zero":[3,24,27,30,31,32,33,34,35,36,37,38,39,40,41,43],"lvl_zero_item":[1,2],"m":[1,24,29,37,39,42],"m1":42,"m2":42,"machin":42,"main":[35,40],"maintain":29,"major":29,"make":[29,33,36,42],"make_portfolio":[1,2,27,31,32,33,38,39,40,43],"make_subplot":36,"manag":[29,32,35,38,41,42],"mani":40,"manipul":[21,42],"manual":[0,12,26,31,39],"mar":[1,24],"mark":26,"marker":[18,22,26,32],"market":[0,1,2,3,4,8,11,12,13,15,24,25,26,31,32,33,36,37,38,39,40,41],"market_cap_portfolio":38,"market_column":[1,2],"market_data":31,"market_proxi":[31,38],"market_returns_df":38,"market_seri":36,"marketsnotstringnorliststrerror":26,"master":29,"match":26,"matrix":[1,2,31,37,38,39,43],"max":[1,24,27,31,32,33,34,35,36,37,38,39,40],"max_concentr":39,"max_dd":[33,38,40,43],"max_dd_dat":37,"max_div":[1,2,26,32,38,40],"max_div_portfolio":[32,38],"max_div_portfolio_df":[32,38],"max_drawdown":[1,2,3,24,26,27,31,32,33,34,35,37,38,39,40,43],"max_drawdown_cal_year":[1,2,3,24,26,37],"max_drawdown_d":[1,2,3,24,26,34,37,39],"max_drawdown_func":[1,24],"max_length":[3,24,26],"max_leverage_loc":[1,24],"max_sharpe_idx":[32,38],"max_sharpe_portfolio":32,"max_sharpe_portfolio_df":32,"max_sharpe_weight":32,"max_var_daili":39,"max_volatil":39,"max_weight":39,"maxdiversificationnanerror":[32,38,40],"maxdiversificationnegativeweightserror":[32,38,40],"maxim":[32,38],"maximum":[1,6,9,22,24,33,34,37,39,40,43],"may":[14,26,27,31,32,33,34,36,37,38,39,40,42],"md":29,"mdd":[1,24],"mead":26,"mean":[1,2,4,16,22,24,25,31,33,34,35,36,37,38,39,40,41],"mean_annual_return":[4,25,33,40,42,43],"mean_annual_vol":[4,25,33,40,42,43],"mean_return":29,"meaning":40,"measur":[1,2,24,29,37,39],"mechan":33,"media":[23,31],"medium":33,"mega":31,"member":29,"merg":[1,2,26,29,40],"merge_seri":[1,2,40],"mergingresultedinemptyerror":26,"merton":[4,25],"met":[1,24],"meta":31,"metadata":[1,2,4,25,26],"method":[2,3,4,5,6,9,16,18,21,22,25,31,32,33,34,35,36,38,39,43],"metric":[2,21,23,29,31,32,33,34,35,38],"microsoft":[30,31,39,42],"midpoint":26,"might":27,"min":[31,32,33,36,38,39],"min_accepted_return":[1,24],"min_length":[3,24,26],"min_leverage_loc":[1,24],"min_period":[1,24],"min_sharp":39,"min_vol_idx":32,"min_vol_overweight":[1,2,26,32,38,40],"min_vol_portfolio":[32,38],"min_vol_portfolio_df":[32,38],"min_vol_row":32,"min_vol_weight":32,"minim":[6,9,22,29,42],"minimize_method":[6,9,22],"minimum":[1,6,9,22,24,33,35,39,40],"minlen":[3,24,26],"minor":29,"minut":43,"mismatch":[29,41],"miss":[0,1,8,11,12,13,15,24,35,40,43],"mix":[39,40],"mixedvaluetypeserror":26,"mm":[3,24],"mobil":[1,10,23,24,30,36],"mode":[1,23,24,27],"model":[1,2,3,4,24,25,26],"model_config":[1,2,3,4,24,25,26],"moder":37,"modifi":[1,24,27,29,31,34,37,38,39,40,41,43],"modul":[0,22,29,35,42],"moment":[1,24],"monitor":[28,35],"mont":35,"month":[0,1,2,3,8,23,24,33,34,35,38,39,40,41,43],"monthly_metr":41,"monthly_rebalanc":33,"monthly_rebalanced_seri":33,"monthly_return":37,"monthly_vol":37,"months_from_last":[1,2,3,24,40,41],"months_offset":[0,1,8,24],"motion":[4,25],"motiv":29,"move":[1,2,3,24,39],"ms":[1,24],"msft":[30,31,34,39,41],"msg":29,"multi":[1,2,21,32,35,43],"multi_asset_analysi":31,"multi_factor_linear_regress":[1,2,36],"multiindex":[1,2],"multipl":[1,9,22,23,31,32,34,35,38],"multiple_seri":41,"multipli":[1,24],"must":[0,3,11,24,26,29,38,40,41],"mutabl":40,"mutat":35,"my_plot":[10,36],"mypi":[29,42],"n":[1,24,31,32,33,34,36,37,38,39],"n_asset":39,"name":[1,2,3,4,6,18,22,24,25,26,29,30,31,32,33,34,36,37,38,39,40,41,42,43],"nameerror":[1,24],"nan":[1,24,32,43],"nanalysi":38,"nasdaq":43,"nasset":31,"nativ":[31,32,38,39],"nav":32,"naverag":31,"navperunit":32,"nbest":32,"ncash":33,"ncg":26,"ncorrel":43,"ncreat":[31,38],"ndarray":[9,16,22],"ndetail":33,"ndiffer":33,"ndiversif":31,"nearest":[1,2,3,24,26],"necessari":[26,37],"need":[0,27,29,30,32,36,40,42,43],"negat":[1,2,24,31,32,37,39],"nelder":26,"nequal":31,"netflix":31,"never":40,"new":[0,1,2,3,4,15,24,27,28,29,40,41,42],"new_seri":[1,2,41],"newcom":29,"newton":26,"next":[31,35],"nfactor":36,"nflx":31,"ninvest":32,"nkey":31,"nminimum":32,"nmonth":33,"nmulti":31,"non":38,"none":[0,1,2,3,4,6,8,9,10,11,12,13,15,17,18,22,23,24,25,26,33],"nonetyp":[1,2,4,25,26],"nonnegativefloat":[4,25],"noptim":[32,38],"normal":[1,4,24,25,26,29,37,39],"note":[23,29,31,32,37,39],"now":[27,33,36,37,38,43],"noweightserror":26,"np":[34,37,41],"nportfolio":39,"nr":36,"nrank":[31,32,38],"nresult":[34,38],"nrisk":39,"nroll":38,"nsimul":[32,38],"nsmallest":39,"ntotal":33,"num_port":[9,19,22,32,38,39],"num_simul":39,"numba":42,"number":[0,1,2,3,4,6,8,9,11,15,19,22,24,25,26,31,33,37,38,40,43],"number_of_sim":[4,25,33,40,42,43],"numberofitemsandlabelsnotsameerror":26,"numer":[1,2,32,40,41,42],"numpi":[26,34,37,41,42],"nvda":31,"nverif":39,"nvidia":31,"nworst":39,"nyse":[40,41],"object":[1,2,3,4,20,23,24,25,30,36,40,41],"observ":[1,2,3,24,27,31,33,34,36,37,38,39,40,43],"occur":[1,24],"offset":[0,1,2,3,8,15,24],"offset_business_day":0,"ok":39,"old_fe":[20,24],"omega":[1,24,37,39],"omega_ratio":[1,24,26,37,39],"omega_ratio_func":[1,24],"onc":[29,34,37,41],"one":[1,2,3,6,20,22,23,24,29],"onli":[1,2,3,4,10,23,24,27,32,33,37,39,41],"opac":[1,24],"open":[1,10,18,22,23,24,30,37,43],"openfram":[6,9,16,17,19,21,22,23,24,26,30,31,32,33,35,36,37,38,39],"openframepropertieslist":26,"openpyxl":[37,42],"openseri":[0,1,22,23,24,25,26,27,28,30,31,32,33,34,36,37,38,39,40,41,43],"openseries_env":42,"opentimeseri":[1,2,5,6,16,20,21,22,26,27,29,30,31,32,33,34,35,36,37,38,39,41,42],"opentimeseriespropertieslist":26,"oper":[27,40,41,42],"optim":[1,6,9,16,18,23,24,27,29,35,36,40],"optimal_portfolio":[27,32,38],"optimal_portfolio_df":32,"optimal_portfolio_seri":32,"optimal_weight":38,"optimization_plot":32,"option":[1,2,3,10,23,24,27,36],"ord_least_squares_fit":[1,2],"order":[1,24,39],"ordinari":[1,2],"org":[1,2,24],"orient":[1,10,24,36],"origin":[27,29,31,34,37,38,39,40,41,43],"original_d":40,"original_valu":40,"os":29,"otherwis":[1,10,24],"outcom":[1,24],"outer":[1,2,26,40],"outlier":[1,24],"output":[1,2,5,10,18,22,23,24,26],"output_path":[10,36],"output_typ":[1,10,17,18,22,23,24,30,32,36],"outsid":[1,24],"overlap":[1,24],"overlay":[1,24,26],"overrid":[1,2,3,24],"overview":[35,43],"overweight":[35,40],"overwrit":[1,24],"owntyp":[26,32,38,40],"p":[1,3,24,30,33,35,36,37,38,39,43],"p_":40,"p_t":40,"pacf":[1,3,24],"packag":[3,24,35,42],"pad":[1,2,3,24,26],"page":[10,17,23,28,35],"pair":[31,38],"pair_fram":31,"panda":[1,2,3,4,24,25,26,29,30,31,32,36,37,40,42],"pandas_df":[3,24],"panel":36,"parallel":42,"param":33,"paramet":[0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,23,24,25,26,32,33,40],"parenthes":27,"pariti":40,"pars":[0,3,4,7,42],"parse_d":41,"partial":[1,3,24],"partial_autocorr":[3,24,26],"pass":[6,9,22,26,29],"patch":29,"path":[1,10,17,23,24,30,36],"path_typ":[1,24],"pathlib":[10,36],"pathtyp":[1,24],"pattern":[3,24,25,26,27,32,35],"pay":27,"pct_chang":[1,24],"pd":[29,30,36,37,39,41],"pdb":29,"pdf":[1,2],"penal":[1,24,37],"pep":26,"per":[1,3,24,31,37,40],"percent":26,"percentag":[1,24,31,32,33,37,38,39],"percentage_metr":31,"percentil":[1,24,31,39],"percentile_idx":39,"perform":[1,2,23,24,26,29,35,36,40,42,43],"period":[1,2,3,23,24,29,30,31,32,33,34,35,36,37,38,39,40,41,43],"periods_in_a_year":[1,3,24,26,37,40],"periods_in_a_year_fix":[1,2,3,24],"pick":[3,24,31],"pin":[29,42],"pip":[35,43],"pitfal":27,"place":[1,2,3,24,27,37,39,40],"placehold":[3,24],"plan":42,"platform":[31,35],"pleas":[28,29],"plot":[1,10,14,17,18,21,22,23,24,27,29,32,34,35,37,42,43],"plot_bar":[1,24,37,43],"plot_data":32,"plot_histogram":[1,24,27,34,37,40,43],"plot_seri":[1,24,34,35,37,40,43],"plot_typ":[1,24],"plotfil":[10,36],"plotlyconfigtyp":10,"plotlylayouttyp":14,"plugin":42,"plus":[3,24],"point":[1,4,9,22,24,25,34,38,43],"point_fram":[16,18,22,32],"point_frame_mod":[18,22,32],"popul":[3,24],"popular":[1,2],"portfolio":[2,6,9,16,18,19,30,31,35,39],"portfolio_analysi":38,"portfolio_asset":39,"portfolio_df":[27,31,32,38,39,40,43],"portfolio_impact":39,"portfolio_metr":38,"portfolio_optimization_result":32,"portfolio_return":39,"portfolio_returns_df":38,"portfolio_rolling_vol":38,"portfolio_seri":33,"portfolio_sharp":39,"portfolio_stress_return":38,"portfolio_twr":33,"portfolio_vol":39,"portfolio_volatil":39,"portfolio_vs_market":38,"portfolionam":[6,22],"portfoliotool":[22,39],"posit":[1,2,3,4,24,33,37,39,43],"positive_shar":[1,2,3,24,26,37,39,43],"positive_share_func":[1,24],"positivefloat":[4,25],"positiveint":[4,25],"possibl":[6,9,19,22,26,29],"post":27,"potenti":[32,38],"powel":26,"pr":29,"practic":[1,2,32,35],"pre":[29,42],"predict":[1,2],"prefer":[26,40],"prepar":[16,22,32],"prepare_plot_data":[22,32],"preserv":40,"preview":36,"previous":[0,12],"price":[1,2,3,5,24,26,27,29,32,34,36,37,40,42,43],"primarili":26,"principl":40,"print":[27,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"prioriti":[3,24],"privat":21,"probabl":[1,4,24,25,26],"problem":42,"process":[1,4,9,19,22,25,32],"produc":32,"profession":32,"program":29,"project":[26,29],"proper":[10,29,36],"properti":[2,3,4,21,25,26,31,34,35,38,39,43],"propertiesinputvalidationerror":26,"propertieslist":26,"provid":[0,1,3,7,21,22,23,24,25,26,27,29,32,33,35,36,37,38,39,40,43],"proxi":[31,36],"ps1":[29,42],"public":[29,32],"publish":29,"purpos":25,"push":29,"pvalu":[3,24],"px":36,"py":29,"pydant":[1,2,3,4,21,24,25,26,35,40,42,43],"pydantic_cor":[3,4],"pylanc":29,"pypi":[28,29],"pyproject":29,"pytest":[29,42],"python":[29,42],"q":[3,24],"qqq":39,"qualiti":[29,35,39,42],"quantil":[1,24,31,38,39],"quantiti":33,"quarter":[23,33,38,40,41],"queri":23,"question":29,"quick":42,"r":[1,2,29,36],"r_squar":36,"rais":[0,1,2,3,4,7,13,24,26,29,32,41],"raise_for_status":32,"random":[4,9,19,22,25,32,38],"rang":[0,1,3,6,9,11,13,22,24,26,29,31,32,34,36,37,38,39,40,43],"rank":[32,35],"rate":[1,3,24,29,36,39],"rather":[33,40],"ratio":[1,2,5,18,22,23,24,26,27,29,31,32,33,34,35,37,38,39,40,43],"ratioinputerror":26,"raw":[1,24],"raw_dat":[0,8],"reach":29,"read_csv":41,"readabl":31,"real":[35,37,43],"real_asset":33,"real_portfolio":33,"real_univers":33,"realist":[25,33,38],"realized_mean_return":[4,25],"realized_vol":[4,25],"rebalanc":[1,2,35],"rebalanced_portfolio":[1,2,33,38],"rebalancing_day":38,"rebas":40,"recent":39,"recent_outli":41,"recent_return":39,"recent_vol":40,"recommend":[29,32],"red":[1,24],"reduc":41,"reduct":31,"refer":[1,2,3,24,43],"reflect":40,"regress":[1,2,35],"regression_result":36,"reindex":[1,24],"reit":38,"relat":[1,2,5,23,24,26,33],"releas":42,"relev":29,"reliabl":32,"relrtrn":[5,26],"remain":[1,2,29,32],"rememb":35,"remot":14,"remov":[1,24],"render":[10,23],"replac":41,"report":[17,35,36],"report_html":[23,30],"repositori":[28,29],"repres":[1,2,6,22,24,26,40],"represent":26,"reproduc":[29,39,42],"request":[27,32,42],"requests_get":32,"requir":[1,2,3,4,10,24,25,26,29,35,40],"resampl":[1,2,3,24,34,37,43],"resample_to_business_period_end":[1,2,3,24,26,34,37,40,41,43],"resampledatalosserror":[3,24,26],"resiz":[10,36],"respect":[1,24,29],"respons":[1,10,14,17,24,30,32,35,36],"restor":[37,39],"result":[4,18,19,22,24,25,26,33,35,36,38,39,40],"ret":[29,31,32,33,38,39],"ret_pct":31,"ret_vol_ratio":[1,2,3,24,26,27,31,32,33,34,35,37,38,39,43],"ret_vol_ratio_func":[1,24],"return":[0,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,25,26,29,32,33,34,35,36,38,39,42,43],"return_diff":33,"return_nan_handl":[1,24,41],"return_v":39,"returns_data":39,"returns_seri":[27,39],"returnseri":[1,2],"returnsimul":[33,35,40,42,43],"reusabl":26,"revalidate_inst":[1,2,3,4,24,25],"revers":[31,32,38],"review":42,"risk":[2,3,9,21,22,23,24,29,35,43],"risk_adj_metr":39,"risk_analysis_report":39,"risk_decomp":39,"risk_free_r":29,"risk_limit":39,"risk_parity_portfolio":38,"risk_report":39,"risk_seri":43,"riskfre":[1,2,24],"riskfree_r":[1,2,24],"robust":[32,35,39],"roll":[2,5,24,26,27,31,35,40,43],"rollbeta":[5,26],"rollcorr":[5,26],"rollcvar":[5,26],"rollinforatio":[5,26],"rolling_beta":[1,2,36],"rolling_corr":[1,2,27,31,36,38,40],"rolling_cvar":39,"rolling_cvar_down":[1,24,39],"rolling_info_ratio":[1,2],"rolling_metr":40,"rolling_ret":[40,43],"rolling_return":[1,24,27,34,37,40,43],"rolling_risk":43,"rolling_var":[34,39],"rolling_var_down":[1,24,27,34,39],"rolling_vol":[1,24,27,34,37,38,39,40,43],"rollrtrn":[5,26],"rollvar":[5,26],"rollvol":[5,26,40],"root":[1,24],"rough":33,"round":[31,32,38,39],"row":[36,41],"rsquar":[1,2],"rtrn":[1,2,3,5,24,26,33,40,42,43],"ruff":[29,42],"ruffen":29,"rug":[1,24],"run":[26,32,42,43],"runner":42,"running_adjust":[3,24],"runtim":[26,42],"russel":43,"rut":[36,43],"s":[0,1,2,3,4,8,11,12,13,15,21,23,24,25,26,30,31,32,33,34,35,36,37,38,39,40,42,43],"safeti":[21,35,43],"sampl":[1,2,3,24,30,36,40,41,42],"save":[1,18,22,23,24,30,36,37,39,40,43],"scalar":27,"scale":[1,2,3,24,39],"scatter":[1,10,18,22,24,36],"scenario":[32,41,43],"scenario_nam":39,"scientif":42,"scikit":[1,2,42],"scipi":[6,9,22,42],"score":[1,24,37,39],"screen":[1,23,24,36],"script":42,"se":[0,3,4,8,11,12,13,15,24,25,32,33,43],"se0011337195":32,"se0011670843":32,"se0015243886":32,"se0017832280":32,"se0017832330":32,"search":35,"second":[1,2,29],"second_column":[1,2],"secondary_i":36,"section":[23,27,30,32,40],"sector":35,"see":26,"seed":[4,9,19,22,25,32,33,38,39,40,42,43],"sek":[3,24],"select":[1,2],"self":[1,2,3,4,24,25,26,27,29,40],"sell":33,"selloff":39,"semant":29,"separ":[1,23,24,33],"sequenc":26,"sequenti":27,"seri":[1,2,3,4,6,20,21,22,24,25,26,29,30,32,33,35,36,37,38,39,42,43],"series1":[41,43],"series2":[41,43],"series3":43,"series_copi":40,"series_datafram":41,"series_list":[30,31,32,38,39,41,43],"series_typ":33,"series_valu":41,"series_with_nan":41,"seriesim":33,"seriesorfloat_co":[1,24,26],"serv":26,"set":[0,1,2,3,10,15,24,26,27,29,32,33,34,35,36,40,42,43],"set_index":41,"set_new_label":[3,24,27,30,31,32,33,34,35,36,37,38,39,40,41,43],"settl":33,"settlement":33,"setup":35,"sever":[29,32,36,40],"share":[1,21,24,39],"sharp":[1,18,22,23,24,29,31,32,33,34,35,37,38,39,43],"sharpe_ratio":[31,32,37,38],"sharpeplot":[16,22,32],"sharperatio":[1,24],"sheet":[1,24,31,32,41],"sheet_nam":39,"sheet_titl":[1,24,41],"shock":39,"short_column":[1,2],"shorter":23,"shortest":[1,2,3,24],"shortfal":[37,39,40],"shorthand":[1,24],"show":[23,29,30,31,32,33,39],"show_last":[1,24],"show_rug":[1,24],"side":23,"signific":37,"silicon":42,"sim_cvar_95":39,"sim_fram":[18,22,32],"sim_return":32,"sim_sharpe_ratio":[32,38],"sim_var_95":39,"sim_volatil":32,"simfram":[19,22,27,32,38,39],"similar":40,"simpl":[1,23,24,29,33,35,36,40,41],"simpli":[1,2],"simplifi":[1,2,3,24,39],"simul":[4,6,9,18,19,35,40,42,43],"simulate_portfolio":[18,22,27,32,38,39],"simulated_df":[27,32,38],"simulated_portfolio":[32,39],"simulation_result":[27,32,38],"simultan":31,"sinc":[3,24],"singl":[1,21,24,35,37,39,41,43],"single_seri":41,"size":[1,4,23,24,25,29,36],"skew":[1,2,3,24,26,34,37,39,43],"skew_func":[1,24],"skip":36,"slsqp":[6,9,22,26],"small":36,"smaller":23,"smallest":[1,24],"social":31,"sold":[1,2],"solid":43,"sophist":[36,38,40],"sort":[31,32,38,39],"sort_valu":31,"sorted_indic":[32,38],"sorted_return":39,"sorted_sharp":31,"sorted_strategi":32,"sortino":[1,23,24,34,37,39,40,43],"sortino_ratio":[1,2,3,24,26,34,37,39,40,43],"sortino_ratio_func":[1,24],"sortinoratio":[1,24],"sought":[1,3,24],"sourc":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,23,24,25,26,29,35],"sp500":[37,43],"sp500_analysi":[37,43],"sp500_data":43,"sp500_metric":37,"span":[1,2,41],"span_of_day":[1,3,24,26,41],"span_of_days_al":[1,2,26],"sparser":[3,24],"spdr":39,"spec":36,"specif":[2,3,24,29,35,40,41,43],"specifi":[1,2,3,24,27,38,40],"specific_metr":27,"spi":[30,33,39],"split":33,"spreadsheet":[1,24],"squar":[1,2,3,24,36],"stabl":32,"stack":[23,26],"standalon":10,"standard":[1,2,4,18,22,23,24,25,37,40],"start":[0,1,4,11,15,24,25,26,33,38,39,40,41],"start_cut":[1,2],"startyear":[0,13],"state":40,"static":[26,29,42],"statist":[3,24,25,32,33,38,40,43],"stay":28,"std":[1,24,36],"std_dev":29,"stdev":[1,24,32,38,39],"step":[29,35],"stock":[31,32,33,34,36,41],"stock_comparison_report":30,"stock_data":[36,41],"stock_vs_market":36,"store":26,"str":[0,1,2,3,4,6,8,10,11,12,13,15,17,18,22,23,24,25,26],"strategi":[1,2,27,35,40,43],"strategy_fram":33,"strategy_metr":33,"strategy_nam":[32,38],"strenum":[5,26],"stress":35,"stress_dat":38,"stress_return":31,"strftime":[37,39],"strict":[1,2,3,4,24,25,26,29,33,41],"string":[0,1,2,3,10,13,23,24,26,40,41],"stringconstraint":[3,24,26],"strip_whitespac":[3,24,26],"strorbytessequ":26,"strsequenc":26,"structur":[21,32,38,41],"stub":[1,2,3,24],"style":[1,24,35,36],"subclass":[20,24],"subplot":36,"subplot_titl":36,"subset":35,"subset_portfolio":33,"subtract":[3,24],"subtyp":26,"success":38,"sum":[29,31,33,38,39],"summari":[1,2,35,36,39],"summat":[3,24],"suppli":[1,2],"support":[0,1,3,4,7,8,10,11,12,13,15,24,25,29,31,32],"sure":42,"swedish":43,"switch":23,"sync":29,"syntax":26,"synthet":41,"system":[21,35,39],"t":[1,2,24,26,27,36,40],"tabl":[17,23],"tag":23,"tail":[37,39],"take":[1,2,3,24,40],"takeaway":33,"target":[1,24,33],"target_vol":[1,24],"target_weight":33,"target_weight_from_var":[1,24],"tech":[31,39],"tech_fram":41,"tech_stock":31,"tech_tick":31,"techniqu":[32,37,38,39],"technolog":32,"templat":[14,30],"term":[1,2,24,33],"tesla":[31,39],"test":[1,2,3,24,25,33,35,42],"test_calculate_return":29,"test_fram":29,"test_from_arrays_bas":29,"test_from_arrays_invalid_d":29,"test_funct":29,"test_portfoliotool":29,"test_seri":29,"test_specif":29,"testopentimeseri":29,"text":[18,22,26,32],"thank":29,"theoret":[32,35,38],"theoretical_portfolio":[33,38],"theoretical_portfolio_df":[33,38],"theoretical_seri":33,"thereof":[20,24],"threshold":[1,24,31,38,39,41],"threshold_idx":31,"throughout":26,"tick":[1,24],"tick_fmt":[1,24],"ticker":[30,31,32,33,34,35,36,37,38,39,41,43],"ticker_symbol":[34,41],"tidi":27,"time":[1,2,4,21,23,24,25,26,33,35,37,38,39,40,42,43],"timedelta":39,"timeout":32,"timeseri":[1,2,3,4,6,20,22,24,25,26,43],"timeseries_chain":24,"timeseries_id":[3,24],"timestamp":[26,29],"titl":[1,10,17,18,22,23,24,30,32,36,41],"title_text":36,"titletext":[18,22],"tlt":[33,36,39],"tnc":26,"tnx":37,"to_csv":34,"to_cumret":[1,2,3,24,32,33,40,41,42,43],"to_dat":[1,2,3,24],"to_datafram":[4,25,33,40,42,43],"to_drawdown_seri":[1,24,34,37,40,43],"to_dt":[1,24],"to_excel":[31,32,37,38,39],"to_json":[1,24,34,40,41,43],"to_low":[3,24,26],"to_upp":[3,24,26],"to_xlsx":[1,24,31,32,34,37,39,40,41,43],"today":[0,12],"togeth":[20,24],"toler":29,"tolist":[32,37],"toml":29,"tool":[29,35,38,42],"top":[32,38],"top_indic":38,"top_sharpe_indic":32,"total":[1,4,5,24,25,26,32,33,34,37,38,39,40,43],"total_return":37,"total_trad":33,"touch":23,"toward":29,"trace":[1,24,36],"track":[1,2,23,28,33],"tracking_error":40,"tracking_error_func":[1,2,40],"trackingerror":[1,2],"trade":[4,25,26,33,38,41,42,43],"trading_day":[0,4,11,25,33,38,40,42,43],"trading_days_in_year":[4,25,40,42,43],"tradingdaysnotabovezeroerror":26,"transact":[35,38],"transaction_data":33,"transaction_seri":33,"transform":35,"treasuri":[33,37,39],"treat":[1,2],"tri":42,"troubleshoot":35,"true":[0,1,2,3,4,8,9,10,14,17,18,22,23,24,25,26,29,31,32,33,36,38,41],"trunc_fram":[1,2,32,40,43],"truncat":[1,2,40],"trust":[26,39],"tsdf":[1,2,3,24,26,31,33,38,39,40,41,43],"tsla":[31,34,39,41],"tupl":[1,2,3,6,9,14,17,18,22,23,24,26,27],"turn":21,"tutori":[29,36,37,38,39,43],"tweak":[9,22],"two":[1,2,20,21,24,27,31,35,40],"twr":[1,2,33],"type":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,23,24,25,35,36,42,43],"typealia":26,"typecheck":29,"typeerror":[0,3,7,24],"typeopentimeseri":[20,24],"typevar":26,"typo":29,"tzdata":42,"unavail":[14,31,32,33,36,38,39],"unclear":29,"underlying":[26,39],"understand":[27,35,37,38,40],"union":26,"uniqu":[23,26],"unit":[1,24,31,37],"univers":[31,32,33,38],"unlik":33,"unnecessari":27,"unpack":27,"untouch":40,"unusu":[37,39],"updat":[28,29,36],"update_layout":[32,36],"url":[10,32],"us":[33,38,40,41,43],"usag":43,"use":[1,2,3,4,10,16,18,21,22,23,24,25,26,27,29,31,33,34,35,37,38,39,40,41,43],"user":[1,3,24,26,40],"usual":40,"utf":30,"util":[1,2,35],"uv":[29,42],"v":[1,24],"valid":[0,3,4,15,21,35,42,43],"valid_d":41,"valid_valu":41,"validate_assign":[1,2,3,4,24,25],"validationerror":[3,4,41],"valu":[0,1,2,3,4,5,15,24,25,29,31,32,34,35,36,37,43],"valuabl":33,"value_list":40,"value_nan_handl":[1,24,32,40,41,43],"value_ret":[1,2,3,24,26,33,34,37,38,39,43],"value_ret_calendar_period":[1,24,34,37],"value_ret_func":[1,24],"value_to_diff":[1,2,3,24,41],"value_to_log":[1,24,34,40,41,43],"value_to_ret":[1,2,3,24,27,29,31,34,37,38,39,40,41,43],"valueerror":[1,2,24,29],"valuelisttyp":[3,24,26],"values_with_nan":41,"valuetyp":[1,2,3,24,26,32,33,40,42,43],"var":[1,3,5,24,26,27,34,35,37,38,40,43],"var_90":37,"var_95":[37,40,43],"var_99":37,"var_daili":39,"var_down":[1,2,3,24,26,34,37,38,39,40,43],"var_down_func":[1,24,37,39],"var_seri":27,"var_valu":39,"variabl":[1,2,26,36],"varianc":[1,2,3,24,26,35],"various":[24,29,32,36,37,38,41,43],"vde":32,"ve":43,"vea":32,"venv":[29,42],"verifi":[35,39],"version":[23,28,29,42],"vertic":23,"vertical_legend":[17,23],"vgt":32,"vht":32,"via":[1,3,24,26],"view":[1,23,24,33],"viewport":[10,36],"virtual":42,"visit":28,"visual":[29,32,35,36,40,42],"vnq":[32,38],"vol":[1,2,3,24,26,27,31,32,33,34,35,37,38,39,40,43],"vol_dat":37,"vol_diff":33,"vol_from_var":[1,2,3,24,26,37],"vol_from_var_func":[1,24],"vol_func":[1,24,40],"vol_pct":31,"vol_seri":37,"vol_valu":37,"volatil":[1,2,3,4,5,16,22,23,24,25,26,27,31,33,34,35,36,37,39,40,43],"volum":[36,41],"volume12":[1,2],"vs":[29,31,35],"vscode":29,"vti":32,"vwo":32,"w":[30,31,39,40,41],"want":42,"warn":39,"watch":28,"way":[26,27,41,42],"web":[23,36],"week":[33,39],"weight":[1,2,3,16,19,22,24,26,27,31,35,39,40,43],"weight_strat":[1,2,27,31,32,33,38,39,40,43],"weighted_avg_return":31,"weighted_avg_vol":31,"weighted_return":38,"welcom":29,"well":[26,36],"what_output":[1,24,41,43],"whether":[1,10,14,18,22,24],"width":[1,23,24,32],"wiki":[1,24],"wikipedia":[1,24],"will":[1,2,3,23,24,27,29,37,40,41,43],"window":[1,2,10,18,22,24,27,29,34,36,39],"winter":[1,2],"within":39,"without":[1,24,27,42],"work":[27,29,32,35,36,40,42],"workflow":[35,36],"working_data":40,"workspacefold":29,"world":[35,43],"worst":[1,2,3,23,24,26,31,34,37,38,39],"worst_1_perc":39,"worst_5_day":39,"worst_5_perc":39,"worst_day":[31,37,38],"worst_days_threshold":38,"worst_func":[1,24],"worst_idx":39,"worst_month":[1,2,3,24,26,37,39],"worst_threshold":31,"worst_year_dd":37,"write":30,"writer":39,"written":[1,24],"wrong":[27,41],"www":[1,2,3,24],"x":[1,2,10,24,31,32,36],"x_column":[1,2],"x_fmt":[1,24],"xbins_siz":[1,24],"xdist":42,"xlsx":[1,24,31,32,34,37,38,39,40,41,43],"y":[1,2,10,24,36,37,39],"y_column":[1,2],"y_fmt":[1,24],"yahoo":42,"year":[0,1,2,3,13,23,24,31,34,36,38,39,40],"year_return":[34,37],"yearfrac":[1,3,24,26,37],"yf":[30,31,32,33,34,35,36,37,38,39,41,43],"yfinanc":[30,31,32,33,34,35,36,37,38,39,41,42,43],"yield":[32,37],"yourusernam":29,"yyyi":[3,24],"z":[1,3,24,26,37,39,41],"z_score":[1,2,3,24,26,37,39],"z_score_func":[1,24],"zero":[0,1,2,3,11,15,24,26,38],"zip":[30,31,33,38,39,43],"zone":42,"zscore":[1,24]},"titles":["Date Utilities","OpenFrame","openseries.OpenFrame","openseries.OpenTimeSeries","openseries.ReturnSimulation","openseries.ValueType","openseries.constrain_optimized_portfolios","openseries.date_fix","openseries.date_offset_foll","openseries.efficient_frontier","openseries.export_plotly_figure","openseries.generate_calendar_date_range","openseries.get_previous_business_day_before_today","openseries.holiday_calendar","openseries.load_plotly_dict","openseries.offset_business_days","openseries.prepare_plot_data","openseries.report_html","openseries.sharpeplot","openseries.simulate_portfolios","openseries.timeseries_chain","openseries package","Portfolio Tools","Report Generation","OpenTimeSeries","Simulation","Types and Enums","API Consistency Notes","Changelog","Contributing to openseries","Reporting","Multi-Asset Analysis","Portfolio Optimization","Rebalanced Portfolio Simulation","Single Asset Analysis","openseries Documentation","Advanced Features","Basic Financial Analysis","Portfolio Analysis","Risk Management","Core Concepts","Data Handling","Installation","Quick Start Guide"],"titleterms":{"All":43,"Do":27,"From":41,"Other":21,"The":40,"Up":[31,37,38,39],"Your":43,"__format__":27,"add":41,"adjust":[37,39],"advanc":[32,36,37,38],"alias":26,"align":[40,41],"analysi":[1,24,31,32,33,34,36,37,38,39,40,43],"annual":[37,40],"api":[27,35],"applic":33,"architectur":40,"array":41,"asset":[31,34,38,41,43],"attribut":[31,33,38],"backtest":32,"base":32,"basic":[32,33,34,37,39,43],"benchmark":37,"benefit":36,"best":[27,33,40],"bug":29,"build":29,"built":30,"busi":[40,41,43],"calcul":40,"calendar":[34,37,40],"callabl":27,"carlo":[32,38,39],"cash":33,"categori":40,"chain":27,"changelog":28,"check":[40,41],"class":[1,21,24,25,26,40],"code":29,"commit":29,"common":[1,24,27,42,43],"communiti":29,"compar":31,"comparison":[32,37,38],"complet":[31,32,34],"comprehens":37,"concept":[40,43],"conda":42,"condit":39,"consider":41,"consist":[27,40,41],"constrain_optimized_portfolio":6,"constraint":22,"construct":[1,24,38,40],"content":35,"contribut":29,"control":39,"convers":41,"core":[40,42],"correl":[1,31,38],"cost":33,"coverag":29,"creat":[36,41,43],"creation":27,"csv":41,"custom":[26,33,36,38],"cvar":39,"daili":33,"dashboard":39,"data":[1,24,32,38,40,41,43],"datafram":[27,41],"date":[0,21,40,41,42],"date_fix":7,"date_offset_fol":8,"day":[40,41,43],"debug":29,"decomposit":39,"depend":42,"design":23,"detail":[32,33],"detect":41,"develop":[29,35,42],"differ":[33,41],"distribut":37,"diversif":[32,38],"docstr":29,"document":[29,35],"drawdown":37,"drop":41,"effici":[38,41],"efficient_fronti":9,"embed":30,"enum":[21,26],"environ":29,"equal":[32,33,38],"error":38,"exampl":[33,35],"excel":41,"except":26,"exist":30,"export":[1,24,31,32,34,36,39,40,41,43],"export_plotly_figur":[10,36],"express":36,"extern":43,"factor":36,"featur":[29,35,36],"figur":36,"file":42,"financ":41,"financi":[1,24,37,40,42,43],"first":43,"fix":41,"format":[27,41],"frame":1,"framework":32,"frequenc":33,"frontier":38,"function":[0,21,23,24,27],"fund":32,"generat":23,"generate_calendar_date_rang":11,"get":[29,42,43],"get_previous_business_day_before_today":12,"github":28,"guid":[35,43],"guidelin":29,"handl":[0,38,40,41],"help":[29,42],"hint":29,"histor":39,"holiday_calendar":13,"html":[23,30,36],"ide":29,"immut":40,"import":[27,35,41],"indic":35,"inlin":36,"instal":42,"integr":41,"interpret":37,"invers":32,"issu":[27,42],"json":41,"key":[35,43],"layer":40,"length":41,"limit":39,"linux":42,"liter":26,"load":[40,41,43],"load_plotly_dict":14,"maco":42,"main":21,"manag":[1,33,39,40],"manipul":[1,24],"maximum":[32,38],"mean":32,"memori":[40,41],"messag":29,"method":[1,24,27,40,41],"metric":[1,24,27,37,39,40,43],"minimum":[32,38],"miss":41,"model":36,"monitor":39,"mont":[32,38,39],"month":37,"multi":[31,36],"multipl":[40,41,43],"mutat":40,"name":27,"nan":41,"network":42,"next":43,"non":24,"note":[27,35,42],"notif":28,"numer":24,"object":27,"offset_business_day":15,"onc":43,"openfram":[1,2,40,41,43],"openseri":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,29,35,42],"opentimeseri":[3,24,40,43],"optim":[22,32,38],"option":42,"outlier":41,"output":36,"overview":40,"overweight":[32,38],"packag":21,"page":30,"panda":41,"paramet":27,"pariti":38,"pass":27,"pattern":43,"perform":[31,32,33,34,37,38,39,41],"pip":42,"platform":42,"plot":36,"portfolio":[1,21,22,27,32,33,38,40,43],"practic":[27,33,40],"prepare_plot_data":16,"price":41,"process":29,"properti":[1,24,27,40],"pull":29,"python":35,"qualiti":41,"quick":[35,43],"rang":41,"rank":31,"rate":41,"real":[32,33,41],"rebalanc":[33,38],"recommend":42,"refer":35,"regress":36,"releas":[28,29],"rememb":43,"remov":41,"report":[23,29,30,37,38,39],"report_html":17,"request":29,"requir":42,"resampl":[40,41],"resample_to_business_period_end":27,"respons":23,"result":[31,32,34,43],"return":[1,23,24,27,31,37,40,41],"returnsimul":[4,25],"review":29,"risk":[1,31,32,34,37,38,39,40],"roll":[1,34,36,37,38,39],"run":[27,29],"safeti":40,"scenario":39,"sector":31,"seri":[27,31,34,40,41],"set":[31,37,38,39],"setup":[29,32,33,34],"sharpeplot":18,"simpl":38,"simul":[21,22,25,32,33,38,39],"simulate_portfolio":19,"singl":34,"solut":27,"sourc":[41,42,43],"specif":[1,27,42],"standard":29,"start":[29,35,43],"statist":1,"step":43,"strategi":[32,33,38,41],"stress":[31,38,39],"string":27,"structur":29,"style":[29,31],"subset":33,"summari":[33,37,38],"support":[35,42],"system":42,"tabl":35,"test":[29,31,38,39],"theoret":33,"time":[31,34],"timeseries_chain":20,"tool":[21,22],"transact":33,"transform":[1,24,34,40,41,43],"troubleshoot":42,"tutori":35,"type":[21,26,29,40],"typeerror":27,"understand":33,"unsupport":27,"usag":41,"use":[30,32,36,42],"user":35,"util":[0,21,24,42],"valid":[26,40,41],"valu":[23,26,27,39,40,41],"valuetyp":[5,27],"var":39,"varianc":32,"verifi":42,"version":35,"visual":[1,22,24,34,37,43],"volatil":[32,38],"vs":[27,33,40],"weight":[32,33,38],"window":42,"work":[41,43],"workflow":[29,31,32,34,40],"world":[32,33],"write":29,"yahoo":41,"year":37}}) \ No newline at end of file diff --git a/docs/build/html/tutorials/advanced_features.html b/docs/build/html/tutorials/advanced_features.html deleted file mode 100644 index d33e80cb..00000000 --- a/docs/build/html/tutorials/advanced_features.html +++ /dev/null @@ -1,374 +0,0 @@ - - - - - - - - - Advanced Features — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Advanced Features

-

This tutorial covers advanced openseries features including custom analysis, integration with other libraries, and extending functionality.

-
-

Factor Analysis and Regression

-
-

Multi-Factor Model Analysis

-
import yfinance as yf
-from openseries import OpenTimeSeries, OpenFrame
-
-# Load factor data (Fama-French factors would be ideal, using proxies here)
-factor_tickers = {
-     "^GSPC": "Market",
-     "^RUT": "Small Cap",  # Size factor proxy
-     "EFA": "International",  # International factor
-     "TLT": "Bonds"  # Interest rate factor
-}
-
-# Load factor data
-factor_series = []
-for ticker, name in factor_tickers.items():
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="3y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=name)
-     factor_series.append(series)
-
-# Create factor frame
-factors = OpenFrame(constituents=factor_series)
-
-# Load individual stock for analysis
-stock_data = yf.Ticker("AAPL").history(period="3y")
-apple = OpenTimeSeries.from_df(dframe=stock_data['Close'])
-apple.set_new_label(lvl_zero="Apple")
-
-# Add stock to factor frame for regression
-analysis_frame = OpenFrame(constituents=factor_series + [apple])
-
-# Perform multi-factor regression
-# This may fail with various exceptions
-regression_results = analysis_frame.multi_factor_linear_regression(
-     dependent_variable_idx=-1  # Apple is the last series (dependent variable)
-)
-
-print("\n=== MULTI-FACTOR REGRESSION RESULTS ===")
-print("Regression Summary:")
-print(regression_results['summary'])
-
-print("\nFactor Loadings (Betas):")
-for i, factor_name in enumerate([s.label for s in factor_series]):
-     beta = regression_results['coefficients'][i+1]  # Skip intercept
-     print(f"  {factor_name}: {beta:.4f}")
-
-print(f"\nR-squared: {regression_results['r_squared']:.4f}")
-print(f"Adjusted R-squared: {regression_results['adj_r_squared']:.4f}")
-
-
-
-
-

Rolling Factor Analysis

-
# Rolling beta analysis with market
-market_series = factor_series[0]  # S&P 500
-stock_vs_market = OpenFrame(constituents=[apple, market_series])
-
-# Calculate rolling beta
-rolling_beta = stock_vs_market.rolling_beta(observations=252)  # 1-year rolling
-
-print(f"\n=== ROLLING BETA ANALYSIS ===")
-print(f"Current Beta: {rolling_beta.iloc[-1, 0]:.3f}")
-print(f"Average Beta: {rolling_beta.mean().iloc[0]:.3f}")
-print(f"Beta Range: {rolling_beta.min().iloc[0]:.3f} to {rolling_beta.max().iloc[0]:.3f}")
-print(f"Beta Volatility: {rolling_beta.std().iloc[0]:.3f}")
-
-# Rolling correlation
-rolling_corr = stock_vs_market.rolling_corr(observations=252)
-
-print(f"\n=== ROLLING CORRELATION ANALYSIS ===")
-print(f"Current Correlation: {rolling_corr.iloc[-1, 0]:.3f}")
-print(f"Average Correlation: {rolling_corr.mean().iloc[0]:.3f}")
-print(f"Correlation Range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}")
-
-
-
-
-
-

Exporting Custom Plotly Figures

-

The export_plotly_figure function allows you to export any Plotly figure to a mobile-responsive HTML file. This is useful when you create custom visualizations using Plotly’s graph objects that aren’t directly available through openseries plotting methods.

-
-

Creating Custom Plots

-

You can create any Plotly figure and export it using the same responsive HTML format that openseries uses internally:

-
import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-from openseries import export_plotly_figure
-from pathlib import Path
-
-# Create a custom subplot figure
-fig = make_subplots(
-    rows=2, cols=2,
-    subplot_titles=('Price Chart', 'Volume', 'Returns Distribution', 'Drawdown'),
-    specs=[[{"secondary_y": True}, {"type": "bar"}],
-          [{"type": "histogram"}, {"type": "scatter"}]]
-)
-
-# Add traces (example data)
-fig.add_trace(
-    go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], name="Price"),
-    row=1, col=1
-)
-fig.add_trace(
-    go.Bar(x=[1, 2, 3, 4], y=[100, 200, 150, 300], name="Volume"),
-    row=1, col=2
-)
-fig.add_trace(
-    go.Histogram(x=[0.01, -0.02, 0.015, -0.01, 0.02], name="Returns"),
-    row=2, col=1
-)
-fig.add_trace(
-    go.Scatter(x=[1, 2, 3, 4], y=[0, -0.05, -0.03, -0.08], name="Drawdown"),
-    row=2, col=2
-)
-
-# Update layout
-fig.update_layout(height=800, title_text="Custom Multi-Panel Dashboard")
-
-# Export to responsive HTML
-output_path = export_plotly_figure(
-    figure=fig,
-    fig_config={"responsive": True},
-    output_type="file",
-    filename="custom_dashboard.html",
-    include_plotlyjs="cdn",
-    plotfile=Path("output/custom_dashboard.html"),
-    title="Custom Financial Dashboard",
-    auto_open=True,
-)
-
-print(f"Dashboard saved to: {output_path}")
-
-
-
-
-

Using with Plotly Express

-

You can also use export_plotly_figure with Plotly Express figures:

-
import plotly.express as px
-import pandas as pd
-from openseries import export_plotly_figure
-from pathlib import Path
-
-# Create sample data
-df = pd.DataFrame({
-    'Date': pd.date_range('2020-01-01', periods=100),
-    'Asset_A': 100 + pd.Series(range(100)).cumsum() * 0.1,
-    'Asset_B': 100 + pd.Series(range(100)).cumsum() * 0.15,
-})
-
-# Create a Plotly Express figure
-fig = px.line(
-    df, x='Date', y=['Asset_A', 'Asset_B'],
-    title='Asset Comparison',
-    labels={'value': 'Price', 'variable': 'Asset'}
-)
-
-# Export with responsive HTML
-export_plotly_figure(
-    figure=fig,
-    fig_config={"responsive": True, "displayModeBar": True},
-    output_type="file",
-    filename="asset_comparison.html",
-    include_plotlyjs="cdn",
-    plotfile=Path("output/asset_comparison.html"),
-    title="Asset Price Comparison",
-    auto_open=False,
-)
-
-
-
-
-

Inline HTML Output

-

For embedding in web applications or reports, you can generate inline HTML divs:

-
import plotly.graph_objects as go
-from openseries import export_plotly_figure
-
-# Create a simple figure
-fig = go.Figure(data=go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13]))
-
-# Generate inline HTML div
-html_div = export_plotly_figure(
-    figure=fig,
-    fig_config={},
-    output_type="div",
-    filename="my_plot.html",
-    include_plotlyjs="cdn",
-    plotfile=Path("dummy.html"),  # Ignored for div output
-)
-
-# html_div can now be embedded in HTML documents
-print(html_div[:100])  # Preview the HTML
-
-
-
-
-

Benefits of export_plotly_figure

-

The export_plotly_figure function provides several advantages over Plotly’s default HTML export:

-
    -
  • Mobile Responsive: Automatically adapts to different screen sizes and device orientations

  • -
  • Optimized Viewport: Proper viewport settings for mobile devices

  • -
  • Auto-Resize: JavaScript handles window resizing and orientation changes

  • -
  • Consistent Styling: Uses the same responsive CSS as openseries internal plots

  • -
  • Optional Title Container: Can include a title and logo in a responsive header

  • -
-

This makes it ideal for creating dashboards and reports that need to work well on both desktop and mobile devices.

-

This tutorial demonstrates how to extend openseries with advanced functionality for sophisticated financial analysis workflows.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/tutorials/basic_analysis.html b/docs/build/html/tutorials/basic_analysis.html deleted file mode 100644 index a72f4890..00000000 --- a/docs/build/html/tutorials/basic_analysis.html +++ /dev/null @@ -1,492 +0,0 @@ - - - - - - - - - Basic Financial Analysis — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Basic Financial Analysis

-

This tutorial demonstrates how to perform fundamental financial analysis using openseries with real market data.

-
-

Setting Up

-

First, let’s import the necessary libraries and download some data:

-
import yfinance as yf
-import pandas as pd
-import numpy as np
-from openseries import OpenTimeSeries, OpenFrame
-from datetime import date, datetime
-
-# Download S&P 500 data for the last 5 years
-ticker = yf.Ticker("^GSPC")
-data = ticker.history(period="5y")
-
-# Create OpenTimeSeries
-sp500 = OpenTimeSeries.from_df(
-     dframe=data['Close']
-)
-
-# Set a descriptive label
-sp500.set_new_label(lvl_zero="S&P 500 Index")
-
-print(f"Loaded {sp500.length} observations")
-print(f"Date range: {sp500.first_idx} to {sp500.last_idx}")
-
-
-
-
-

Basic Performance Metrics

-

Let’s calculate the fundamental performance metrics:

-
# Total return over the period
-total_return = sp500.value_ret
-print(f"Total Return: {total_return:.2%}")
-
-# Annualized return (CAGR)
-annual_return = sp500.geo_ret
-print(f"Annualized Return (CAGR): {annual_return:.2%}")
-
-# Arithmetic mean return
-arithmetic_return = sp500.arithmetic_ret
-print(f"Arithmetic Mean Return: {arithmetic_return:.2%}")
-
-# Time period analysis
-print(f"Investment period: {sp500.yearfrac:.2f} years")
-print(f"Number of observations: {sp500.length}")
-print(f"Periods per year: {sp500.periods_in_a_year:.1f}")
-
-
-
-
-

Risk Analysis

-

Now let’s examine the risk characteristics:

-
# Volatility (annualized standard deviation)
-volatility = sp500.vol
-print(f"Annualized Volatility: {volatility:.2%}")
-
-# Downside deviation (volatility of negative returns only)
-downside_vol = sp500.downside_deviation
-print(f"Downside Deviation: {downside_vol:.2%}")
-
-# Value at Risk (95% confidence level)
-var_95 = sp500.var_down
-print(f"95% Value at Risk (daily): {var_95:.2%}")
-
-# Conditional Value at Risk (Expected Shortfall)
-cvar_95 = sp500.cvar_down
-print(f"95% CVaR (daily): {cvar_95:.2%}")
-
-# Maximum single-day loss
-worst_day = sp500.worst
-print(f"Worst single day: {worst_day:.2%}")
-
-
-
-
-

Risk-Adjusted Returns

-

Calculate risk-adjusted performance metrics:

-
# Sharpe Ratio (return per unit of total risk)
-sharpe_ratio = sp500.ret_vol_ratio
-print(f"Sharpe Ratio: {sharpe_ratio:.2f}")
-
-# Sortino Ratio (return per unit of downside risk)
-sortino_ratio = sp500.sortino_ratio
-print(f"Sortino Ratio: {sortino_ratio:.2f}")
-
-# Kappa-3 Ratio (penalizes larger downside deviations more)
-kappa3_ratio = sp500.kappa3_ratio
-print(f"Kappa-3 Ratio: {kappa3_ratio:.2f}")
-
-# Omega Ratio
-omega_ratio = sp500.omega_ratio
-print(f"Omega Ratio: {omega_ratio:.2f}")
-
-
-
-
-

Drawdown Analysis

-

Analyze drawdowns to understand downside risk:

-
# Maximum drawdown
-max_drawdown = sp500.max_drawdown
-max_dd_date = sp500.max_drawdown_date
-print(f"Maximum Drawdown: {max_drawdown:.2%}")
-print(f"Max Drawdown Date: {max_dd_date}")
-
-# Create drawdown series for visualization (modifies original)
-sp500.to_drawdown_series()
-
-# Plot drawdowns
-sp500.plot_series()
-# This will open an interactive plot in your browser
-
-# Worst calendar year drawdown
-worst_year_dd = sp500.max_drawdown_cal_year
-print(f"Worst Calendar Year Drawdown: {worst_year_dd:.2%}")
-
-
-
-
-

Distribution Analysis

-

Examine the return distribution characteristics:

-
# Convert to returns for distribution analysis (modifies original)
-sp500.value_to_ret()
-
-# Note: value_to_ret() modifies the original series in place
-# Restore the original series for further analysis
-sp500 = OpenTimeSeries.from_df(dframe=data['Close'])
-sp500.set_new_label(lvl_zero="S&P 500 Index")
-
-# Skewness (asymmetry of the distribution)
-skewness = sp500.skew
-print(f"Skewness: {skewness:.2f}")
-if skewness < 0:
-     print("  → Negative skew: more extreme negative returns")
-elif skewness > 0:
-     print("  → Positive skew: more extreme positive returns")
-
-# Kurtosis (tail heaviness)
-kurtosis = sp500.kurtosis
-print(f"Kurtosis: {kurtosis:.2f}")
-if kurtosis > 3:
-     print("  → Fat tails: more extreme returns than normal distribution")
-
-# Percentage of positive days
-positive_share = sp500.positive_share
-print(f"Positive Days: {positive_share:.1%}")
-
-# Current Z-score (how unusual is the last return?)
-z_score = sp500.z_score
-print(f"Last Return Z-score: {z_score:.2f}")
-
-
-
-
-

Monthly and Annual Analysis

-

Break down performance by different time periods:

-
# Resample to monthly data (modifies original)
-sp500.resample_to_business_period_ends(freq="BME")
-print(f"Monthly observations: {sp500.length}")
-
-# Monthly metrics
-monthly_return = sp500.geo_ret
-monthly_vol = sp500.vol
-print(f"Monthly Return (annualized): {monthly_return:.2%}")
-print(f"Monthly Volatility (annualized): {monthly_vol:.2%}")
-
-# Worst month
-worst_month = sp500.worst_month
-print(f"Worst Month: {worst_month:.2%}")
-
-# Annual data (modifies original)
-sp500.resample_to_business_period_ends(freq="BYE")
-print(f"Annual observations: {sp500.length}")
-
-
-
-

Calendar Year Returns

-
# Calculate calendar year returns
-years = range(2019, 2025)  # Adjust based on your data range
-
-for year in years:
-     # This may fail if no data exists for the year
-     year_return = sp500.value_ret_calendar_period(year=year)
-     print(f"{year}: {year_return:.2%}")
-
-
-
-
-
-

Rolling Analysis

-

Analyze how metrics change over time:

-
# 252-day (1-year) rolling volatility
-rolling_vol = sp500.rolling_vol(observations=252)
-print(f"Rolling volatility calculated for {len(rolling_vol)} periods")
-
-# 30-day rolling returns
-rolling_returns = sp500.rolling_return(observations=30)
-
-# Plot rolling volatility
-# Convert to OpenTimeSeries for plotting
-vol_dates = rolling_vol.index.strftime('%Y-%m-%d').tolist()
-vol_values = rolling_vol.iloc[:, 0].tolist()
-
-vol_series = OpenTimeSeries.from_arrays(
-     dates=vol_dates,
-     values=vol_values,
-     name="Rolling Volatility"
-)
-
-vol_series.plot_series()
-
-
-
-
-

Comprehensive Report

-

Get all metrics at once:

-
# Generate comprehensive metrics report
-all_metrics = sp500.all_properties()
-print("\n=== COMPREHENSIVE ANALYSIS REPORT ===")
-print(all_metrics)
-
-# Save to Excel for further analysis
-sp500.to_xlsx(filename="sp500_analysis.xlsx")
-all_metrics.to_excel(excel_writer="sp500_metrics.xlsx", engine="openpyxl")
-
-
-
-
-

Visualization

-

Create various visualizations:

-
# Price chart
-sp500.plot_series()
-
-# Returns bar plot and histogram
-returns = sp500.from_deepcopy()
-returns.value_to_ret()
-returns.plot_bars()
-returns.plot_histogram()
-
-# Drawdown chart
-sp500.to_drawdown_series()
-sp500.plot_series()
-
-
-
-
-

Comparison with Benchmark

-

Let’s compare with a bond index:

-
# Download bond data (10-year Treasury)
-bond_ticker = yf.Ticker("^TNX")
-bond_data = bond_ticker.history(period="5y")
-
-# Create bond series (using yield data)
-bonds = OpenTimeSeries.from_df(
-     dframe=bond_data['Close']
-)
-bonds.set_new_label(lvl_zero="10Y Treasury Yield")
-
-# Create frame for comparison
-comparison_frame = OpenFrame(constituents=[sp500, bonds])
-
-# Compare metrics
-comparison_metrics = comparison_frame.all_properties()
-print("\n=== ASSET COMPARISON ===")
-print(comparison_metrics)
-
-# Calculate correlation
-correlation_matrix = comparison_frame.correl_matrix
-print("\n=== CORRELATION MATRIX ===")
-print(correlation_matrix)
-
-
-
-
-

Advanced Risk Metrics

-

Calculate some advanced risk measures:

-
# VaR at different confidence levels
-var_90 = sp500.var_down_func(level=0.90)
-var_95 = sp500.var_down_func(level=0.95)
-var_99 = sp500.var_down_func(level=0.99)
-
-print(f"90% VaR: {var_90:.2%}")
-print(f"95% VaR: {var_95:.2%}")
-print(f"99% VaR: {var_99:.2%}")
-
-# CVaR at different confidence levels
-cvar_90 = sp500.cvar_down_func(level=0.90)
-cvar_95 = sp500.cvar_down_func(level=0.95)
-cvar_99 = sp500.cvar_down_func(level=0.99)
-
-print(f"90% CVaR: {cvar_90:.2%}")
-print(f"95% CVaR: {cvar_95:.2%}")
-print(f"99% CVaR: {cvar_99:.2%}")
-
-# Implied volatility from VaR (assuming normal distribution)
-vol_from_var = sp500.vol_from_var
-print(f"Volatility implied from VaR: {vol_from_var:.2%}")
-print(f"Actual volatility: {sp500.vol:.2%}")
-
-
-
-
-

Summary and Interpretation

-
print("\n=== INVESTMENT SUMMARY ===")
-print(f"Asset: {sp500.label}")
-print(f"Period: {sp500.first_idx} to {sp500.last_idx}")
-print(f"Total Return: {sp500.value_ret:.2%}")
-print(f"Annualized Return: {sp500.geo_ret:.2%}")
-print(f"Annualized Volatility: {sp500.vol:.2%}")
-print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}")
-print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}")
-print(f"95% VaR (daily): {sp500.var_down:.2%}")
-
-# Risk assessment
-if sp500.ret_vol_ratio > 1.0:
-     print("✓ Good risk-adjusted returns (Sharpe > 1.0)")
-else:
-     print("⚠ Moderate risk-adjusted returns (Sharpe < 1.0)")
-
-if abs(sp500.max_drawdown) < 0.20:
-     print("✓ Moderate maximum drawdown (< 20%)")
-else:
-     print("⚠ Significant maximum drawdown (> 20%)")
-
-
-

This tutorial provides a comprehensive foundation for financial analysis using openseries. You can adapt these techniques for any financial time series data.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/tutorials/portfolio_analysis.html b/docs/build/html/tutorials/portfolio_analysis.html deleted file mode 100644 index 5c918467..00000000 --- a/docs/build/html/tutorials/portfolio_analysis.html +++ /dev/null @@ -1,662 +0,0 @@ - - - - - - - - - Portfolio Analysis — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Portfolio Analysis

-

This tutorial demonstrates how to construct and analyze portfolios using openseries, including optimization techniques and performance attribution.

-
-

Setting Up the Data

-

Let’s start by downloading data for a diversified set of assets:

-
import yfinance as yf
-from openseries import OpenTimeSeries, OpenFrame
-from openseries import efficient_frontier, simulate_portfolios
-
-# Define our universe of assets
-tickers = {
-     "^GSPC": "S&P 500",
-     "EFA": "EAFE International",
-     "EEM": "Emerging Markets",
-     "AGG": "US Aggregate Bonds",
-     "VNQ": "US REITs",
-     "GLD": "Gold",
-     "DBC": "Commodities"
-}
-
-# Download 5 years of data
-series_list = []
-for ticker, name in tickers.items():
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="5y")
-     series = OpenTimeSeries.from_df(
-          dframe=data['Close']
-     )
-     series.set_new_label(lvl_zero=name)
-     series_list.append(series)
-     print(f"Loaded {name}: {series.length} observations")
-
-# Create OpenFrame
-assets = OpenFrame(constituents=series_list)
-print(f"\nCreated frame with {assets.item_count} assets")
-print(f"Common date range: {assets.first_idx} to {assets.last_idx}")
-
-
-
-
-

Asset Analysis

-

First, let’s analyze the individual assets:

-
# Get metrics for all assets
-asset_metrics = assets.all_properties()
-print("=== INDIVIDUAL ASSET METRICS ===")
-print(asset_metrics)
-
-# Key metrics comparison
-returns = asset_metrics.loc['Geometric return']
-volatilities = asset_metrics.loc['Volatility']
-sharpe_ratios = asset_metrics.loc['Return vol ratio']
-max_drawdowns = asset_metrics.loc['Max drawdown']
-
-print("\n=== ASSET COMPARISON ===")
-for asset in returns.index:
-    print(f"{asset}:")
-    print(f"  Annual Return: {returns[asset]:.2%}")
-    print(f"  Volatility: {volatilities[asset]:.2%}")
-    print(f"  Sharpe Ratio: {sharpe_ratios[asset]:.2f}")
-    print(f"  Max Drawdown: {max_drawdowns[asset]:.2%}")
-
-
-
-
-

Correlation Analysis

-

Understanding correlations is crucial for portfolio construction:

-
# Calculate correlation matrix
-correlation_matrix = assets.correl_matrix
-print("\n=== CORRELATION MATRIX ===")
-print(correlation_matrix.round(3))
-
-# Identify highly correlated pairs
-print("\n=== HIGHLY CORRELATED PAIRS (>0.7) ===")
-for i in range(len(correlation_matrix.columns)):
-     for j in range(i+1, len(correlation_matrix.columns)):
-          corr = correlation_matrix.iloc[i, j]
-          if abs(corr) > 0.7:
-                asset1 = correlation_matrix.columns[i]
-                asset2 = correlation_matrix.columns[j]
-                print(f"{asset1} - {asset2}: {corr:.3f}")
-
-# Average correlation with other assets
-avg_correlations = correlation_matrix.mean()
-print("\n=== AVERAGE CORRELATIONS ===")
-for asset, avg_corr in avg_correlations.items():
-     print(f"{asset}: {avg_corr:.3f}")
-
-
-
-
-

Simple Portfolio Construction

-

Let’s start with basic portfolio construction methods:

-
-

Equal Weight Portfolio

-
# Create equal-weighted portfolio using native weight_strat
-portfolio_df = assets.make_portfolio(name="Equal Weight Portfolio", weight_strat="eq_weights")
-equal_weight_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-print(f"Equal Weight Portfolio Return: {equal_weight_portfolio.geo_ret:.2%}")
-print(f"Equal Weight Portfolio Volatility: {equal_weight_portfolio.vol:.2%}")
-print(f"Equal Weight Portfolio Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Custom Weight Portfolio

-

You can also specify custom weights for portfolio construction:

-
# Define custom weights (must sum to 1)
-custom_weights = [0.50, 0.15, 0.10, 0.15, 0.05, 0.03, 0.02]
-
-assets.weights = custom_weights
-portfolio_df = assets.make_portfolio(name="Custom Weighted")
-custom_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-print(f"Custom Portfolio Return: {custom_portfolio.geo_ret:.2%}")
-print(f"Custom Portfolio Volatility: {custom_portfolio.vol:.2%}")
-print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Risk Parity Portfolio

-
# Use native inverse volatility weighting (risk parity)
-portfolio_df = assets.make_portfolio(name="Risk Parity", weight_strat="inv_vol")
-risk_parity_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-print(f"Risk Parity Portfolio Return: {risk_parity_portfolio.geo_ret:.2%}")
-print(f"Risk Parity Portfolio Volatility: {risk_parity_portfolio.vol:.2%}")
-print(f"Risk Parity Portfolio Sharpe: {risk_parity_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Advanced Weight Strategies

-

OpenSeries provides additional weight strategies beyond basic equal weighting and risk parity:

-
-

Maximum Diversification Strategy

-

The maximum diversification strategy optimizes the correlation structure to maximize portfolio diversification:

-
from openseries.owntypes import MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError
-
-# This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError
-max_div_portfolio_df = assets.make_portfolio(
-     name="Maximum Diversification",
-     weight_strat="max_div"
-)
-max_div_portfolio = OpenTimeSeries.from_df(dframe=max_div_portfolio_df)
-
-print(f"Max Diversification Return: {max_div_portfolio.geo_ret:.2%}")
-print(f"Max Diversification Volatility: {max_div_portfolio.vol:.2%}")
-print(f"Max Diversification Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Minimum Volatility Overweight Strategy

-

The minimum volatility overweight strategy overweights the least volatile asset:

-
# This may fail with various exceptions
-min_vol_portfolio_df = assets.make_portfolio(
-     name="Min Vol Overweight",
-     weight_strat="min_vol_overweight"
-)
-min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df)
-
-print(f"Min Vol Overweight Return: {min_vol_portfolio.geo_ret:.2%}")
-print(f"Min Vol Overweight Volatility: {min_vol_portfolio.vol:.2%}")
-print(f"Min Vol Overweight Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}")
-
-
-
-
-

Strategy Comparison with Error Handling

-

When comparing multiple strategies, it’s important to handle potential failures gracefully:

-
strategies = {
-     'Equal Weight': 'eq_weights',
-     'Risk Parity': 'inv_vol',
-     'Max Diversification': 'max_div',
-     'Min Vol Overweight': 'min_vol_overweight'
-}
-
-results = {}
-for name, strategy in strategies.items():
-     # This may fail with MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError, or other exceptions
-     portfolio_df = assets.make_portfolio(name=name, weight_strat=strategy)
-     portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-     results[name] = {
-          'Return': portfolio.geo_ret,
-          'Volatility': portfolio.vol,
-          'Sharpe': portfolio.ret_vol_ratio
-     }
-
-if results:
-     print("\n=== STRATEGY COMPARISON ===")
-     for strategy_name, metrics in results.items():
-         print(f"\n{strategy_name}:")
-         print(f"  Return: {metrics['return']*100:.2f}%")
-         print(f"  Volatility: {metrics['volatility']*100:.2f}%")
-         print(f"  Sharpe: {metrics['sharpe']:.2f}")
-         print(f"  Max Drawdown: {metrics['max_drawdown']*100:.2f}%")
-
-
-
-
-
-
-

Portfolio Optimization

-

Now let’s use openseries’ optimization tools:

-
-

Efficient Frontier

-
# Calculate efficient frontier
-# This may fail with various exceptions
-frontier_df, simulated_df, optimal_portfolio = efficient_frontier(
-     eframe=assets,
-     num_ports=50,
-     seed=42
-)
-
-print("Efficient frontier calculated successfully")
-print(f"Number of frontier points: {len(frontier_df)}")
-print(f"Number of simulated portfolios: {len(simulated_df)}")
-
-# Find maximum Sharpe ratio portfolio
-sharpe_ratios = frontier_df['ret'] / frontier_df['stdev']
-max_sharpe_idx = sharpe_ratios.idxmax()
-
-print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===")
-print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}")
-print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}")
-print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}")
-
-# Get optimal weights
-optimal_weights = optimal_portfolio[-len(assets.constituents):]
-print("\nOptimal Weights:")
-for i, weight in enumerate(optimal_weights):
-     asset_name = assets.constituents[i].label
-     print(f"  {asset_name}: {weight:.1%}")
-
-
-
-
-

Monte Carlo Portfolio Simulation

-
# Simulate random portfolios
-# This may fail with various exceptions
-simulation_results = simulate_portfolios(
-     simframe=assets,
-     num_ports=10000,
-     seed=42
-)
-
-print(f"\nSimulated {len(simulation_results)} random portfolios")
-
-# Find best performing portfolios
-sim_sharpe_ratios = simulation_results['ret'] / simulation_results['stdev']
-
-# Top 5 Sharpe ratios
-sorted_indices = sorted(range(len(sim_sharpe_ratios)), key=lambda i: sim_sharpe_ratios.iloc[i], reverse=True)
-top_indices = sorted_indices[:5]
-
-print("\n=== TOP 5 SIMULATED PORTFOLIOS ===")
-for i, idx in enumerate(top_indices, 1):
-     print(f"\nRank {i}:")
-     print(f"  Return: {simulation_results.iloc[idx]['ret']:.2%}")
-     print(f"  Volatility: {simulation_results.iloc[idx]['stdev']:.2%}")
-     print(f"  Sharpe: {sim_sharpe_ratios.iloc[idx]:.2f}")
-
-
-
-
-
-

Portfolio Comparison

-

Let’s compare all our portfolios:

-
# Add all portfolios to a comparison frame
-portfolios = [equal_weight_portfolio, market_cap_portfolio, risk_parity_portfolio]
-
-# Add individual assets for comparison
-all_series = assets.constituents + portfolios
-comparison_frame = OpenFrame(constituents=all_series)
-
-# Get comprehensive metrics
-portfolio_metrics = comparison_frame.all_properties()
-
-# Focus on key metrics
-key_metrics = portfolio_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']]
-key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown']
-
-print("\n=== PORTFOLIO COMPARISON ===")
-print((key_metrics * 100).round(2))  # Convert to percentages
-
-
-
-
-

Risk Attribution

-

Analyze the risk contribution of each asset:

-
# Calculate portfolio statistics using openseries methods
-# Create equal weight portfolio
-equal_weight_portfolio_df = assets.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
-equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df)
-
-print("\n=== RISK ATTRIBUTION (Equal Weight Portfolio) ===")
-print(f"Portfolio Volatility: {equal_weight_portfolio.vol:.4f}")
-print(f"Portfolio Return: {equal_weight_portfolio.geo_ret:.4f}")
-
-# Individual asset contributions can be analyzed using openseries properties
-for i, series in enumerate(assets.constituents):
-    weight = equal_weights[i]
-    asset_vol = series.vol
-    print(f"\n{series.label}:")
-    print(f"  Weight: {weight:.4f}")
-    print(f"  Individual Volatility: {asset_vol:.4f}")
-    print(f"  Weighted Contribution: {weight * asset_vol:.4f}")
-
-
-
-
-

Performance Attribution

-

Analyze performance contribution over time:

-
# Calculate performance attribution using openseries
-# Individual asset performance is available through openseries properties
-print("\n=== PERFORMANCE ATTRIBUTION ===")
-for i, series in enumerate(assets.constituents):
-    weight = equal_weights[i]
-    asset_return = series.geo_ret
-    contribution = weight * asset_return
-    print(f"{series.label}:")
-    print(f"  Weight: {weight:.2%}")
-    print(f"  Return: {asset_return:.2%}")
-    print(f"  Contribution: {contribution:.2%}")
-
-# Cumulative contribution
-cumulative_contrib = (1 + weighted_returns).cumprod()
-
-print("\n=== PERFORMANCE ATTRIBUTION ===")
-print("Final cumulative contribution by asset:")
-final_contrib = cumulative_contrib.iloc[-1]
-for asset, contrib in final_contrib.items():
-     print(f"  {asset}: {contrib:.3f}")
-
-
-
-
-

Rolling Portfolio Analysis

-

Analyze how portfolio characteristics change over time:

-
# Rolling correlation with market (S&P 500)
-market_proxy = assets.constituents[0]  # Assuming first asset is S&P 500
-
-# Create frame with portfolio and market
-portfolio_vs_market = OpenFrame(constituents=[equal_weight_portfolio, market_proxy])
-
-# Calculate rolling correlation
-rolling_corr = portfolio_vs_market.rolling_corr(observations=252)  # 1-year rolling
-
-print(f"\nRolling correlation calculated for {len(rolling_corr)} periods")
-print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}")
-print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}")
-
-# Rolling portfolio volatility
-portfolio_rolling_vol = equal_weight_portfolio.rolling_vol(observations=252)
-
-print(f"\nRolling volatility statistics:")
-print(f"Average volatility: {portfolio_rolling_vol.mean().iloc[0]:.2%}")
-print(f"Volatility range: {portfolio_rolling_vol.min().iloc[0]:.2%} to {portfolio_rolling_vol.max().iloc[0]:.2%}")
-
-
-
-
-

Rebalancing Analysis

-

Analyze the impact of rebalancing frequency using the realistic rebalanced_portfolio method:

-
# Compare different rebalancing frequencies using realistic simulation
-frequencies = [1, 21, 63]  # Daily, monthly, quarterly
-frequency_names = ["Daily", "Monthly", "Quarterly"]
-
-rebalanced_portfolios = []
-
-for freq, name in zip(frequencies, frequency_names):
-     portfolio = assets.rebalanced_portfolio(
-          name=f"{name} Rebalanced",
-          frequency=freq,
-          bal_weights=equal_weights
-     )
-     rebalanced_portfolios.append(portfolio.constituents[-1])
-
-# Compare with theoretical portfolio
-assets.weights = equal_weights
-theoretical_portfolio_df = assets.make_portfolio(name="Theoretical")
-theoretical_portfolio = OpenTimeSeries.from_df(dframe=theoretical_portfolio_df)
-
-# Create comprehensive comparison
-all_portfolios = [theoretical_portfolio] + rebalanced_portfolios
-comparison_frame = OpenFrame(constituents=all_portfolios)
-comparison_metrics = comparison_frame.all_properties()
-
-print("\n=== REALISTIC REBALANCING COMPARISON ===")
-print("Strategy | Return | Volatility | Sharpe | Max DD")
-print("-" * 50)
-
-for series in all_portfolios:
-     ret = comparison_metrics.loc['Geometric return', series.label].iloc[0] * 100
-     vol = comparison_metrics.loc['Volatility', series.label].iloc[0] * 100
-     sharpe = comparison_metrics.loc['Return vol ratio', series.label].iloc[0]
-     max_dd = comparison_metrics.loc['Max drawdown', series.label].iloc[0] * 100
-
-     print(f"{series.label:>15} | {ret:6.2f}% | {vol:10.2f}% | {sharpe:6.2f} | {max_dd:6.2f}%")
-
-# Analyze transaction costs
-print(f"\n=== TRANSACTION COST ANALYSIS ===")
-for freq, name in zip(frequencies, frequency_names):
-     detailed_portfolio = assets.rebalanced_portfolio(
-          name=f"{name} Detailed",
-          frequency=freq,
-          bal_weights=equal_weights,
-          drop_extras=False  # Get detailed trading data
-     )
-
-     # Count rebalancing events
-     rebalancing_days = 0
-     for series in detailed_portfolio.constituents:
-          if "buysell_qty" in series.label:
-                # Count days with non-zero trading
-                trading_days = (series.tsdf != 0).any(axis=1).sum()
-                rebalancing_days = max(rebalancing_days, trading_days)
-
-     print(f"{name:>15}: {rebalancing_days} rebalancing events")
-
-
-
-
-

Stress Testing

-

Test portfolio performance during market stress:

-
# Identify worst periods for the market (modifies original)
-market_proxy.value_to_ret()
-market_returns_df = market_proxy.tsdf
-
-# Find worst 5% of days
-worst_days_threshold = market_returns_df.quantile(0.05).iloc[0]
-worst_days = market_returns_df[market_returns_df <= worst_days_threshold]
-
-print(f"\n=== STRESS TEST RESULTS ===")
-print(f"Market stress threshold: {worst_days_threshold:.2%}")
-print(f"Number of stress days: {len(worst_days)}")
-
-# Portfolio performance during stress (modifies original)
-equal_weight_portfolio.value_to_ret()
-portfolio_returns_df = equal_weight_portfolio.tsdf
-
-# Align dates and calculate portfolio performance during market stress
-stress_dates = worst_days.index
-portfolio_stress_returns = portfolio_returns_df.loc[stress_dates]
-
-print(f"Portfolio average return during stress: {portfolio_stress_returns.mean().iloc[0]:.2%}")
-print(f"Portfolio worst day during stress: {portfolio_stress_returns.min().iloc[0]:.2%}")
-
-
-
-
-

Summary Report

-

Generate a comprehensive portfolio analysis report:

-
print("\n" + "="*60)
-print("PORTFOLIO ANALYSIS SUMMARY REPORT")
-print("="*60)
-
-print(f"\nAnalysis Period: {assets.first_idx} to {assets.last_idx}")
-print(f"Number of Assets: {assets.item_count}")
-print(f"Asset Universe: {', '.join([s.label for s in assets.constituents])}")
-
-print(f"\n--- EQUAL WEIGHT PORTFOLIO PERFORMANCE ---")
-print(f"Total Return: {equal_weight_portfolio.value_ret:.2%}")
-print(f"Annualized Return: {equal_weight_portfolio.geo_ret:.2%}")
-print(f"Annualized Volatility: {equal_weight_portfolio.vol:.2%}")
-print(f"Sharpe Ratio: {equal_weight_portfolio.ret_vol_ratio:.2f}")
-print(f"Maximum Drawdown: {equal_weight_portfolio.max_drawdown:.2%}")
-print(f"95% VaR (daily): {equal_weight_portfolio.var_down:.2%}")
-
-print(f"\n--- PORTFOLIO CHARACTERISTICS ---")
-avg_correlation = correlation_matrix.mean().mean()
-print(f"Average Asset Correlation: {avg_correlation:.3f}")
-print(f"Portfolio Diversification Benefit: {(asset_metrics.loc['Volatility'].mean() - equal_weight_portfolio.vol):.2%}")
-
-# Export results
-portfolio_metrics.to_excel("portfolio_analysis.xlsx")
-correlation_matrix.to_excel("correlation_matrix.xlsx")
-
-print(f"\nResults exported to Excel files")
-print("Analysis complete!")
-
-
-

This tutorial provides a comprehensive framework for portfolio analysis using openseries. You can extend these techniques for more sophisticated portfolio management strategies.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/tutorials/risk_management.html b/docs/build/html/tutorials/risk_management.html deleted file mode 100644 index a7bbefa0..00000000 --- a/docs/build/html/tutorials/risk_management.html +++ /dev/null @@ -1,673 +0,0 @@ - - - - - - - - - Risk Management — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Risk Management

-

This tutorial demonstrates comprehensive risk management techniques using openseries, including VaR calculations, stress testing, and risk monitoring.

-
-

Setting Up Risk Analysis

-

Let’s start with a portfolio of assets for risk analysis:

-
import yfinance as yf
-from openseries import OpenTimeSeries, OpenFrame
-from datetime import datetime, timedelta
-import warnings
-warnings.filterwarnings('ignore')
-
-# Download data for a mixed portfolio
-tickers = {
-     "AAPL": "Apple Inc.",
-     "GOOGL": "Alphabet Inc.",
-     "MSFT": "Microsoft Corp.",
-     "TSLA": "Tesla Inc.",
-     "SPY": "SPDR S&P 500 ETF",
-     "QQQ": "Invesco QQQ Trust",
-     "TLT": "iShares 20+ Year Treasury",
-     "GLD": "SPDR Gold Shares"
-}
-
-# Download 3 years of data
-series_list = []
-for ticker, name in tickers.items():
-     # This may fail if the ticker is invalid or data unavailable
-     data = yf.Ticker(ticker).history(period="3y")
-     series = OpenTimeSeries.from_df(
-          dframe=data['Close']
-     )
-     series.set_new_label(lvl_zero=name)
-     series_list.append(series)
-     print(f"Loaded {name}: {series.length} observations")
-
-# Create portfolio frame
-portfolio_assets = OpenFrame(constituents=series_list)
-
-# Create equal-weighted portfolio
-n_assets = portfolio_assets.item_count
-
-# Set weights on the frame first
-portfolio_df = portfolio_assets.make_portfolio(
-     name="Diversified Portfolio",
-     weight_strat="eq_weights"
-)
-portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-print(f"\nPortfolio created with {n_assets} assets")
-print(f"Date range: {portfolio.first_idx} to {portfolio.last_idx}")
-
-
-
-
-

Basic Risk Metrics

-

Start with fundamental risk measurements:

-
print("=== BASIC RISK METRICS ===")
-
-# Volatility measures
-print(f"Annualized Volatility: {portfolio.vol:.2%}")
-print(f"Downside Deviation: {portfolio.downside_deviation:.2%}")
-
-# Return distribution
-print(f"Skewness: {portfolio.skew:.3f}")
-print(f"Kurtosis: {portfolio.kurtosis:.3f}")
-
-# Tail risk
-print(f"Worst Single Day: {portfolio.worst:.2%}")
-print(f"Worst Month: {portfolio.worst_month:.2%}")
-
-# Drawdown analysis
-print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}")
-print(f"Max Drawdown Date: {portfolio.max_drawdown_date}")
-
-
-
-
-

Value at Risk (VaR) Analysis

-

Calculate VaR at different confidence levels:

-
print("\n=== VALUE AT RISK ANALYSIS ===")
-
-# VaR at different confidence levels
-confidence_levels = [0.90, 0.95, 0.99]
-
-for level in confidence_levels:
-     var_value = portfolio.var_down_func(level=level)
-     print(f"{level*100:.0f}% VaR (daily): {var_value:.2%}")
-
-# Convert daily VaR to different time horizons
-# Assuming normal distribution and independence
-daily_var_95 = portfolio.var_down_func(level=0.95)
-
-print(f"\n=== VaR TIME HORIZONS (95% confidence) ===")
-print(f"1-day VaR: {daily_var_95:.2%}")
-# Scale VaR to different time horizons
-print(f"1-week VaR: {daily_var_95 * (5 ** 0.5):.2%}")
-print(f"1-month VaR: {daily_var_95 * (22 ** 0.5):.2%}")
-print(f"1-year VaR: {daily_var_95 * (252 ** 0.5):.2%}")
-
-
-
-
-

Conditional Value at Risk (CVaR)

-

Analyze expected shortfall beyond VaR:

-
print("\n=== CONDITIONAL VALUE AT RISK (CVaR) ===")
-
-for level in confidence_levels:
-     cvar_value = portfolio.cvar_down_func(level=level)
-     var_value = portfolio.var_down_func(level=level)
-
-     print(f"{level*100:.0f}% CVaR: {cvar_value:.2%} (VaR: {var_value:.2%})")
-     print(f"  Expected loss beyond VaR: {cvar_value - var_value:.2%}")
-
-
-
-
-

Rolling Risk Analysis

-

Monitor how risk changes over time:

-
# Calculate rolling risk metrics
-window = 252  # 1-year rolling window
-
-print(f"\n=== ROLLING RISK ANALYSIS ({window}-day window) ===")
-
-# Rolling volatility
-rolling_vol = portfolio.rolling_vol(observations=window)
-print(f"Rolling Volatility - Current: {rolling_vol.iloc[-1, 0]:.2%}")
-print(f"Rolling Volatility - Average: {rolling_vol.mean().iloc[0]:.2%}")
-print(f"Rolling Volatility - Range: {rolling_vol.min().iloc[0]:.2%} to {rolling_vol.max().iloc[0]:.2%}")
-
-# Rolling VaR
-rolling_var = portfolio.rolling_var_down(observations=window)
-print(f"Rolling VaR (95%) - Current: {rolling_var.iloc[-1, 0]:.2%}")
-print(f"Rolling VaR (95%) - Average: {rolling_var.mean().iloc[0]:.2%}")
-
-# Rolling CVaR
-rolling_cvar = portfolio.rolling_cvar_down(observations=window)
-print(f"Rolling CVaR (95%) - Current: {rolling_cvar.iloc[-1, 0]:.2%}")
-print(f"Rolling CVaR (95%) - Average: {rolling_cvar.mean().iloc[0]:.2%}")
-
-
-
-
-

Stress Testing

-

Test portfolio performance under extreme scenarios:

-
-

Historical Stress Testing

-
print("\n=== HISTORICAL STRESS TESTING ===")
-
-# Convert to returns for analysis (modifies original)
-portfolio.value_to_ret()
-returns_data = portfolio.tsdf
-
-# Note: value_to_ret() modifies the original series in place
-# Restore the original portfolio for further analysis
-portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-# Identify worst periods
-worst_1_percent = returns_data.quantile(0.01).iloc[0]
-worst_5_percent = returns_data.quantile(0.05).iloc[0]
-
-print(f"Worst 1% threshold: {worst_1_percent:.2%}")
-print(f"Worst 5% threshold: {worst_5_percent:.2%}")
-
-# Count extreme events
-extreme_events_1pct = (returns_data <= worst_1_percent).sum().iloc[0]
-extreme_events_5pct = (returns_data <= worst_5_percent).sum().iloc[0]
-
-print(f"Days with returns <= 1% threshold: {extreme_events_1pct}")
-print(f"Days with returns <= 5% threshold: {extreme_events_5pct}")
-
-# Worst consecutive days - simplified approach
-print(f"\nWorst 5 single days:")
-returns_series = returns_data.iloc[:, 0]  # Get the first (and only) column
-worst_5_days = returns_series.nsmallest(5)
-for i, (date, return_val) in enumerate(worst_5_days.items()):
-     print(f"  {i+1}. {date.strftime('%Y-%m-%d')}: {return_val:.2%}")
-
-
-
-
-

Scenario Analysis

-
print("\n=== SCENARIO ANALYSIS ===")
-
-# Define stress scenarios (percentage moves in underlying assets)
-scenarios = {
-     "Market Crash": [-0.20, -0.25, -0.22, -0.30, -0.18, -0.20, 0.05, 0.10],
-     "Tech Selloff": [-0.35, -0.40, -0.30, -0.45, -0.10, -0.15, 0.02, 0.03],
-     "Interest Rate Shock": [-0.10, -0.12, -0.08, -0.15, -0.05, -0.08, -0.15, 0.01],
-     "Flight to Quality": [0.05, 0.02, 0.08, -0.10, 0.10, 0.12, 0.20, 0.15]
-}
-
-print("Portfolio impact under stress scenarios:")
-for scenario_name, asset_moves in scenarios.items():
-     # Calculate portfolio impact
-     portfolio_impact = sum(w * move for w, move in zip(equal_weights, asset_moves))
-     print(f"  {scenario_name}: {portfolio_impact:.2%}")
-
-
-
-
-
-

Monte Carlo Risk Simulation

-

Use Monte Carlo methods for risk assessment:

-
print("\n=== MONTE CARLO RISK SIMULATION ===")
-
-# Import the simulate_portfolios function
-from openseries.portfoliotools import simulate_portfolios
-
-# Monte Carlo simulation using native function
-num_simulations = 10000
-seed = 42  # For reproducible results
-
-# Generate simulated portfolios using the native function
-simulated_portfolios = simulate_portfolios(
-     simframe=portfolio_assets,
-     num_ports=num_simulations,
-     seed=seed
-)
-
-# Extract portfolio metrics from simulation
-portfolio_returns = simulated_portfolios['ret']
-portfolio_volatilities = simulated_portfolios['stdev']
-portfolio_sharpes = simulated_portfolios['sharpe']
-
-# Calculate risk metrics from simulation
-# Calculate 5th percentile manually
-sorted_returns = sorted(portfolio_returns)
-percentile_idx = int(len(sorted_returns) * 0.05)
-sim_var_95 = sorted_returns[percentile_idx]
-sim_cvar_95 = portfolio_returns[portfolio_returns <= sim_var_95].mean()
-
-print(f"Monte Carlo Results ({num_simulations:,} simulations):")
-print(f"Expected Return: {portfolio_returns.mean():.2%}")
-print(f"Average Volatility: {portfolio_volatilities.mean():.2%}")
-print(f"95% VaR: {sim_var_95:.2%}")
-print(f"95% CVaR: {sim_cvar_95:.2%}")
-# Calculate percentiles manually
-worst_idx = int(len(sorted_returns) * 0.001)
-best_idx = int(len(sorted_returns) * 0.999)
-print(f"Worst Case (0.1%): {sorted_returns[worst_idx]:.2%}")
-print(f"Best Case (99.9%): {sorted_returns[best_idx]:.2%}")
-print(f"Average Sharpe Ratio: {portfolio_sharpes.mean():.3f}")
-
-# Show distribution of portfolio characteristics
-print(f"\nPortfolio Distribution:")
-print(f"Return Range: {portfolio_returns.min():.2%} to {portfolio_returns.max():.2%}")
-print(f"Volatility Range: {portfolio_volatilities.min():.2%} to {portfolio_volatilities.max():.2%}")
-print(f"Sharpe Range: {portfolio_sharpes.min():.3f} to {portfolio_sharpes.max():.3f}")
-
-
-
-
-

Risk Decomposition

-

Analyze risk contribution by asset:

-
print("\n=== RISK DECOMPOSITION ===")
-
-# Calculate individual asset volatilities using OpenFrame
-asset_metrics = portfolio_assets.all_properties()
-asset_vols = asset_metrics.loc['Volatility'].values
-
-# Portfolio volatility
-portfolio_vol = portfolio.vol
-
-# Calculate correlation matrix
-correlation_matrix = portfolio_assets.correl_matrix
-
-# Risk contribution analysis using openseries
-# Create portfolio to get portfolio-level metrics
-portfolio_df = portfolio_assets.make_portfolio(name="Portfolio", weight_strat="eq_weights")
-portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-portfolio_vol = portfolio.vol
-
-print("Risk Contribution Analysis:")
-for i, series in enumerate(portfolio_assets.constituents):
-    weight = equal_weights[i]
-    asset_vol = asset_vols[i]
-    print(f"\n{series.label}:")
-    print(f"  Weight: {weight:.4f}")
-    print(f"  Individual Volatility: {asset_vol:.4f}")
-    print(f"  Weighted Volatility Contribution: {weight * asset_vol:.4f}")
-print(f"\nPortfolio Volatility: {portfolio_vol:.4f}")
-
-# Verify portfolio metrics
-print(f"\nVerification:")
-print(f"Portfolio volatility: {portfolio_vol:.4f}")
-
-
-
-
-

Risk-Adjusted Performance

-

Evaluate risk-adjusted returns:

-
print("\n=== RISK-ADJUSTED PERFORMANCE ===")
-
-# Sharpe ratio
-print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}")
-
-# Sortino ratio (downside risk only)
-print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}")
-
-# Kappa-3 ratio (higher-order downside risk)
-print(f"Kappa-3 Ratio: {portfolio.kappa3_ratio:.3f}")
-
-# Omega ratio
-print(f"Omega Ratio: {portfolio.omega_ratio:.3f}")
-
-# Compare with individual assets
-print(f"\n=== RISK-ADJUSTED COMPARISON ===")
-all_assets = portfolio_assets.constituents + [portfolio]
-comparison_frame = OpenFrame(constituents=all_assets)
-
-risk_adj_metrics = comparison_frame.all_properties(
-     properties=['ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'omega_ratio']
-)
-
-print(risk_adj_metrics.round(3))
-
-
-
-
-

Risk Monitoring Dashboard

-

Create a comprehensive risk monitoring summary using openseries properties and methods:

-
print("\n" + "="*60)
-print("RISK MONITORING DASHBOARD")
-print("="*60)
-
-# Current date and lookback period
-current_date = portfolio.last_idx
-lookback_date = portfolio.first_idx
-
-print(f"Portfolio: {portfolio.label}")
-print(f"Current Date: {current_date}")
-print(f"Analysis Period: {lookback_date} to {current_date}")
-print(f"Observations: {portfolio.length}")
-
-# Risk metrics using openseries properties
-print(f"\n--- CURRENT RISK METRICS ---")
-print(f"Volatility (annualized): {portfolio.vol:.2%}")
-print(f"Downside Deviation: {portfolio.downside_deviation:.2%}")
-print(f"95% VaR (daily): {portfolio.var_down:.2%}")
-print(f"95% CVaR (daily): {portfolio.cvar_down:.2%}")
-print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}")
-
-# Performance metrics using openseries properties
-print(f"\n--- PERFORMANCE METRICS ---")
-print(f"Total Return: {portfolio.value_ret:.2%}")
-print(f"Annualized Return: {portfolio.geo_ret:.2%}")
-print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}")
-print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}")
-
-# Distribution characteristics using openseries properties
-print(f"\n--- RETURN DISTRIBUTION ---")
-print(f"Skewness: {portfolio.skew:.3f}")
-print(f"Kurtosis: {portfolio.kurtosis:.3f}")
-print(f"Positive Days: {portfolio.positive_share:.1%}")
-
-# Recent performance using openseries properties
-recent_return = portfolio.z_score
-print(f"\n--- RECENT ACTIVITY ---")
-print(f"Last Return Z-Score: {recent_return:.2f}")
-
-if abs(recent_return) > 2:
-     print("  ⚠️  ALERT: Recent return is unusual (|z| > 2)")
-elif abs(recent_return) > 3:
-     print("  🚨 WARNING: Recent return is extreme (|z| > 3)")
-else:
-     print("  ✅ Recent return is within normal range")
-
-# Risk alerts based on openseries metrics
-print(f"\n--- RISK ALERTS ---")
-alerts = []
-
-if portfolio.vol > 0.25:
-     alerts.append("High volatility (>25%)")
-
-if abs(portfolio.max_drawdown) > 0.20:
-     alerts.append("Large maximum drawdown (>20%)")
-
-if portfolio.ret_vol_ratio < 0.5:
-     alerts.append("Low Sharpe ratio (<0.5)")
-
-if portfolio.skew < -1:
-     alerts.append("Highly negative skew (<-1)")
-
-if portfolio.kurtosis > 5:
-     alerts.append("High kurtosis (>5) - fat tails")
-
-if alerts:
-     for alert in alerts:
-          print(f"  ⚠️  {alert}")
-else:
-     print("  ✅ No risk alerts")
-
-
-
-
-

Risk Limits and Controls

-

Implement risk limit monitoring:

-
print("\n=== RISK LIMITS MONITORING ===")
-
-# Define risk limits
-risk_limits = {
-     'max_volatility': 0.20,      # 20% annual volatility
-     'max_var_daily': -0.03,      # 3% daily VaR
-     'max_drawdown': -0.15,       # 15% maximum drawdown
-     'min_sharpe': 0.5,           # Minimum Sharpe ratio
-     'max_concentration': 0.30    # Maximum single asset weight
-}
-
-# Check current metrics against limits
-current_metrics = {
-     'volatility': portfolio.vol,
-     'var_daily': portfolio.var_down,
-     'drawdown': portfolio.max_drawdown,
-     'sharpe': portfolio.ret_vol_ratio,
-     'max_weight': max(equal_weights)
-}
-
-print("Risk Limit Monitoring:")
-print("-" * 40)
-
-# Volatility check
-if current_metrics['volatility'] > risk_limits['max_volatility']:
-     print(f"❌ BREACH: Volatility {current_metrics['volatility']:.2%} > {risk_limits['max_volatility']:.2%}")
-else:
-     print(f"✅ OK: Volatility {current_metrics['volatility']:.2%} <= {risk_limits['max_volatility']:.2%}")
-
-# VaR check
-if current_metrics['var_daily'] < risk_limits['max_var_daily']:
-     print(f"❌ BREACH: VaR {current_metrics['var_daily']:.2%} < {risk_limits['max_var_daily']:.2%}")
-else:
-     print(f"✅ OK: VaR {current_metrics['var_daily']:.2%} >= {risk_limits['max_var_daily']:.2%}")
-
-# Drawdown check
-if current_metrics['drawdown'] < risk_limits['max_drawdown']:
-     print(f"❌ BREACH: Drawdown {current_metrics['drawdown']:.2%} < {risk_limits['max_drawdown']:.2%}")
-else:
-     print(f"✅ OK: Drawdown {current_metrics['drawdown']:.2%} >= {risk_limits['max_drawdown']:.2%}")
-
-# Sharpe ratio check
-if current_metrics['sharpe'] < risk_limits['min_sharpe']:
-     print(f"❌ BREACH: Sharpe {current_metrics['sharpe']:.3f} < {risk_limits['min_sharpe']:.3f}")
-else:
-     print(f"✅ OK: Sharpe {current_metrics['sharpe']:.3f} >= {risk_limits['min_sharpe']:.3f}")
-
-# Concentration check
-if current_metrics['max_weight'] > risk_limits['max_concentration']:
-     print(f"❌ BREACH: Max weight {current_metrics['max_weight']:.2%} > {risk_limits['max_concentration']:.2%}")
-else:
-     print(f"✅ OK: Max weight {current_metrics['max_weight']:.2%} <= {risk_limits['max_concentration']:.2%}")
-
-
-
-
-

Export Risk Report

-

Save comprehensive risk analysis:

-
# Create comprehensive risk report
-# Create risk report using openseries methods
-print("\n=== RISK REPORT ===")
-print("Risk metrics are available through openseries properties:")
-for series in portfolio_assets.constituents:
-    print(f"\n{series.label}:")
-    print(f"  VaR (95%): {series.var_down:.4f}")
-    print(f"  CVaR (95%): {series.cvar_down:.4f}")
-    print(f"  Volatility: {series.vol:.4f}")
-    print(f"  Max Drawdown: {series.max_drawdown:.4f}")
-
-# Note: For comprehensive Excel export, use openseries to_xlsx() method
-portfolio_assets.to_xlsx('risk_analysis_report.xlsx')
-
-# Alternative: risk_report = pd.DataFrame({
-     'Metric': [
-          'Annualized Return', 'Annualized Volatility', 'Sharpe Ratio',
-          'Sortino Ratio', 'Maximum Drawdown', '95% VaR (daily)',
-          '95% CVaR (daily)', 'Skewness', 'Kurtosis', 'Positive Days %'
-     ],
-     'Value': [
-          f"{portfolio.geo_ret:.2%}",
-          f"{portfolio.vol:.2%}",
-          f"{portfolio.ret_vol_ratio:.3f}",
-          f"{portfolio.sortino_ratio:.3f}",
-          f"{portfolio.max_drawdown:.2%}",
-          f"{portfolio.var_down:.2%}",
-          f"{portfolio.cvar_down:.2%}",
-          f"{portfolio.skew:.3f}",
-          f"{portfolio.kurtosis:.3f}",
-          f"{portfolio.positive_share:.1%}"
-     ]
-})
-
-# Export to Excel
-# Export using openseries native method (commented out ExcelWriter approach)
-# with pd.ExcelWriter('risk_analysis_report.xlsx') as writer:
-     risk_report.to_excel(writer, sheet_name='Risk Metrics', index=False)
-     risk_decomp.to_excel(writer, sheet_name='Risk Decomposition', index=False)
-     correlation_matrix.to_excel(writer, sheet_name='Correlations')
-
-     # Add rolling metrics if available
-     if 'rolling_vol' in locals():
-          rolling_vol.to_excel(writer, sheet_name='Rolling Volatility')
-     if 'rolling_var' in locals():
-          rolling_var.to_excel(writer, sheet_name='Rolling VaR')
-
-print(f"\nRisk analysis report exported to 'risk_analysis_report.xlsx'")
-print("Risk management analysis complete!")
-
-
-

This comprehensive risk management tutorial provides the foundation for implementing robust risk controls and monitoring systems using openseries.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/user_guide/core_concepts.html b/docs/build/html/user_guide/core_concepts.html deleted file mode 100644 index 79fa5078..00000000 --- a/docs/build/html/user_guide/core_concepts.html +++ /dev/null @@ -1,603 +0,0 @@ - - - - - - - - - Core Concepts — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Core Concepts

-

This section explains the fundamental concepts and design principles behind openseries.

-
-

Architecture Overview

-

openseries is built around two main classes that inherit from Pydantic’s BaseModel:

-
    -
  • OpenTimeSeries: Manages individual financial time series

  • -
  • OpenFrame: Manages collections of OpenTimeSeries objects

  • -
-

Both classes provide:

-
    -
  • Type safety through Pydantic validation

  • -
  • Immutable data - original data is preserved

  • -
  • Consistent API - similar methods across both classes

  • -
  • Financial focus - methods designed for financial analysis

  • -
-
-
-

Mutation and data layers

-

openseries favors in-place transformations. Many methods modify the existing object -and return self for chaining rather than creating a new object. -On OpenTimeSeries, the dates and values arrays are always left untouched, -while the working data in the tsdf pandas DataFrame is mutable. -On OpenFrame, the tsdf DataFrame is also mutable and reflects -transformations applied to the frame. If you need to preserve the original state or -compare before/after results, create an explicit copy -(for example, OpenTimeSeries.from_deepcopy() or OpenFrame.from_deepcopy()).

-
-
-

The OpenTimeSeries Class

-
-

Core Properties

-

Every OpenTimeSeries has these fundamental properties:

-
# Create a sample series using openseries simulation
-from openseries import ReturnSimulation, ValueType
-import datetime as dt
-
-simulation = ReturnSimulation.from_lognormal(
-     number_of_sims=1,
-     trading_days=100,
-     mean_annual_return=0.25,  # ~0.001 daily
-     mean_annual_vol=0.32,     # ~0.02 daily
-     trading_days_in_year=252,
-     seed=42
-)
-
-series = OpenTimeSeries.from_df(
-     dframe=simulation.to_dataframe(name="Sample Asset", end=dt.date(2023, 12, 31)),
-     valuetype=ValueType.RTRN
-).to_cumret()  # Convert returns to cumulative prices
-
-# Core properties
-print(f"Name: {series.label}")
-print(f"Length: {series.length}")
-print(f"First date: {series.first_idx}")
-print(f"Last date: {series.last_idx}")
-print(f"Value type: {series.valuetype}")
-
-
-
-
-

Data Immutability

-

The original data is never modified:

-
# Original data is preserved
-original_dates = series.dates      # List of date strings
-original_values = series.values    # List of float values
-
-# Working data is in the tsdf DataFrame
-working_data = series.tsdf         # pandas DataFrame
-
-# Transformations modify the original object (method chaining)
-series.value_to_ret()    # Modifies original series
-print(f"Series length: {series.length}")  # Usually length - 1
-
-
-
-
-

Value Types

-

The ValueType enum identifies what the series represents:

-
from openseries import ValueType
-
-# Common value types
-print(ValueType.PRICE)      # "Price(Close)"
-print(ValueType.RTRN)       # "Return(Total)"
-print(ValueType.ROLLVOL)    # "Rolling volatility"
-
-# Check series type
-print(f"Series type: {series.valuetype}")
-
-# Type changes with transformations
-series.value_to_ret()  # Modifies original
-print(f"Returns type: {series.valuetype}")
-
-
-
-
-
-

The OpenFrame Class

-
-

Managing Multiple Series

-

OpenFrame manages collections of OpenTimeSeries:

-
from openseries import OpenFrame
-
-# Create multiple series using openseries simulation
-simulation = ReturnSimulation.from_lognormal(
-     number_of_sims=3,
-     trading_days=100,
-     mean_annual_return=0.25,  # ~0.001 daily
-     mean_annual_vol=0.32,     # ~0.02 daily
-     trading_days_in_year=252,
-     seed=42
-)
-
-# Create OpenFrame with multiple series from simulation
-frame = OpenFrame(
-     constituents=[
-          OpenTimeSeries.from_df(
-                dframe=simulation.to_dataframe(name="Asset", end=dt.date(2023, 12, 31)),
-                column_nmbr=serie,
-                valuetype=ValueType.RTRN,
-          ).to_cumret()  # Convert returns to cumulative prices
-          for serie in range(simulation.number_of_sims)
-     ]
-)
-
-# Frame properties
-print(f"Number of series: {frame.item_count}")
-print(f"Column names: {frame.columns_lvl_zero}")
-print(f"Common length: {frame.length}")
-
-
-
-
-

Data Alignment

-

OpenFrame concatenates series data but does not automatically align them. -The library provides explicit methods for alignment that require user choice:

-
# Series with different date ranges are concatenated (not aligned)
-print("Individual series lengths:")
-print(frame.lengths_of_items)
-
-print(f"Frame length (concatenated): {frame.length}")
-
-# Explicit alignment methods require user choice:
-
-# 1. Truncate to common date range
-frame.trunc_frame()
-
-# 2. Align to business day calendar (modifies original)
-frame.align_index_to_local_cdays(countries="US")
-
-# 3. Handle missing values (modifies original)
-frame.value_nan_handle(method="fill")
-
-# 4. Merge with explicit join strategy
-frame.merge_series(how="inner")
-frame.merge_series(how="outer")
-
-
-
-
-
-

Financial Calculations

-
-

Return Calculations

-

openseries uses standard financial formulas:

-
# Simple returns: (P_t / P_{t-1}) - 1
-series.value_to_ret()  # Modifies original
-
-# Log returns: ln(P_t / P_{t-1})
-series.value_to_log()  # Modifies original
-
-# Cumulative returns: rebasing to start at 1.0 (modifies original)
-series.to_cumret()
-
-
-
-
-

Annualization

-

Metrics are annualized using the actual number of observations per year:

-
# Automatic calculation of periods per year
-print(f"Periods per year: {series.periods_in_a_year:.1f}")
-
-# Annualized return (geometric mean)
-annual_return = series.geo_ret
-print(f"Annualized return: {annual_return:.2%}")
-
-# Annualized volatility
-annual_vol = series.vol
-print(f"Annualized volatility: {annual_vol:.2%}")
-
-
-
-
-

Risk Metrics

-

Risk calculations follow industry standards:

-
# Value at Risk (95% confidence)
-var_95 = series.var_down
-print(f"95% VaR: {var_95:.2%}")
-
-# Conditional Value at Risk (Expected Shortfall)
-cvar_95 = series.cvar_down
-print(f"95% CVaR: {cvar_95:.2%}")
-
-# Maximum Drawdown
-max_dd = series.max_drawdown
-print(f"Maximum Drawdown: {max_dd:.2%}")
-
-# Sortino Ratio (downside deviation)
-sortino = series.sortino_ratio
-print(f"Sortino Ratio: {sortino:.2f}")
-
-
-
-
-
-

Date Handling

-
-

Business Day Calendars

-

openseries integrates with business day calendars:

-
# Align to specific country's business days (modifies original)
-series.align_index_to_local_cdays(countries="US")
-
-# Multiple countries (intersection of business days) (modifies original)
-series.align_index_to_local_cdays(countries=["US", "GB"])
-
-# Custom markets using pandas-market-calendars (modifies original)
-series.align_index_to_local_cdays(markets="NYSE")
-
-
-
-
-

Resampling

-

Convert between different frequencies:

-
# Resample to month-end (modifies original)
-series.resample_to_business_period_ends(freq="BME")
-
-# Resample to quarter-end (modifies original)
-series.resample_to_business_period_ends(freq="BQE")
-
-# Custom resampling (modifies original)
-series.resample(freq="W")
-
-
-
-
-
-

Data Validation

-
-

Type Safety

-

Pydantic ensures data integrity:

-
# Dates must be valid ISO format strings
-# This will fail with a validation error
-invalid_series = OpenTimeSeries.from_arrays(
-     dates=["invalid-date"],
-     values=[100.0]
-)
-
-# Values must be numeric
-# This will fail with a validation error
-invalid_series = OpenTimeSeries.from_arrays(
-     dates=["2023-01-01"],
-     values=["not a number"]
-)
-
-
-
-
-

Consistency Checks

-

The library performs consistency checks:

-
# Dates and values must have same length
-# Mixed value types in OpenFrame are detected
-# Date alignment issues are caught
-
-
-
-
-
-

Method Categories

-

openseries methods fall into several categories:

-
-

Properties vs Methods

-
    -
  • Properties: Return calculated values (e.g., series.vol)

  • -
  • Methods: Perform operations or take parameters (e.g., series.vol_func())

  • -
-
# Property - uses full series
-volatility = series.vol
-
-# Method - can specify date range
-recent_vol = series.vol_func(months_from_last=12)
-
-
-
-
-

Transformation Methods

-

Methods that modify the original object (return self for chaining):

-
# Data transformations (modify original)
-series.value_to_ret()        # Prices to returns
-series.to_drawdown_series()  # Drawdown series
-series.to_cumret()           # Cumulative returns
-
-# Time transformations (modify original)
-series.resample_to_business_period_ends(freq="BME")
-series.align_index_to_local_cdays(countries="US")
-
-
-

Methods that return new objects:

-
# Analysis methods (return new objects)
-rolling_vol = series.rolling_vol(observations=30)
-rolling_ret = series.rolling_return(observations=30)
-
-
-
-
-

Analysis Methods

-

Methods that return calculated values:

-
# Rolling calculations
-rolling_vol = series.rolling_vol(observations=30)
-rolling_corr = frame.rolling_corr(observations=60)
-
-# Statistical analysis
-beta = frame.beta()
-tracking_error = frame.tracking_error_func()
-
-
-
-
-

Export Methods

-

Methods for saving results:

-
# File exports
-series.to_xlsx("analysis.xlsx")
-series.to_json("data.json")
-
-# Visualization
-series.plot_series()
-series.plot_histogram()
-
-
-
-
-
-

Best Practices

-
-

Data Loading

-
# Prefer from_df for pandas data
-series = OpenTimeSeries.from_df(dframe=dataframe['Close'])
-series.set_new_label(lvl_zero="Asset")
-
-# Use from_arrays for custom data
-series = OpenTimeSeries.from_arrays(dates=date_list, values=value_list)
-
-# Always set meaningful names
-series.set_new_label(lvl_zero="Descriptive Name")
-
-
-
-
-

Analysis Workflow

-
# 1. Load and validate data
-series = OpenTimeSeries.from_df(dframe=data['Close'])
-series.set_new_label(lvl_zero="Asset")
-
-# 2. Basic analysis
-metrics = series.all_properties()
-
-# 3. Specific calculations
-series.to_drawdown_series()  # Convert to drawdown (modifies original)
-rolling_metrics = series.rolling_vol(observations=252)  # Returns DataFrame
-
-# 4. Visualization
-series.plot_series()
-
-# 5. Export results
-series.to_xlsx(fiilename="analysis.xlsx")
-
-
-
-
-

Memory Management

-
# Original data is preserved - use deepcopy if needed
-series_copy = OpenTimeSeries.from_deepcopy(series)
-
-# Large datasets - consider resampling (modifies original)
-series.resample_to_business_period_ends(freq="BME")
-
-# Clean up intermediate results
-del intermediate_series
-
-
-
-
-

Portfolio Construction

-

OpenFrame provides several built-in weight strategies for portfolio construction:

-
from openseries.owntypes import MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError
-
-# Available weight strategies
-strategies = {
-     'eq_weights': 'Equal weights for all assets',
-     'inv_vol': 'Inverse volatility weighting (risk parity)',
-     'max_div': 'Maximum diversification optimization',
-     'min_vol_overweight': 'Minimum volatility overweight strategy'
-}
-
-# Example with error handling
-# This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError
-portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div")
-
-
-

Understanding these core concepts will help you use openseries effectively and build more sophisticated financial analysis workflows.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/user_guide/data_handling.html b/docs/build/html/user_guide/data_handling.html deleted file mode 100644 index 6533cfa3..00000000 --- a/docs/build/html/user_guide/data_handling.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - Data Handling — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Data Handling

-

This guide covers data loading, validation, transformation, and management in openseries.

-
-

Loading Data

-
-

From pandas DataFrame/Series

-

The most common way to load data is from pandas objects:

-
import pandas as pd
-from openseries import OpenTimeSeries
-
-# From pandas Series with DatetimeIndex
-data = pd.Series([100, 101, 99, 102],
-                     index=pd.date_range('2023-01-01', periods=4))
-series = OpenTimeSeries.from_df(dframe=data)
-series.set_new_label(lvl_zero="Sample")
-
-# From pandas DataFrame column
-df = pd.DataFrame({
-     'Date': pd.date_range('2023-01-01', periods=4),
-     'Close': [100, 101, 99, 102],
-     'Volume': [1000, 1100, 900, 1200]
-})
-df.set_index('Date', inplace=True)
-series = OpenTimeSeries.from_df(dframe=df['Close'])
-series.set_new_label(lvl_zero="Stock")
-
-
-
-
-

From Arrays

-

For custom data or when working with lists:

-
# From date strings and values
-dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
-values = [100.0, 101.0, 99.0, 102.0]
-
-series = OpenTimeSeries.from_arrays(
-     dates=dates,
-     values=values,
-     name="Custom Data"
-)
-
-
-
-
-

From Fixed Rate

-

Generate synthetic data from a fixed rate:

-
from datetime import date
-
-# Create 252 trading days at 5% annual rate
-series = OpenTimeSeries.from_fixed_rate(
-     rate=0.05,
-     days=252,
-     end_date=date(2023, 12, 31),
-     name="5% Fixed Rate"
-)
-
-
-
-
-
-

Data Validation

-
-

Date Format Validation

-

openseries enforces strict date formats:

-
# Valid date formats
-valid_dates = ['2023-01-01', '2023-12-31', '2024-02-29']  # ISO format
-
-# Invalid formats will raise ValidationError
-# This will fail with a validation error
-invalid_series = OpenTimeSeries.from_arrays(
-     dates=['01/01/2023', '2023-1-1'],  # Wrong format
-     values=[100, 101]
-)
-
-
-
-
-

Value Validation

-

Values must be numeric and finite:

-
import numpy as np
-
-# Valid values
-valid_values = [100.0, 101.5, 99.25, 102.75]
-
-# Handle NaN values appropriately
-values_with_nan = [100.0, np.nan, 99.0, 102.0]
-series = OpenTimeSeries.from_arrays(
-     dates=['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
-     values=values_with_nan,
-     name="Data with NaN"
-)
-
-# Clean NaN values (modifies original)
-series.value_nan_handle()  # Forward fill
-
-
-
-
-

Length Consistency

-

Dates and values must have the same length:

-
# This will raise an error
-# This will fail with a length mismatch error
-invalid_series = OpenTimeSeries.from_arrays(
-     dates=['2023-01-01', '2023-01-02'],
-     values=[100.0, 101.0, 102.0]  # Different length
-)
-
-
-
-
-
-

Data Transformations

-
-

Price and Return Conversions

-
# Assume we have a price series
-prices = OpenTimeSeries.from_arrays(
-     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
-     values=[100.0, 102.0, 99.0],
-     name="Stock Price"
-)
-
-# Convert to simple returns (modifies original)
-prices.value_to_ret()
-print(f"Returns: {prices.values}")  # [0.02, -0.0294...]
-
-# Convert to log returns (modifies original)
-prices.value_to_log()
-
-# Convert returns back to cumulative values (modifies original)
-prices.to_cumret()
-
-# Convert to differences (absolute changes) (modifies original)
-prices.value_to_diff()
-
-
-
-
-

Resampling

-

Change the frequency of your data:

-
# Daily to monthly (business month end) (modifies original)
-series.resample_to_business_period_ends(freq="BME")
-
-# Daily to quarterly (modifies original)
-series.resample_to_business_period_ends(freq="BQE")
-
-# Daily to annual (modifies original)
-series.resample_to_business_period_ends(freq="BYE")
-
-# Custom resampling with pandas frequency strings (modifies original)
-series.resample(freq="W")
-
-# Resample with specific method (modifies original)
-series.resample(freq="W", method="mean")
-
-
-
-
-

Business Day Alignment

-

Align data to business day calendars:

-
# Align to US business days (modifies original)
-series.align_index_to_local_cdays(countries="US")
-
-# Align to multiple countries (intersection) (modifies original)
-series.align_index_to_local_cdays(countries=["US", "GB", "JP"])
-
-# Align to specific market calendar (modifies original)
-series.align_index_to_local_cdays(markets="NYSE")
-
-
-
-
-
-

Handling Missing Data

-
-

NaN Handling Strategies

-
import numpy as np
-
-# Create series with missing values
-dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
-values = [100.0, np.nan, 102.0, np.nan]
-
-series_with_nan = OpenTimeSeries.from_arrays(
-     dates=dates, values=values, name="With NaN"
-)
-
-# Forward fill missing values (for price series) (modifies original)
-series_with_nan.value_nan_handle()
-
-# For return series, replace NaN with 0.0 (modifies original)
-series_with_nan.value_to_ret()
-series_with_nan.return_nan_handle()
-
-
-
-
-

Dropping Missing Data

-
# Remove NaN values entirely (modifies original)
-series_with_nan.value_nan_handle(method="drop")
-
-
-
-
-
-

Working with Multiple Assets

-
-

Creating OpenFrame

-
from openseries import OpenFrame
-
-# Create multiple series
-series1 = OpenTimeSeries.from_arrays(
-     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
-     values=[100, 102, 99], name="Asset A"
-)
-
-series2 = OpenTimeSeries.from_arrays(
-     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
-     values=[50, 51, 49], name="Asset B"
-)
-
-# Create frame
-frame = OpenFrame(constituents=[series1, series2])
-
-
-
-
-

Handling Different Date Ranges

-

OpenFrame automatically handles series with different date ranges:

-
# Series with different start/end dates
-early_series = OpenTimeSeries.from_arrays(
-     dates=['2022-12-01', '2023-01-01', '2023-01-02'],
-     values=[95, 100, 102], name="Early Start"
-)
-
-late_series = OpenTimeSeries.from_arrays(
-     dates=['2023-01-02', '2023-01-03', '2023-01-04'],
-     values=[51, 49, 52], name="Late Start"
-)
-
-# Frame will align to common date range
-frame = OpenFrame(constituents=[early_series, late_series])
-print(f"Frame date range: {frame.first_idx} to {frame.last_idx}")
-
-
-
-
-

Adding and Removing Series

-
# Add a new series
-new_series = OpenTimeSeries.from_arrays(
-     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
-     values=[200, 205, 198], name="Asset C"
-)
-frame.add_timeseries(new_series)
-
-# Remove a series by index
-frame.delete_timeseries(item_idx=0)
-
-
-
-
-
-

Data Export and Import

-
-

Excel Export

-
# Export single series
-series.to_xlsx(filename="single_series.xlsx")
-
-# Export frame (multiple series)
-frame.to_xlsx(filename="multiple_series.xlsx")
-
-# Export with custom sheet title
-series.to_xlsx(
-     filename="formatted_export.xlsx",
-     sheet_title="Analysis"
-)
-
-
-
-
-

JSON Export

-
# Export series values only
-series.to_json(what_output="values", filename="series_values.json")
-
-# Export full dataframe structure
-series.to_json(what_output="tsdf", filename="series_dataframe.json")
-
-# Export frame data
-frame.to_json(what_output="values", filename="frame_values.json")
-
-
-
-
-
-

Working with Real Data Sources

-
-

Yahoo Finance Integration

-
import yfinance as yf
-
-# Single asset
-ticker = yf.Ticker("AAPL")
-data = ticker.history(period="2y")
-
-apple = OpenTimeSeries.from_df(
-     dframe=data['Close'],
-     name="Apple Inc."
-)
-
-# Multiple assets
-tickers = ["AAPL", "GOOGL", "MSFT"]
-series_list = []
-
-for ticker_symbol in tickers:
-     ticker = yf.Ticker(ticker_symbol)
-     data = ticker.history(period="1y")
-     series = OpenTimeSeries.from_df(
-          dframe=data['Close'],
-          name=ticker_symbol
-     )
-     series_list.append(series)
-
-tech_frame = OpenFrame(constituents=series_list)
-
-
-
-
-

CSV Data

-
# Load from CSV
-df = pd.read_csv("stock_data.csv", index_col=0, parse_dates=True)
-
-series = OpenTimeSeries.from_df(
-     dframe=df['Close'],
-     name="Stock from CSV"
-)
-
-
-
-
-
-

Data Quality Checks

-
-

Validation Methods

-
# Check for data quality issues
-print(f"Series length: {series.length}")
-print(f"Date range: {series.first_idx} to {series.last_idx}")
-print(f"Span of days: {series.span_of_days}")
-
-# Check for gaps in data
-expected_length = (series.last_idx - series.first_idx).days + 1
-actual_length = series.length
-
-if expected_length != actual_length:
-     print(f"Data gaps detected: expected {expected_length}, got {actual_length}")
-
-
-
-
-

Outlier Detection

-
# Convert to returns for outlier analysis (modifies original)
-series.value_to_ret()
-
-# Detect outliers using the built-in method
-outliers = series.outliers(threshold=3.0)
-print(f"Found {len(outliers)} outliers (|z| > 3)")
-
-# For OpenFrame, outliers returns a DataFrame
-frame_outliers = frame.outliers(threshold=3.0)
-print(f"Found outliers in frame: {len(frame_outliers)} rows")
-
-# Customize threshold and date range
-recent_outliers = series.outliers(
-     threshold=2.5,
-     months_from_last=6
-)
-
-
-
-
-
-

Performance Considerations

-
-

Memory Usage

-
# For large datasets, consider resampling
-large_series = series  # Assume this is large daily data
-
-# Reduce to monthly for analysis (modifies original)
-large_series.resample_to_business_period_ends(freq="BME")
-
-# Use monthly for computationally intensive operations
-monthly_metrics = large_series.all_properties()
-
-
-
-
-

Efficient Data Loading

-
# When loading multiple assets, batch the operations
-tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
-
-# Download all at once
-data = yf.download(tickers, period="2y")['Close']
-
-# Create series efficiently
-series_list = []
-for ticker in tickers:
-     series = OpenTimeSeries.from_df(
-          dframe=data[ticker].dropna(),
-          name=ticker
-     )
-     series_list.append(series)
-
-frame = OpenFrame(constituents=series_list)
-
-
-

This comprehensive guide should help you handle various data scenarios effectively with openseries.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/user_guide/installation.html b/docs/build/html/user_guide/installation.html deleted file mode 100644 index 0b50d7a4..00000000 --- a/docs/build/html/user_guide/installation.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - - Installation — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Installation

-
-

System Requirements

-

openseries requires Python 3.11 or higher and is compatible with:

-
    -
  • Operating Systems: Windows, macOS, Linux

  • -
  • Python versions: 3.11, 3.12, 3.13, 3.14

  • -
-
-
-

Installing openseries

- -
-

Using conda

-

openseries is also available on conda-forge:

-
conda install -c conda-forge openseries
-
-
-
-
-

Installing from source

-

To install the latest development version from GitHub:

-
git clone https://github.com/CaptorAB/openseries.git
-cd openseries
-pip install -e .
-
-
-
-
-
-

Dependencies

-

openseries automatically installs the following dependencies:

-
-

Core Dependencies

-
    -
  • pandas (>=2.1.2) - Data manipulation and analysis

  • -
  • numpy (>=1.23.2) - Numerical computing

  • -
  • pydantic (>=2.5.2) - Data validation and settings management

  • -
  • plotly (>=5.18.0) - Interactive plotting

  • -
  • scipy (>=1.14.1) - Scientific computing

  • -
  • scikit-learn (>=1.4.0) - Machine learning utilities

  • -
-
-
-

Financial and Date Utilities

-
    -
  • exchange-calendars (>=4.8) - Trading calendar support

  • -
  • holidays (>=0.30) - Holiday calendar support

  • -
  • python-dateutil (>=2.8.2) - Date parsing utilities

  • -
  • tzdata (>=2025.3) - IANA time zone data

  • -
-
-
-

File and Network Support

-
    -
  • openpyxl (>=3.1.2) - Excel file support

  • -
  • requests (>=2.20.0) - HTTP library

  • -
-
-
-

Optional Dependencies

-

For data acquisition examples, you may want to install:

-
pip install yfinance  # For Yahoo Finance data
-
-
-
-
-
-

Verifying Installation

-

To verify that openseries is installed correctly, run:

-
import openseries
-print(openseries.__version__)
-
-
-

You can also run a quick test:

-
from openseries import OpenTimeSeries, ReturnSimulation, ValueType
-import datetime as dt
-
-# Create sample data using openseries simulation
-simulation = ReturnSimulation.from_lognormal(
-     number_of_sims=1,
-     trading_days=100,
-     mean_annual_return=0.25,  # ~0.001 daily
-     mean_annual_vol=0.32,     # ~0.02 daily
-     trading_days_in_year=252,
-     seed=42
-)
-
-# Create OpenTimeSeries
-series = OpenTimeSeries.from_df(
-     dframe=simulation.to_dataframe(name="Test Series", end=dt.date(2023, 12, 31)),
-     valuetype=ValueType.RTRN
-).to_cumret()  # Convert returns to cumulative prices
-
-print(f"Series length: {series.length}")
-print(f"Annual return: {series.geo_ret:.2%}")
-
-
-
-
-

Development Installation

-

If you plan to contribute to openseries or need the development dependencies, -use the same pinned tooling as CI (uv==0.11.21):

-
git clone https://github.com/CaptorAB/openseries.git
-cd openseries
-make install
-
-
-

On Windows, run .\make.ps1 make instead of make install.

-

This creates venv, installs locked runtime, development, and documentation -dependencies from uv.lock, and installs pre-commit hooks. Development -dependencies include:

-
    -
  • pytest (>=9.1.0) - Testing framework

  • -
  • pytest-cov (>=7.1.0) - Coverage plugin

  • -
  • pytest-xdist (>=3.8.0) - Parallel test runner

  • -
  • mypy (==2.1.0) - Static type checking

  • -
  • ruff (==0.15.18) - Linting and formatting

  • -
  • pre-commit (>=4.6.0) - Git hooks for code quality

  • -
-
-
-

Troubleshooting

-
-

Common Issues

-

ImportError: No module named ‘openseries’

-

Make sure openseries is installed in the correct Python environment. If using virtual environments, ensure it’s activated.

-

Version conflicts

-

If you encounter dependency conflicts, try creating a fresh virtual environment:

-
python -m venv openseries_env
-source openseries_env/bin/activate  # On Windows: openseries_env\Scripts\activate
-pip install openseries
-
-
-

Performance issues

-

For better performance with large datasets, consider installing optional accelerated packages:

-
pip install numba  # For numerical acceleration
-pip install bottleneck  # For faster pandas operations
-
-
-
-
-

Getting Help

-

If you encounter issues:

-
    -
  1. Check the GitHub Issues

  2. -
  3. Review the Release Notes

  4. -
  5. Create a new issue with a minimal reproducible example

  6. -
-
-
-
-

Platform-Specific Notes

-
-

Windows

-

On Windows, you may need to install Microsoft Visual C++ Build Tools if you encounter compilation errors with dependencies.

-
-
-

macOS

-

On macOS with Apple Silicon (M1/M2), all dependencies should install without issues. If you encounter problems, try using conda instead of pip.

-
-
-

Linux

-

Most Linux distributions should work without issues. On minimal installations, you may need to install additional system packages for some dependencies.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/build/html/user_guide/quickstart.html b/docs/build/html/user_guide/quickstart.html deleted file mode 100644 index 1d2071ef..00000000 --- a/docs/build/html/user_guide/quickstart.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - - - - - - Quick Start Guide — openseries 2.1.10 documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Quick Start Guide

-

This guide will get you up and running with openseries in just a few minutes.

-
-

Your First OpenTimeSeries

-

Let’s start by creating a simulated financial time series using openseries’ built-in simulation capabilities:

-
from openseries import OpenTimeSeries, ReturnSimulation, ValueType
-import datetime as dt
-
-# Create a simulated time series using lognormal distribution
-simulation = ReturnSimulation.from_lognormal(
-     number_of_sims=1,
-     trading_days=1000,
-     mean_annual_return=0.08,  # 8% annual return
-     mean_annual_vol=0.15,     # 15% annual volatility
-     trading_days_in_year=252,
-     seed=71
-)
-
-# Convert simulation to OpenTimeSeries
-sp500 = OpenTimeSeries.from_df(
-     dframe=simulation.to_dataframe(name="S&P 500", end=dt.date(2023, 12, 31)),
-     valuetype=ValueType.RTRN
-).to_cumret()  # Convert returns to cumulative prices
-
-sp500.set_new_label(lvl_zero="S&P 500")
-
-# Display basic information
-print(f"Series: {sp500.label}")
-print(f"Start date: {sp500.first_idx}")
-print(f"End date: {sp500.last_idx}")
-print(f"Number of observations: {sp500.length}")
-
-
-
-
-

Loading Data from External Sources

-

Alternatively, you can load data from external sources like yfinance:

-
import yfinance as yf  # pip install yfinance
-from openseries import OpenTimeSeries
-
-# Download S&P 500 data
-ticker = yf.Ticker("^GSPC")
-data = ticker.history(period="2y")
-
-# Create OpenTimeSeries from the Close prices
-sp500 = OpenTimeSeries.from_df(dframe=data['Close'])
-
-# Set a more descriptive label
-sp500.set_new_label(lvl_zero="S&P 500 Index")
-
-print(f"Loaded {sp500.length} observations")
-print(f"Date range: {sp500.first_idx} to {sp500.last_idx}")
-
-
-
-
-

Basic Financial Metrics

-

openseries provides a comprehensive set of financial metrics:

-
# Key performance metrics
-print(f"Total Return: {sp500.value_ret:.2%}")
-print(f"Annualized Return (CAGR): {sp500.geo_ret:.2%}")
-print(f"Annualized Volatility: {sp500.vol:.2%}")
-print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}")
-print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}")
-
-# Risk metrics
-print(f"95% VaR (daily): {sp500.var_down:.2%}")
-print(f"95% CVaR (daily): {sp500.cvar_down:.2%}")
-print(f"Sortino Ratio: {sp500.sortino_ratio:.2f}")
-
-# Distribution statistics
-print(f"Skewness: {sp500.skew:.2f}")
-print(f"Kurtosis: {sp500.kurtosis:.2f}")
-print(f"Positive Days: {sp500.positive_share:.1%}")
-
-
-
-

Get All Metrics at Once

-

Use the all_properties attribute to get a comprehensive overview:

-
# Get all metrics of an OpenTimeSeries or OpenFrame
-metrics = sp500.all_properties()
-print(metrics)
-
-
-
-
-
-

Creating Visualizations

-

openseries integrates with Plotly for interactive visualizations:

-
# Plot the timeseries
-sp500.plot_series()
-# This opens an interactive plot in your browser
-
-# Plot returns histogram
-returns = sp500.from_deepcopy()
-returns.value_to_ret()  # Convert to returns (modifies original)
-returns.plot_histogram()
-
-# Plot bar chart (useful for plotting returns)
-returns.plot_bars()
-
-# Plot drawdown series
-sp500.to_drawdown_series()  # Convert to drawdown (modifies original)
-sp500.plot_series()
-
-
-
-
-

Working with Multiple Assets (OpenFrame)

-

For multi-asset analysis, use the OpenFrame class:

-
from openseries import OpenFrame
-import yfinance as yf
-
-# Download data for multiple assets
-tickers = ["^GSPC", "^IXIC", "^RUT"]  # S&P 500, NASDAQ, Russell 2000
-names = ["S&P 500", "NASDAQ", "Russell 2000"]
-
-series_list = []
-for ticker, name in zip(tickers, names):
-     data = yf.Ticker(ticker).history(period="2y")
-     series = OpenTimeSeries.from_df(dframe=data['Close'])
-     series.set_new_label(lvl_zero=name)
-     series_list.append(series)
-
-# Create OpenFrame
-frame = OpenFrame(constituents=series_list)
-frame.value_nan_handle().trunc_frame()
-
-# Get metrics for all series
-all_metrics = frame.all_properties()
-print(all_metrics)
-
-# Calculate correlations
-correlations = frame.correl_matrix
-print("\nCorrelation Matrix:")
-print(correlations)
-
-
-
-
-

Portfolio Analysis

-

Create and analyze portfolios:

-
# Equal-weighted portfolio
-portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
-portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-
-print(f"Equal Weight Portfolio Return: {portfolio.geo_ret:.2%}")
-print(f"Equal Weight Portfolio Volatility: {portfolio.vol:.2%}")
-print(f"Equal Weight Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}")
-
-# Create custom weighted portfolio
-frame.weights = [0.8, 0.2]  # Custom allocation
-custom_df = frame.make_portfolio(name="Custom Portfolio")
-custom_portfolio = OpenTimeSeries.from_df(dframe=custom_df)
-print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}")
-
-# Compare with individual assets
-frame.add_timeseries(portfolio)
-frame.add_timeseries(custom_portfolio)
-comparison = frame.all_properties()
-print(comparison)
-
-
-
-
-

Data Transformations

-

openseries provides various data transformation methods:

-
# Convert prices to returns (modifies original)
-sp500.value_to_ret()
-print(f"Returns series length: {sp500.length}")
-
-# Convert to log returns (modifies original)
-sp500.value_to_log()
-
-# Calculate rolling statistics
-rolling_vol = sp500.rolling_vol(observations=30)  # 30-day rolling volatility
-rolling_ret = sp500.rolling_return(observations=30)  # 30-day rolling returns
-
-# Resample to monthly data (modifies original)
-sp500.resample_to_business_period_ends(freq="BME")
-print(f"Monthly data points: {sp500.length}")
-
-
-
-
-

Exporting Results

-

Save your analysis results:

-
# Export to Excel
-sp500.to_xlsx(filename="sp500_analysis.xlsx")
-
-# Export to JSON
-sp500.to_json(filename="sp500_data.json", what_output="tsdf")
-
-
-
-
-

Working with Business Days

-

openseries handles business day calendars automatically:

-
# Align to Swedish business days (modifies original)
-sp500.align_index_to_local_cdays(countries="SE")
-
-# Use multiple countries (modifies original)
-sp500.align_index_to_local_cdays(countries=["US", "GB"])
-
-# Handle missing values (modifies original)
-sp500.value_nan_handle()  # Forward fill NaN values
-
-
-
-
-

Next Steps

-

Now that you’ve learned the basics, explore:

-
    -
  1. Tutorials - Detailed examples for specific use cases

  2. -
  3. API Reference - Complete documentation of all methods and properties

  4. -
  5. Examples - Real-world analysis scenarios

  6. -
-
-
-

Key Concepts to Remember

-
    -
  • OpenTimeSeries: For single asset analysis

  • -
  • OpenFrame: For multi-asset and portfolio analysis

  • -
  • ValueType: Enum to identify data types (prices, returns, etc.)

  • -
  • Business day handling: Automatic alignment to trading calendars

  • -
  • Interactive plotting: Built-in Plotly integration

  • -
  • Type safety: Pydantic-based validation ensures data integrity

  • -
-
-
-

Common Patterns

-

Here are some common usage patterns:

-
# Pattern 1: Load, analyze, visualize
-series = OpenTimeSeries.from_df(dframe=data['Close'])
-series.set_new_label(lvl_zero="Asset")
-metrics = series.all_properties()
-series.plot_series()
-
-# Pattern 2: Multi-asset comparison
-frame = OpenFrame(constituents=[series1, series2, series3])
-comparison = frame.all_properties()
-correlations = frame.correl_matrix
-
-# Pattern 3: Portfolio construction (built-in strategies)
-portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
-portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
-frame.add_timeseries(portfolio)
-
-# Pattern 3b: Custom portfolio construction (create fresh frame)
-custom_frame = OpenFrame(constituents=[series1, series2, series3])
-custom_frame.weights = [0.4, 0.3, 0.3]
-custom_df = custom_frame.make_portfolio(name="Custom Portfolio")
-custom_portfolio = OpenTimeSeries.from_df(dframe=custom_df)
-
-# Pattern 4: Risk analysis
-risk_series = series.from_deepcopy()  # Create copy for risk analysis
-var_95 = risk_series.var_down  # VaR on returns
-max_dd = series.max_drawdown
-rolling_risk = risk_series.rolling_vol(observations=252)
-
-# Drawdown analysis (on original series)
-series.to_drawdown_series()  # Convert to drawdown (modifies original)
-
-
-

This should give you a solid foundation to start using openseries for your financial analysis needs!

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 64fc5dea..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -sphinx>=9.0.4 -sphinx-autobuild>=2025.8.25 -sphinx-autodoc-typehints>=3.6.0 -sphinx-rtd-theme>=3.1.0 diff --git a/docs/source/development/contributing.rst b/docs/source/development/contributing.rst index cfb3b523..273ce8b6 100644 --- a/docs/source/development/contributing.rst +++ b/docs/source/development/contributing.rst @@ -18,7 +18,7 @@ Development Setup cd openseries 3. Create the development environment. This installs the pinned uv version - (``uv==0.11.21``), syncs locked ``dev`` and ``docs`` dependencies from + (``uv==0.12.7``), syncs locked ``dev`` and ``docs`` dependencies from ``uv.lock``, and installs pre-commit hooks: .. code-block:: bash diff --git a/docs/source/user_guide/installation.rst b/docs/source/user_guide/installation.rst index 69f66f93..3213a087 100644 --- a/docs/source/user_guide/installation.rst +++ b/docs/source/user_guide/installation.rst @@ -119,7 +119,7 @@ Development Installation ------------------------ If you plan to contribute to openseries or need the development dependencies, -use the same pinned tooling as CI (``uv==0.11.21``): +use the same pinned tooling as CI (``uv==0.12.7``): .. code-block:: bash @@ -137,7 +137,7 @@ dependencies include: - **pytest-cov** (>=7.1.0) - Coverage plugin - **pytest-xdist** (>=3.8.0) - Parallel test runner - **mypy** (==2.1.0) - Static type checking -- **ruff** (==0.15.18) - Linting and formatting +- **ruff** (==0.16.5) - Linting and formatting - **pre-commit** (>=4.6.0) - Git hooks for code quality Troubleshooting diff --git a/make.ps1 b/make.ps1 index e4ab9018..020d284c 100644 --- a/make.ps1 +++ b/make.ps1 @@ -14,7 +14,7 @@ param ( $ErrorActionPreference = 'Stop' -$UV_VERSION = "0.11.21" +$UV_VERSION = "0.12.7" $PIP_AUDIT_VERSION = "2.10.0" # Ensure we run from repo root diff --git a/pyproject.toml b/pyproject.toml index e41dea98..23f9ec0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ dev = [ "pytest>=9.1.0", "pytest-cov>=7.1.0", "pytest-xdist>=3.8.0", - "ruff==0.15.18", + "ruff==0.16.5", "types-openpyxl>=3.1.2", "scipy-stubs>=1.14.1.0", "types-python-dateutil>=2.8.2", @@ -121,7 +121,7 @@ exclude = ["docs"] [tool.ruff.lint] select = ["ALL"] -ignore = ["COM812"] +ignore = ["COM812", "CPY001"] fixable = ["ALL"] pydocstyle = { convention = "google" } pylint = { max-args = 20, max-branches = 12, max-statements = 48 } diff --git a/scripts/ci-pr-paths.sh b/scripts/ci-pr-paths.sh new file mode 100755 index 00000000..1e25a727 --- /dev/null +++ b/scripts/ci-pr-paths.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "usage: $0 ..." >&2 + exit 2 +fi + +write_output() { + local value="$1" + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "run=${value}" >> "${GITHUB_OUTPUT}" + fi + echo "run=${value}" +} + +event="${GITHUB_EVENT_NAME:-}" +if [[ "${event}" != "pull_request" ]]; then + write_output true + exit 0 +fi + +if [[ -n "${CI_PR_PATHS_FILES:-}" ]]; then + files="${CI_PR_PATHS_FILES}" +else + token="${GH_TOKEN:-${GITHUB_TOKEN:-}}" + repo="${GITHUB_REPOSITORY:-}" + pr="${PR_NUMBER:-}" + if [[ -z "${token}" || -z "${repo}" || -z "${pr}" ]]; then + echo "ci-pr-paths: missing token or PR context; running job" >&2 + write_output true + exit 0 + fi + export GH_TOKEN="${token}" + files="$(gh api --paginate "repos/${repo}/pulls/${pr}/files" --jq '.[].filename')" || { + echo "ci-pr-paths: failed to list PR files; running job" >&2 + write_output true + exit 0 + } +fi + +while IFS= read -r file; do + [[ -z "${file}" ]] && continue + for pattern in "$@"; do + if [[ "${file}" == ${pattern} ]]; then + write_output true + exit 0 + fi + done +done <<< "${files}" + +write_output false diff --git a/scripts/run-zizmor.sh b/scripts/run-zizmor.sh index ff502aac..ecb6c52e 100755 --- a/scripts/run-zizmor.sh +++ b/scripts/run-zizmor.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -readonly ZIZMOR_VERSION=1.25.2 +readonly ZIZMOR_VERSION=1.29.0 if [[ -z "${GH_TOKEN:-}" && -z "${GITHUB_TOKEN:-}" ]]; then if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then diff --git a/tests/test_ci_pr_paths.py b/tests/test_ci_pr_paths.py new file mode 100644 index 00000000..ae6319db --- /dev/null +++ b/tests/test_ci_pr_paths.py @@ -0,0 +1,84 @@ +"""Test pull-request path filtering used by GitHub Actions.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +ROOT = Path(__file__).parent.parent +SCRIPT = ROOT / "scripts" / "ci-pr-paths.sh" + + +class CiPrPathsError(Exception): + """Raised when ci-pr-paths.sh does not behave as expected.""" + + +def _run( + *patterns: str, + env: dict[str, str], +) -> str: + bash = shutil.which("bash") + if bash is None: + msg = "bash executable not found" + raise CiPrPathsError(msg) + merged = os.environ.copy() + merged.pop("GITHUB_OUTPUT", None) + merged.update(env) + completed = subprocess.run( # noqa: S603 + [bash, str(SCRIPT), *patterns], + check=True, + capture_output=True, + cwd=ROOT, + env=merged, + text=True, + ) + return completed.stdout.strip() + + +class TestCiPrPaths: + """class to verify scripts/ci-pr-paths.sh output.""" + + def test_non_pull_request_always_runs(self: TestCiPrPaths) -> None: + """Test workflow_dispatch runs regardless of patterns.""" + output = _run("openseries/*", env={"GITHUB_EVENT_NAME": "workflow_dispatch"}) + if output != "run=true": + msg = f"expected run=true, got {output!r}" + raise CiPrPathsError(msg) + + def test_matching_python_path_runs(self: TestCiPrPaths) -> None: + """Test a Python source change matches openseries/*.""" + output = _run( + "openseries/*", + "tests/*", + env={ + "GITHUB_EVENT_NAME": "pull_request", + "CI_PR_PATHS_FILES": "README.md\nopenseries/series.py", + }, + ) + if output != "run=true": + msg = f"expected run=true, got {output!r}" + raise CiPrPathsError(msg) + + def test_docs_only_change_skips(self: TestCiPrPaths) -> None: + """Test a docs-only PR does not match Python test paths.""" + output = _run( + "openseries/*", + "tests/*", + "pyproject.toml", + env={ + "GITHUB_EVENT_NAME": "pull_request", + "CI_PR_PATHS_FILES": "docs/source/index.rst\ndocs/README.md", + }, + ) + if output != "run=false": + msg = f"expected run=false, got {output!r}" + raise CiPrPathsError(msg) + + def test_missing_pr_context_runs(self: TestCiPrPaths) -> None: + """Test a pull_request without API context fails open and runs.""" + output = _run("openseries/*", env={"GITHUB_EVENT_NAME": "pull_request"}) + if output != "run=true": + msg = f"expected run=true, got {output!r}" + raise CiPrPathsError(msg) diff --git a/tests/test_version_alignment.py b/tests/test_version_alignment.py index c68574cc..1a02df64 100644 --- a/tests/test_version_alignment.py +++ b/tests/test_version_alignment.py @@ -13,7 +13,6 @@ PRE_COMMIT_PATH = ROOT / ".pre-commit-config.yaml" MAKEFILE_PATH = ROOT / "Makefile" MAKE_PS1_PATH = ROOT / "make.ps1" -DOCS_REQUIREMENTS_PATH = ROOT / "docs" / "requirements.txt" INSTALLATION_RST_PATH = ROOT / "docs" / "source" / "user_guide" / "installation.rst" CONTRIBUTING_RST_PATH = ROOT / "docs" / "source" / "development" / "contributing.rst" PYTHON_VERSION_PATH = ROOT / ".python-version" @@ -28,6 +27,7 @@ "codeql.yml", "zizmor.yml", "supply-chain.yml", + "codecov.yml", ) MYPY_ADDITIONAL_DEPENDENCIES = ( @@ -258,23 +258,29 @@ def test_lockfile_matches_pyproject(self: TestVersionAlignment) -> None: ) raise VersionAlignmentError(msg) - def test_docs_requirements_match_docs_extra(self: TestVersionAlignment) -> None: - """Test docs/requirements.txt matches the pyproject docs extra.""" - pyproject = _load_toml(PYPROJECT_PATH) - expected = [ - item.replace(" ", "") - for item in pyproject["project"]["optional-dependencies"]["docs"] - ] - actual = [ - line.strip().replace(" ", "") - for line in _read_text(DOCS_REQUIREMENTS_PATH).splitlines() - if line.strip() and not line.strip().startswith("#") + def test_readthedocs_installs_docs_extra(self: TestVersionAlignment) -> None: + """Test Read the Docs installs the pyproject docs extra.""" + rtd = _read_text(ROOT / ".readthedocs.yaml") + extras_match = re.search( + r"extra_requirements:\n((?: - .+\n)+)", + rtd, + ) + if extras_match is None: + msg = ".readthedocs.yaml is missing extra_requirements" + raise VersionAlignmentError(msg) + extras = [ + line.strip()[2:].strip() + for line in extras_match.group(1).splitlines() + if line.strip().startswith("- ") ] - if actual != expected: - msg = ( - "docs/requirements.txt does not match pyproject docs extra: " - f"{actual} != {expected}" + if extras != ["docs"]: + _raise_mismatch( + ".readthedocs.yaml extra_requirements", + "docs", + ", ".join(extras), ) + if "docs/requirements.txt" in rtd: + msg = ".readthedocs.yaml must not install docs/requirements.txt" raise VersionAlignmentError(msg) def test_tool_versions_match(self: TestVersionAlignment) -> None: @@ -431,6 +437,36 @@ def test_docs_list_pyproject_specifiers(self: TestVersionAlignment) -> None: msg = f"{CONTRIBUTING_RST_PATH.name} is missing {uv_pin}" raise VersionAlignmentError(msg) + def test_codecov_reporting_is_master_only(self: TestVersionAlignment) -> None: + """Test Codecov uploads run only from the master coverage workflow.""" + tests = _read_text(WORKFLOW_DIR / "test.yml") + deploy = _read_text(WORKFLOW_DIR / "deploy.yml") + codecov = _read_text(WORKFLOW_DIR / "codecov.yml") + if "codecov/codecov-action" in tests: + msg = "test.yml must not upload to Codecov" + raise VersionAlignmentError(msg) + if "environment: codecov" in tests: + msg = "test.yml must not use the codecov environment" + raise VersionAlignmentError(msg) + if "codecov/codecov-action" in deploy: + msg = "deploy.yml must not upload to Codecov" + raise VersionAlignmentError(msg) + if "environment: codecov" in deploy: + msg = "deploy.yml must not use the codecov environment" + raise VersionAlignmentError(msg) + if "codecov/codecov-action" not in codecov: + msg = "codecov.yml must upload to Codecov" + raise VersionAlignmentError(msg) + if "environment: codecov" not in codecov: + msg = "codecov.yml must use the codecov environment" + raise VersionAlignmentError(msg) + if "branches:\n - master" not in codecov: + msg = "codecov.yml must run on push to master" + raise VersionAlignmentError(msg) + if "workflow_dispatch: {}" not in codecov: + msg = "codecov.yml must allow workflow_dispatch from master" + raise VersionAlignmentError(msg) + def test_python_versions_match(self: TestVersionAlignment) -> None: """Test declared Python versions match CI, docs, and tool targets.""" pyproject = _load_toml(PYPROJECT_PATH) diff --git a/uv.lock b/uv.lock index 4171ce81..2a1780e6 100644 --- a/uv.lock +++ b/uv.lock @@ -269,86 +269,86 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/66/edcec7d7a0b524aa8923e22925fde6fe50ce005a113dca13ae1581455c4c/coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490", size = 222367, upload-time = "2026-08-06T13:47:15.578Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c6/ab8de429e2e8548faf58ec7e1674a4ce00414b4113942d3fe87109cf0f68/coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e", size = 222874, upload-time = "2026-08-06T13:47:16.961Z" }, - { url = "https://files.pythonhosted.org/packages/be/c4/3b7b49587e8a6b9af79b3eb468d443d6042b6d65b47aa26586846a0d6566/coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7", size = 253287, upload-time = "2026-08-06T13:47:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/fb/65/ec03b743a2a229c72cc1eff3e57be9d3564e9c6b4d5aba2d70744a3fc0d8/coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6", size = 255199, upload-time = "2026-08-06T13:47:19.765Z" }, - { url = "https://files.pythonhosted.org/packages/41/4b/5163729e4b6582d61975cfd3ccab45b4ec53e21cf156d9941cb025188468/coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d", size = 257308, upload-time = "2026-08-06T13:47:21.206Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/2167a0f08fb87d702fa423a48578a32865464b7c9e1db3911ad7812ab414/coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce", size = 259268, upload-time = "2026-08-06T13:47:22.503Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e5/68eebae3053dbd48508edea559c21b23fbdf3460784f91370c83a86a6acd/coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7", size = 253392, upload-time = "2026-08-06T13:47:23.88Z" }, - { url = "https://files.pythonhosted.org/packages/1a/46/fd4ced40a2b691c774e515c9b69500bfa64c7960b67fcee4b2f6fad97fc3/coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b", size = 255001, upload-time = "2026-08-06T13:47:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/53/25/ae2e5fa710bb6957a9aadeb9e3598d3b3e4af6587ce857ad42e8639a3f30/coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc", size = 253061, upload-time = "2026-08-06T13:47:26.845Z" }, - { url = "https://files.pythonhosted.org/packages/d7/31/67ddc0365db2c6e93ac8580bc4bbc50f65273262f973f63ebcdbc15c0495/coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571", size = 256831, upload-time = "2026-08-06T13:47:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/f6/78/82b8fd18f57fb13f12d98fe874995bb2c4f9f17be8aff762c426323fdb96/coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719", size = 252781, upload-time = "2026-08-06T13:47:29.712Z" }, - { url = "https://files.pythonhosted.org/packages/0a/eb/6c74ef4dd12b252e573c49bdef9e2ac265bf3dbb79b8d7feb3266e084e9e/coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7", size = 253692, upload-time = "2026-08-06T13:47:31.192Z" }, - { url = "https://files.pythonhosted.org/packages/5a/66/eb9aed1c3fd2d36ee00eb173f434b14fa607fc056739c9a89ff4244010ea/coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e", size = 224461, upload-time = "2026-08-06T13:47:32.572Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6d/81fa4161dfb3ed9d74e40d58647eff83a56b7612e78352581280fce2f477/coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc", size = 224937, upload-time = "2026-08-06T13:47:34.205Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c1/d8dacf683c6cad3cf85ce68fd3774a6774ec402128822fdfaed920f11e6a/coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890", size = 224479, upload-time = "2026-08-06T13:47:36.118Z" }, - { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, - { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3f/f0642a372f494bd0d7dad3b497083b910194a5f1c88be2c94fef707c3b59/coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2", size = 257145, upload-time = "2026-08-06T13:47:41.931Z" }, - { url = "https://files.pythonhosted.org/packages/71/17/8b46d0ed68251016002ec972c8fc0119961a765d0984cafb8bf317c43758/coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931", size = 258257, upload-time = "2026-08-06T13:47:43.527Z" }, - { url = "https://files.pythonhosted.org/packages/30/b8/8498a0e72d0adbe15477dd07463d2b3bb2c9f6a4815e8589e50939e2c3ae/coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8", size = 260517, upload-time = "2026-08-06T13:47:45.121Z" }, - { url = "https://files.pythonhosted.org/packages/41/e1/7dce19c3bdb1e3dd63e769508216500edad81bd5f69a26d724e32aceaf78/coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9", size = 254785, upload-time = "2026-08-06T13:47:46.541Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b1/e1494703c675a2561723cd9b89f45c9168782c31280c611b1f767851e57c/coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839", size = 256176, upload-time = "2026-08-06T13:47:48.155Z" }, - { url = "https://files.pythonhosted.org/packages/73/76/a5629d270fb638a43a4b10466f51e2f49d532c1aa4da2913cbbb150bbe0a/coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72", size = 254321, upload-time = "2026-08-06T13:47:49.757Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/9c44447218435d5766b911534f9d798144a5560f85e9a54ebe5f3f5d19f9/coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52", size = 258390, upload-time = "2026-08-06T13:47:51.248Z" }, - { url = "https://files.pythonhosted.org/packages/de/36/c1e127616fb3fa18a9ff71e76c417f2fd7424332a4870015ac224ef4c039/coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c", size = 253894, upload-time = "2026-08-06T13:47:52.816Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b9/fdb92c8ae7a8bb9b850cc253b7b3b9c8526f68130002048b5671cd510d09/coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4", size = 255763, upload-time = "2026-08-06T13:47:54.296Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/a7d51b2587c7bdb76e71b0896d2565bf7d60436b5122fc83e511adb1f7cd/coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b", size = 224597, upload-time = "2026-08-06T13:47:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/49/b9/5c5f80cc55f5acaaca6dee677626bfcec8c87204a7809b438b08e84f4571/coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd", size = 225135, upload-time = "2026-08-06T13:47:57.52Z" }, - { url = "https://files.pythonhosted.org/packages/47/e4/2a4561f89ff6bf7c925c287d0f2cce8bdf139c3a33735c87e3203401cf94/coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f", size = 224515, upload-time = "2026-08-06T13:47:58.977Z" }, - { url = "https://files.pythonhosted.org/packages/f1/84/651a9310859673aaa3b3203f1aa1641ca60fcf2494683e1c9474c7172780/coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921", size = 222565, upload-time = "2026-08-06T13:48:00.796Z" }, - { url = "https://files.pythonhosted.org/packages/82/f9/4dcf700137e8af550670f4d74d1b63828ce93e1e2b05e5f10710eb2ea987/coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e", size = 222936, upload-time = "2026-08-06T13:48:02.391Z" }, - { url = "https://files.pythonhosted.org/packages/07/4a/612ff1e780b3fbfd637486f542f84adc5503873d8b5d279dec1ffeef9414/coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36", size = 253926, upload-time = "2026-08-06T13:48:04.382Z" }, - { url = "https://files.pythonhosted.org/packages/b0/04/d1cff1c2ead4708a6a79c01d3736b6a25bd38a36678398f72a8dd33dfad9/coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4", size = 256523, upload-time = "2026-08-06T13:48:05.996Z" }, - { url = "https://files.pythonhosted.org/packages/b9/80/d34e13fb4b293cbdb9665838cf5522077b8ad14ef947550631a4bced36a5/coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c", size = 257759, upload-time = "2026-08-06T13:48:08.036Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e7/2c5fe7636fdb0732fe0f09f308a5b066864078b7fc61f6678e8478554f2e/coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7", size = 259890, upload-time = "2026-08-06T13:48:09.834Z" }, - { url = "https://files.pythonhosted.org/packages/92/28/9689f0858dfff59c2ea688938ab9fa2925631235df67126a42b6c5c70ae1/coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25", size = 254121, upload-time = "2026-08-06T13:48:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e2/785077c230c157243eb5aa9a26c3be260ecd02001bead54a3cada3df8e03/coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b", size = 255891, upload-time = "2026-08-06T13:48:13.209Z" }, - { url = "https://files.pythonhosted.org/packages/d4/90/e20371b17b40f912f21305c2db2f30efa3de306f7320fc916804872c85a4/coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78", size = 253859, upload-time = "2026-08-06T13:48:14.736Z" }, - { url = "https://files.pythonhosted.org/packages/05/49/25371987ee459a5f67c0427fb75c74f9358e65f2c71fe75bf41c1b6c5fcb/coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f", size = 258011, upload-time = "2026-08-06T13:48:16.464Z" }, - { url = "https://files.pythonhosted.org/packages/30/6e/32e67467f6154bf4f1c4f63b05acc5097cba4237d45bbeeea446b52e8ac1/coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d", size = 253676, upload-time = "2026-08-06T13:48:18.493Z" }, - { url = "https://files.pythonhosted.org/packages/03/c1/8b24192e89286399765155251f99ee9f070a9d637109018ac23d99b99f6f/coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff", size = 255453, upload-time = "2026-08-06T13:48:20.057Z" }, - { url = "https://files.pythonhosted.org/packages/16/6f/8b41ebdf67c87854e17c035336a90f1cfbad0c14c2a584301be6ff148718/coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c", size = 224605, upload-time = "2026-08-06T13:48:21.655Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e2/2946c7f0b42b152ecb21ff1bdad72e3d301e790c0c487e4a86e8c9f69347/coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4", size = 225148, upload-time = "2026-08-06T13:48:23.376Z" }, - { url = "https://files.pythonhosted.org/packages/9e/83/3f4a69957f48ae7a0aba76c34743f88963d607b19e03f3f8e66f91cae0f9/coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf", size = 224536, upload-time = "2026-08-06T13:48:25.117Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ac/748cf29eeb2d6be34a3176ce26a4f49e38085ee08e8935f05f6f26ed7e0f/coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f", size = 222608, upload-time = "2026-08-06T13:48:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/0b/02/1abbf5c984677b0aa439cdacaccbf38d248939d8ef8fe1cc7a50d73edb77/coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c", size = 222940, upload-time = "2026-08-06T13:48:28.432Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e1/ff8f9f53d9fcf586125b55d0b1f04ec1c14955fee41e83d5814bee141bb5/coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082", size = 253985, upload-time = "2026-08-06T13:48:29.995Z" }, - { url = "https://files.pythonhosted.org/packages/a1/26/595759762e514e81be1d7d01ed03444303bcd152226a6529998d253f9201/coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac", size = 256492, upload-time = "2026-08-06T13:48:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/24/68/b79aabac54d482be23b5fcdd4f4662bff24a78edc4ee29201726929936d5/coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734", size = 257837, upload-time = "2026-08-06T13:48:33.186Z" }, - { url = "https://files.pythonhosted.org/packages/09/0f/bf7f297885a5bf6fd71e5782404e0ff059ca09e8711ceb3a08544abde45a/coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d", size = 260152, upload-time = "2026-08-06T13:48:34.75Z" }, - { url = "https://files.pythonhosted.org/packages/fd/f1/296744e854ff8368542343457414380465e9ceefb9192342feb9d3bc461d/coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf", size = 253978, upload-time = "2026-08-06T13:48:36.434Z" }, - { url = "https://files.pythonhosted.org/packages/55/b0/bbdb2e9057493e66220a2e149ca2d301ba0e3a58a83bd6b90de9826d16f3/coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b", size = 255846, upload-time = "2026-08-06T13:48:38.317Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/38015b2b6d21258713bd17e76b59d033b191efb5703589cffd037dfbca20/coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25", size = 253808, upload-time = "2026-08-06T13:48:39.993Z" }, - { url = "https://files.pythonhosted.org/packages/0b/64/0d515c1e60ee6fbfd1a0e79c07cd87d388a233b7adc37758735677203808/coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303", size = 258081, upload-time = "2026-08-06T13:48:41.971Z" }, - { url = "https://files.pythonhosted.org/packages/91/71/04d9e7a3642146c6351338aef4ef85ab11dbbb54744c13245caba1aad1c0/coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f", size = 253624, upload-time = "2026-08-06T13:48:43.731Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a7/6c28b74c81ebff66987b0e2522ba5cffa3e90b0c33cb6a2eb264d4ee8cf1/coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5", size = 255280, upload-time = "2026-08-06T13:48:45.58Z" }, - { url = "https://files.pythonhosted.org/packages/52/af/bc19996a7014b98d7bbb0f0939453c67074af65784a3aa16a789a07381fa/coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7", size = 224768, upload-time = "2026-08-06T13:48:47.525Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/219484e476d6e101ba0a444852579e05f5b75c37c611a42ed1190f73ef62/coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425", size = 225259, upload-time = "2026-08-06T13:48:49.513Z" }, - { url = "https://files.pythonhosted.org/packages/b7/66/fa77daf4e383e5f776dac62c2409b6af81910ae6fe326bd5170dba74cc63/coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8", size = 224684, upload-time = "2026-08-06T13:48:51.235Z" }, - { url = "https://files.pythonhosted.org/packages/58/5b/f03bf0ce362bbf3f785fa5219620d00778d4ac6fc9e407734828e9c672f6/coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8", size = 223338, upload-time = "2026-08-06T13:48:52.896Z" }, - { url = "https://files.pythonhosted.org/packages/0f/76/e77d0ae22501831cc9f92193e8a957a5caa1dd177f90a6d1d9b106242d92/coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a", size = 223609, upload-time = "2026-08-06T13:48:54.688Z" }, - { url = "https://files.pythonhosted.org/packages/82/1a/b1f089da8d38ac612fa2dd6dc7f4a1a7657d12f3e261d2996edd3a838d0b/coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b", size = 264970, upload-time = "2026-08-06T13:48:56.403Z" }, - { url = "https://files.pythonhosted.org/packages/bf/31/e66d98d6e9c7fcc88470f1e234eaf6b1950dc0dfbf797f7282c1c861da24/coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5", size = 267088, upload-time = "2026-08-06T13:48:58.41Z" }, - { url = "https://files.pythonhosted.org/packages/59/a1/ae94eb2c541add426378408379f233591e069040b1e2cdb33df9498a0682/coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba", size = 269508, upload-time = "2026-08-06T13:49:00.42Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c7/88a10694a1c6a213569766aba9f25847b28155d4ac731b13226db216356d/coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982", size = 270629, upload-time = "2026-08-06T13:49:02.234Z" }, - { url = "https://files.pythonhosted.org/packages/b3/34/d8b8232e5e55169933b59aabcef2fedfa4b9d8897361bb80fcbda146505f/coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c", size = 264043, upload-time = "2026-08-06T13:49:04.102Z" }, - { url = "https://files.pythonhosted.org/packages/7e/35/58b009dbf8c471c7224716478b9fed4a7e1af15320e1ed41660978504663/coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57", size = 266963, upload-time = "2026-08-06T13:49:05.821Z" }, - { url = "https://files.pythonhosted.org/packages/62/aa/57fbda1b42c892968273c56b6ee9dc0f1310850859230a507bc7873b1f65/coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26", size = 264569, upload-time = "2026-08-06T13:49:07.706Z" }, - { url = "https://files.pythonhosted.org/packages/98/8a/360e6e7f24d477b7e889703af0afa878d15b6d4d8d2a822b2835c169a879/coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429", size = 268299, upload-time = "2026-08-06T13:49:09.587Z" }, - { url = "https://files.pythonhosted.org/packages/4e/89/6f701261aee21b6b5fa8f7872229406dc917e125069448292223bf213606/coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017", size = 263413, upload-time = "2026-08-06T13:49:11.604Z" }, - { url = "https://files.pythonhosted.org/packages/3f/0f/6f04036edc260ed425af83e834f627fad48941ce97b50bfe6edd8b6fa623/coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839", size = 265725, upload-time = "2026-08-06T13:49:13.38Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ce/d19b5d4d5c49a7bfb925fd74310fee7d28bc99520ac3367ccbc54e662518/coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85", size = 225079, upload-time = "2026-08-06T13:49:15.265Z" }, - { url = "https://files.pythonhosted.org/packages/26/bb/7aa1b3b173faee0679037ca950bbbe1247273656697994d8d13f80f8d4b4/coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e", size = 225911, upload-time = "2026-08-06T13:49:17.279Z" }, - { url = "https://files.pythonhosted.org/packages/81/1c/4ea9e47426d80038d9222db3c4534cb6021a74b237d3ff97ffd33b6600dd/coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e", size = 225219, upload-time = "2026-08-06T13:49:19.293Z" }, - { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, +version = "7.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/f5/deb1a27aa20746c0278ac998c4179e272004699b2d33959ce020c5ac1615/coverage-7.16.0.tar.gz", hash = "sha256:077f0964087883176ff6ab9b074694cae29f8c708273b13ca62c183c6ed716cd", size = 945620, upload-time = "2026-08-28T21:54:37.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/d2/c76bf165ff01664ca8b1ca7f2b2b5f311353d3959dbac1187dd21c6cc7f8/coverage-7.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:22d8802827404be32f5a4d6ddc037f6fa0074b7d06702c0224cb598def8b665d", size = 223019, upload-time = "2026-08-28T21:51:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/16/7d/a47cebf71cb789b6e25de07035d350bff110d02f9c28bf32f92b4c818874/coverage-7.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a739bf08cdca0fad51b73322e4fade0102dd87794e278450b5ee87ef827954db", size = 223524, upload-time = "2026-08-28T21:51:03.632Z" }, + { url = "https://files.pythonhosted.org/packages/51/b3/42e46d7e247ba33758156a0cc88dc64715f7e7b04640fbe430c4da437ab1/coverage-7.16.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f99d12f8234c00b88b8077fedf288b25c77f746de312053b7db90fa756ecbdb3", size = 253934, upload-time = "2026-08-28T21:51:05.365Z" }, + { url = "https://files.pythonhosted.org/packages/9a/27/ade10badacc00076854f0c5086fcf8975bb1a379d5288b587509e6ee9763/coverage-7.16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7cae7715afa51dd7c9c42e6603bb46daf424c3449fdf06519cc658aa8d46e2e4", size = 255846, upload-time = "2026-08-28T21:51:06.922Z" }, + { url = "https://files.pythonhosted.org/packages/c5/50/38e5d8cf45af5db7419e9580bba4017113f8f1e2697cb6c52213bf7e7e40/coverage-7.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55957d350452017f523b9b03ffac078f9a214e23c04a3d0a674569203550c719", size = 257953, upload-time = "2026-08-28T21:51:08.51Z" }, + { url = "https://files.pythonhosted.org/packages/9b/bb/2f44b99723d0306095dacdf90f994631e299ff8f087a384b42ecc2d1ccb9/coverage-7.16.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b670bd5fa93d9b6855b2837217b45a90863118e2de5e9e033aebd46d07cd08d3", size = 259915, upload-time = "2026-08-28T21:51:10.155Z" }, + { url = "https://files.pythonhosted.org/packages/ab/7d/3f1c312944d88b2d3cae8af72007c15dcf5f92bda6da6d433c2d5f050ee7/coverage-7.16.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe5aa402d02318db2f41e471320b2ecca6085b8f595a034c037085732e49c04a", size = 254028, upload-time = "2026-08-28T21:51:11.845Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f6/52a7e26baeeca7f3114b15da5e840bebcfe6491eb234f6922d33c79ee8fc/coverage-7.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fddd26ed9a2527a7e23f7e4c1fd0734c4a5b45f77b261da1c536b20a7d2e6f0c", size = 255648, upload-time = "2026-08-28T21:51:13.614Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d1/0673e78d9ca29d56f663623791338647753c673f0bc964e860086da07bce/coverage-7.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b2af58ecdcec37fe633d4865fccbc8c00d8aa3b31c099bcacb2720c9a0be6ab9", size = 253708, upload-time = "2026-08-28T21:51:15.19Z" }, + { url = "https://files.pythonhosted.org/packages/6c/23/b74c87828369059415b20884b6f48260f049bff750d6eb454be8554732ab/coverage-7.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a3cd34b9025d62180ce2b5dae8a985bfa6cb8c05ecd57fd34ffc1ff751b5a74d", size = 257479, upload-time = "2026-08-28T21:51:16.988Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b4/09e172472c45a956e226dddf82d449f245764208b7cea47b32a73df955a3/coverage-7.16.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ebaf39dd13f8af65fe5f0316b81046228ef4d91d3c3766192b418753649896d6", size = 253428, upload-time = "2026-08-28T21:51:18.803Z" }, + { url = "https://files.pythonhosted.org/packages/62/22/e378e4f7ffa290ea4775b34e319fa182640bba650a2c6781af791b66b79a/coverage-7.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5dad64d9c17cb1983adef07998e6e2e1cf870a156f1ea80f81ce1970f4c545ce", size = 254337, upload-time = "2026-08-28T21:51:20.785Z" }, + { url = "https://files.pythonhosted.org/packages/51/6f/9a6ca653d86e46c3383a905f726a28bcf7bb2528088794d30a53687b381c/coverage-7.16.0-cp311-cp311-win32.whl", hash = "sha256:38b8e1e73750b8965d1154ed733f5303acd4e24ee2d5ee872bb1bfab744a31ce", size = 225103, upload-time = "2026-08-28T21:51:22.685Z" }, + { url = "https://files.pythonhosted.org/packages/08/0c/6d4627be89ac02f579d88806875a5d6e328c59d7d79c594643c7a4460ef6/coverage-7.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc12e5e32acdd62fe5895939695579560639853219288519685c75b7e968d63a", size = 225577, upload-time = "2026-08-28T21:51:24.334Z" }, + { url = "https://files.pythonhosted.org/packages/f2/3d/d7be38564d00a17775426685776b4bf18e8a6048a085eccf65d75eb0fa5a/coverage-7.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:17fc3628f99812fec24f40092af34c1c73274d331babab3d1d768a75de650cf7", size = 225126, upload-time = "2026-08-28T21:51:26.101Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9c/8d2688694f53dc0b0f0e4783c7eb3c4bb1e79beaf1411879f6dabedf4607/coverage-7.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1c77c3579ac42798f8b7eed6d3dd258debacca32c8753fc8a1f6eaf1db644f5", size = 223194, upload-time = "2026-08-28T21:51:27.767Z" }, + { url = "https://files.pythonhosted.org/packages/ca/11/f002163dd688aa3fa49ac6a424b7c2705c7fcf80fba18ec9f586d77827ca/coverage-7.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1f81cb1554c3712e41649ed5dc98656b50b958e4da12f0f5adb681ce3db92831", size = 223553, upload-time = "2026-08-28T21:51:29.46Z" }, + { url = "https://files.pythonhosted.org/packages/81/65/f9d469e97c4554372a710650a109004a2434dfc56f577142e5d6057fa0cc/coverage-7.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e701938ec9081d3e400a0c9a9a8ae0f7ca44214741daeac4454b1c6ef6dbd19", size = 255054, upload-time = "2026-08-28T21:51:31.54Z" }, + { url = "https://files.pythonhosted.org/packages/95/29/dd89fd39af1a3b6e9a9c3eddeaf03f6376ba517d43d6cbf8b519177e2a10/coverage-7.16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:719a3feb6220dd32ed932d4c3676d17fb8739e2643b29c0e7c3af400ff80ac44", size = 257790, upload-time = "2026-08-28T21:51:33.374Z" }, + { url = "https://files.pythonhosted.org/packages/0a/64/208d26cedc525d6b5db9c492cf9130784c42d9eb08d22badaa7b806005ad/coverage-7.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87771ecf986cff55e87413238cd5e4f54d949c2074bd6fc1657d26a56314ee24", size = 258904, upload-time = "2026-08-28T21:51:35.096Z" }, + { url = "https://files.pythonhosted.org/packages/1f/98/28e2752aa9a8baee5798edade9c95602ca200f4e7eeb503eb64df42e5921/coverage-7.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:47d5e1fc0b321c8308a2aacee0497c435b08acaa629b7059798fdf6fc3006352", size = 261165, upload-time = "2026-08-28T21:51:36.744Z" }, + { url = "https://files.pythonhosted.org/packages/eb/77/fa6ae699a0ea2bc12acb38a85d96b786fea0f833c12b5756056350e0e547/coverage-7.16.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01b18b8a6c9cec8d5f45550e2501426ed982cf2c35016b0acd2ba9b5d8b2fb06", size = 255416, upload-time = "2026-08-28T21:51:38.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/c8/5ee46d1de7d34cb00ba08b5c50da1971114dbc09ca9898ccc32975ec74dd/coverage-7.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:32c56b5b47c50635081445ac404dd08c2d591b9c837c22570aa9e182c3b42cd4", size = 256825, upload-time = "2026-08-28T21:51:40.27Z" }, + { url = "https://files.pythonhosted.org/packages/15/f6/d59e1c0693ad48855fe20169fbf6ee5befefe5887a7fabf5f0bcb464a2dc/coverage-7.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6ad3bbad240ab937512156bc944fdee63ac4dd34a7558a3094548fd4c1150c02", size = 254970, upload-time = "2026-08-28T21:51:43.136Z" }, + { url = "https://files.pythonhosted.org/packages/df/7b/b51bbe05b3a7565927fccfb1be42b8b3c1f4ab15e53d91b303e9923969aa/coverage-7.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4c1f16d5555a195295d0dc9c902612270e3dfed6a11f3bf7bc470b7b6a79ed3c", size = 259039, upload-time = "2026-08-28T21:51:44.983Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/d513f816456a8a43c1859abe88a37d01d7d2515b6c3e24ebb3c9b1dd44ec/coverage-7.16.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f6c9c21a8bf0d19788f3c5f3e020c90317a0a63ef60521b376003801e21250fb", size = 254539, upload-time = "2026-08-28T21:51:46.733Z" }, + { url = "https://files.pythonhosted.org/packages/dc/54/5542190ceb97e0d1333a4ce0c8f95b2ef2efe790f1ad018a4b61766f849e/coverage-7.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f20145a9eb5bf1fd1dde3c0bc2af2e7c22135ab07ca6284d6ada7cc3904c4e", size = 256410, upload-time = "2026-08-28T21:51:48.363Z" }, + { url = "https://files.pythonhosted.org/packages/ee/28/78643f361ff6bb5b2ade90f8bfc8395fe9ca367a18c101f8991215b4c65b/coverage-7.16.0-cp312-cp312-win32.whl", hash = "sha256:916cf8d25c1ce148f7eceb1d45afc9724841200110adc4e53250391852debd91", size = 225239, upload-time = "2026-08-28T21:51:50.22Z" }, + { url = "https://files.pythonhosted.org/packages/67/61/8e76b36c36b1a033dc933dd2480db96b04ce3be975793ce3fad122e7174d/coverage-7.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:78f8b56261d608be102c62edd3a60b66bcd0b581f3f86fdcabaf8b8d95adc950", size = 225775, upload-time = "2026-08-28T21:51:51.912Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f3/bb4787a4b81c1792ca69b502f5f730dbbb609f73fed552ab074c6b92cb8b/coverage-7.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:577c2ac8c0036f6f8edd3a7783a9e67302b17771d1abf0fd2ed246e3158be51b", size = 225159, upload-time = "2026-08-28T21:51:53.667Z" }, + { url = "https://files.pythonhosted.org/packages/54/c5/e62c87f4799d1e3647d5b2ae16ea1d12205d72fde1ea8529e13fe050f678/coverage-7.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1545c52ce756b8a97007f439a220297f1cd72a2cbbcdffccdf1c1f70e74f9a42", size = 223215, upload-time = "2026-08-28T21:51:55.628Z" }, + { url = "https://files.pythonhosted.org/packages/89/e9/5e62fda9397175fb206f75368b6e85da06d831c181b6d0f67ca073cd2f89/coverage-7.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0598aadae641f30a0796b75b45c0b9c5de8619bd5cfb251bb0cc254e86e6dd13", size = 223585, upload-time = "2026-08-28T21:51:57.355Z" }, + { url = "https://files.pythonhosted.org/packages/b9/40/bede08621b1ba67e88c4d3336c22b52cb7911ff1fa4ef055344b6670e58a/coverage-7.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4080ad6bad9f14690e6b2104f5e8d137ccc65a4b5427a36662090637d4bd16d5", size = 254575, upload-time = "2026-08-28T21:51:59.233Z" }, + { url = "https://files.pythonhosted.org/packages/12/d8/ab0bdaa45dfd6b8cbf1a3ec548fdf827684b1997f9724375c5b3e89144fb/coverage-7.16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e9883a2f8206ce3af59117dc278e5d043fea06912bca3f199816129e5e2de354", size = 257172, upload-time = "2026-08-28T21:52:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bb/135de81784bbd7dfedcab2b92b03d71d75b09b0815b42d6dabb052def5a6/coverage-7.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:984e5430fc6f858385009e92549955157d79335b1f3e13e1031e0f89d1284261", size = 258410, upload-time = "2026-08-28T21:52:02.76Z" }, + { url = "https://files.pythonhosted.org/packages/ad/72/ce44ecc062fb2e43d9447bb76154d091c2139232f20c125297c4b58f4c6a/coverage-7.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b1374099dd1ad0d31fbb6c95d00a56a3c5e85fb3343dca14fc12f78323a2b42a", size = 260539, upload-time = "2026-08-28T21:52:04.821Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/9389c36a41e59406ca2bba493807c2294d2e5186a7e9ebcc2e63a0f2a711/coverage-7.16.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34d8686bce035c8465b318a8c2890e69ba14a00801a27f4eb6bdc97c23944d87", size = 254756, upload-time = "2026-08-28T21:52:06.68Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0f/7762447b15e01fb84263608540123c4d9941f06303265ee74d801ccbec0e/coverage-7.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:857fceba6ff4b507ee0ad98798a33d544a8473df0c542bf04251ee4ed5ee6292", size = 256540, upload-time = "2026-08-28T21:52:08.529Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fa/c60dc75a8346c1dbebebc7279b19971c88f70dd575f0bc10bc0cb16f92d5/coverage-7.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bbf08d951abaa1ce89e28c998361d56b952413846b459cd017f116ad4c9adbfa", size = 254508, upload-time = "2026-08-28T21:52:10.323Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/4e0834f3a1fccaa8bf625a2a1d73bde0fa32577dc3249853c0dd0e7f2b20/coverage-7.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1a03e78f53e4d2ab13adac19958a89322d1829913e5623d642627bf60b35da21", size = 258659, upload-time = "2026-08-28T21:52:12.124Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ec/fe712d3a11fd6e874565a5fa5497c48b8ece561d9611da040b44cdcf8386/coverage-7.16.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:dcd3dafcdd78305d27c59a1006b53a4990acb89e68d8fbe0992f4f83503c827f", size = 254326, upload-time = "2026-08-28T21:52:14.181Z" }, + { url = "https://files.pythonhosted.org/packages/e7/78/093e12072e01034c65ff380f76c74b79dd83e44fa92b689a2154389be734/coverage-7.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c1bcfe470a796fbea6234accd81d258a31574dc0b7bf569e16be757572c4de17", size = 256102, upload-time = "2026-08-28T21:52:16.003Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c0/265176117ca5d06e3f65575842884cdda96cf213350a31e9d41c80d65854/coverage-7.16.0-cp313-cp313-win32.whl", hash = "sha256:1420370276f1694b663207b8245c3628aafb9624fe3cebf313a13d860e55ee67", size = 225250, upload-time = "2026-08-28T21:52:17.82Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/8a87f2c04fde322430b45d16d8f543693e9894c5b2d2ca238a287c00beca/coverage-7.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:496277c8d7beed695e02c7be53516a0152e4caef8738a0feab6a638546cce449", size = 225790, upload-time = "2026-08-28T21:52:19.641Z" }, + { url = "https://files.pythonhosted.org/packages/23/40/c21feacd9edfe7063195bf9cc84d650e9938fc6a23063e4f027199b160e1/coverage-7.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:181c2906b9b3759955c1c33c51fbb91c754fbd0b82ea49e2c81061f5a052082c", size = 225180, upload-time = "2026-08-28T21:52:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/850675f262391b322c4c988b6cdc32cdc6629288f0fb158687b587a393a8/coverage-7.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:54b7fba6a74d010de34319a0419d5b65af8c00f539ad0b6f39fc6f342ab99697", size = 223258, upload-time = "2026-08-28T21:52:23.558Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/4f54c6d47c80d1cc58ef8fe6b74e6eb50f9e2c0f6e2de6cf38dbca2937b8/coverage-7.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fa4ff0b3dd52208d2b30903022d5087f82000507b504753dfeee83e4f32d6883", size = 223587, upload-time = "2026-08-28T21:52:25.627Z" }, + { url = "https://files.pythonhosted.org/packages/3c/be/298f2456230fb44e272a4e53a41b3f3c39f0821c242d7b7daa9787b4d6f7/coverage-7.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:35a9676bf86097f790113ebd9fb67681804ef54d40941d2f10ba68c02239e575", size = 254632, upload-time = "2026-08-28T21:52:27.689Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9c/a1bda6439c19c4783d50df896142b67b9e7d432db36675d339a32778669d/coverage-7.16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f98d438add63546745e5e847192e3e9ab897ed6f2ca96f8281e2f5a15958ae62", size = 257139, upload-time = "2026-08-28T21:52:29.741Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cd/cd735c9be757f97237c305f36897a5e5b348bdbc12ebed3b2b80060dd8a9/coverage-7.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:151855767480be14db595cbc2040f6a4db965cdfeebd354d79b0256742b029e0", size = 258484, upload-time = "2026-08-28T21:52:31.68Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/84b2e1e8aae9db3f549782f28ce25bba5fd6a9c7bfba3782ffe8b4cd2559/coverage-7.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:183613f664718b340589d7f005c7e92b4b601cffd20a8a4117cfda3e983b080f", size = 260798, upload-time = "2026-08-28T21:52:33.642Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4f/e04cf52483619a4dc5dd6367b30c9a8ac52243567fdfacec9b11a441565c/coverage-7.16.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:785b114356c99c0dd5b3f57b9696cfd57b7704f4c53847df8dc88c6cc0d9bcb6", size = 254612, upload-time = "2026-08-28T21:52:35.543Z" }, + { url = "https://files.pythonhosted.org/packages/da/33/627c4113f66bfffd43807f54dbf080c4632ecf12e4ef7a3bdd4ec38e46a2/coverage-7.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:30f5aee6d1d517abcdfd4f9cad027969ff79a1440a22da263f9514e31b5b66e9", size = 256495, upload-time = "2026-08-28T21:52:37.485Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/aaca432f4e008a88f2bc4d1459aa7016d8d1bbbe801f7e4fa3cf2746557b/coverage-7.16.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:190ffa0f5af966254c249fb3aeaca2cef389785e3e287fd577d39e134d20f8a3", size = 254454, upload-time = "2026-08-28T21:52:39.425Z" }, + { url = "https://files.pythonhosted.org/packages/cc/db/8430aa87ef0a508f4c17c1b8fa7e0cf80231988d9081aa36c194036592d6/coverage-7.16.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0ccc37c00e1a5d30840902c54557e104d04aead872cedf6d2281c8725a467e06", size = 258728, upload-time = "2026-08-28T21:52:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/76/88/cd8aa8c82493ffbd291d3ef5554452fffc634c6c6098a04ac848c79c98f3/coverage-7.16.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6c60cde430c0e7e3be612973af39b4cff90ec2e2defe7b2b701daea3a0ffff04", size = 254271, upload-time = "2026-08-28T21:52:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/a8/49/fe16c811ea9314a84b48f34e4bf5a3d9013091093b285a74b2272fc863d7/coverage-7.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c5297028c8df849a61b29129cadfe682f90b5b396f528eb319a57d7678eefdad", size = 255927, upload-time = "2026-08-28T21:52:45.461Z" }, + { url = "https://files.pythonhosted.org/packages/d1/45/d0bd410e78cfbf768acc8099b335e1d5c0d5c26103c796d2bebdee001715/coverage-7.16.0-cp314-cp314-win32.whl", hash = "sha256:136988df5bc5a48795d9c42c75c4bbda5d9a78e750a080c1233010edff93a1af", size = 225424, upload-time = "2026-08-28T21:52:47.658Z" }, + { url = "https://files.pythonhosted.org/packages/17/78/1ce6ce4646822e9308dcdb1942eaf31bfd7da43247b8886338b0d6fe3767/coverage-7.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:ce2ba5e9f1842fe09165825abfb3bc6b527c71a27bc2eb3a10f2284ced64506d", size = 225918, upload-time = "2026-08-28T21:52:49.692Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cd/e1323fe3a7dfcdd709451a43fe708ca1dfd36a7fc07b34eb7bd1dfdfb52d/coverage-7.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a89d07e48d9baead9a15599923a02f62c6df6c3d85aa84ef34be3c9fd6aeb91f", size = 225344, upload-time = "2026-08-28T21:52:51.665Z" }, + { url = "https://files.pythonhosted.org/packages/39/fb/1c15460d4cf915f09ae3ad3862fef4f901838991c5641b0cec545050d810/coverage-7.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6e2854b62601c89a63814ad5def3b90d99c6724cc4cb977f75b725e5fca4b1e3", size = 223986, upload-time = "2026-08-28T21:52:53.572Z" }, + { url = "https://files.pythonhosted.org/packages/9f/73/347d2d0009ac211f79ee2a2364fd2aa19d6b9628dc22ed13a9b9386097ab/coverage-7.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f093faf23df888518d273be6da65f0ec5a25b5d8b670231e4d87de07361042e7", size = 224254, upload-time = "2026-08-28T21:52:55.59Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2f/51442e6ad9d705369596f08496021647e276d5b57311818fd4312d93509b/coverage-7.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b7dbbbf6551eb94618e7bc76ab61cc2740a5b3d13294171bd6adb36e12346c3c", size = 265619, upload-time = "2026-08-28T21:52:57.645Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8e/0f752276f6d13efbd019ab6d90792e20d6272c44cda039dc5c6d27b91e7f/coverage-7.16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51e7d0e311d2fba3915f971236cbdd4ad821fc7a23988221c0b33c964b0eba22", size = 267734, upload-time = "2026-08-28T21:52:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/fa/02/4df3baef8029881c9d1a380859f2be73f90080d430def567d182e8566a35/coverage-7.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bb04ee77e557d7476471969d35fbbfb5fc8a4152e9409aa5811780c36d9b23e", size = 270156, upload-time = "2026-08-28T21:53:01.658Z" }, + { url = "https://files.pythonhosted.org/packages/9f/30/ce10fdb74055ebbfb5c8a025d8845dc19c76e4b2c42bb5c755b56678990c/coverage-7.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c72c9b201dc0e8c2c8821d49858fd865010d08181bf877d2320971b6464ebfd5", size = 271279, upload-time = "2026-08-28T21:53:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/71/19/c7e1fc9504d90da848493bad4018dd235c713a80633e48c5f0a41b63d45e/coverage-7.16.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0fca700cae4635656668ba6e2b66a85aac9f2622d7b2bcf82e844c409eaa1313", size = 264677, upload-time = "2026-08-28T21:53:05.741Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f3/4021519dd41583ab396c81955387f927779641f6bac26818b6918a45aafc/coverage-7.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:584896fb8b650e999e24ef57e9513e482c12f8e15a73ee9d4584e23c99465867", size = 267610, upload-time = "2026-08-28T21:53:07.763Z" }, + { url = "https://files.pythonhosted.org/packages/55/fc/df65aac93938d8f506434c8e96440c1d696f6be0a6a01d3c6bfe5d49403e/coverage-7.16.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:949eae7e0f562b1518355aaef4b03523e49a6d3fea12aa3542d9e36c863f8267", size = 265217, upload-time = "2026-08-28T21:53:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/32/2d/dc9a5e62715165fcb4c715f965f411e324917c9daeddde16536e9d36ce3f/coverage-7.16.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:64f0611ee05364fc85cc3e5bc371804117a76fd337720e6017332fc7c534257a", size = 268948, upload-time = "2026-08-28T21:53:11.866Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4e/fe73a5560f25fca52acda76fc1554f30de081793ae4de97e920f8ab161d7/coverage-7.16.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:050a291b3cfe5e0df5999ef2fa5a7aff6e2db329f069d47eb63f02bde2e7e96b", size = 264061, upload-time = "2026-08-28T21:53:13.996Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f7/bb78cc4b97085ebbd77fa18cbc25abfab462814efa3e2363b4e50885c775/coverage-7.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a336b1e2990a64f5c356a9b8380fb9c029d56c832b801255250c44d603271bfd", size = 266371, upload-time = "2026-08-28T21:53:16.233Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ec/84b4af5cd4ad498477b3bfb2217e47b048da919451053790efda66f7383c/coverage-7.16.0-cp314-cp314t-win32.whl", hash = "sha256:058631257350b31784ed43ceb808298b6f074edf4ebca4c7ce5082e6bf873a61", size = 225736, upload-time = "2026-08-28T21:53:18.632Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/50fc0e6c675c3ef14895a74bab2d6120cb5d6f4b562a3d3f5046797758dc/coverage-7.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:ed35097438dfa980c1ec75bc83edf8acbe7a374d7007e571957a257fbd0e2fb3", size = 226570, upload-time = "2026-08-28T21:53:20.754Z" }, + { url = "https://files.pythonhosted.org/packages/fc/24/9effce7bcd3c6eeb4da3561905837509e582dcdde7a7f07d6ef2c8512f76/coverage-7.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0466f4a5c0370461b7d8c7eb259d7d1db0b5756f13d66230b04d22a1d380ee11", size = 225879, upload-time = "2026-08-28T21:53:22.747Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5a/234e8fadf85c3cc48cb31c247b9e8e0c7f06ece80f5b29f9b8c241f9da4c/coverage-7.16.0-py3-none-any.whl", hash = "sha256:245f7de6d023a5bba375dbec9f2e0869bfa26ac0cc639bbb7b4c814884000b73", size = 214977, upload-time = "2026-08-28T21:54:35.189Z" }, ] [package.optional-dependencies] @@ -896,7 +896,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/5f/29fd5f29b0a5d96e2def96ecba3112fc330ecd16e8c97c2b332563c5e201/numpy_typing_compat-20251206.2.4.tar.gz", hash = "sha256:59882d23aaff054a2536da80564012cdce33487657be4d79c5925bb8705fcabc", size = 5011, upload-time = "2025-12-06T20:02:04.942Z" } wheels = [ @@ -916,7 +916,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/db/5cd1d99caea4bf39fd477686ded4b9b70dff3c7673b5d84ef2d96a4f5aab/numpy_typing_compat-20260602.2.5.tar.gz", hash = "sha256:1885a678e9a24564839ed5d1711c0031735fb7de7f0b5ed88d550e5d45a8d4f9", size = 4593, upload-time = "2026-06-02T15:52:39.331Z" } wheels = [ @@ -997,7 +997,7 @@ requires-dist = [ { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.8.0" }, { name = "python-dateutil", specifier = ">=2.8.2" }, { name = "requests", specifier = ">=2.20.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.18" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.5" }, { name = "scikit-learn", specifier = ">=1.4.0" }, { name = "scipy", specifier = ">=1.14.1" }, { name = "scipy-stubs", marker = "extra == 'dev'", specifier = ">=1.14.1.0" }, @@ -1022,7 +1022,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/86/e6f1f6f3487492dfcf3b7a2d4e2534d27af6ac05b364b276706906c34865/optype-0.17.1.tar.gz", hash = "sha256:07bfa32b795dea28fba8605a6288d36370d072f25183fb9c29b5a90f4b6f5638", size = 53572, upload-time = "2026-05-17T22:13:28.725Z" } wheels = [ @@ -1031,8 +1031,8 @@ wheels = [ [package.optional-dependencies] numpy = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy-typing-compat", version = "20251206.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy-typing-compat", version = "20251206.2.4", source = { registry = "https://pypi.org/simple" } }, ] [[package]] @@ -1048,7 +1048,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version == '3.12.*'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/99/51/51dc9b1009e020f44703933d4d1ee3429c647c026ce7806b37ee2b257998/optype-0.18.0.tar.gz", hash = "sha256:ea10dee61b15ca299ed0d97025d362585c4dfc5481159bb999a1d0d414bbcb04", size = 59967, upload-time = "2026-06-07T22:13:17.534Z" } wheels = [ @@ -1057,8 +1057,8 @@ wheels = [ [package.optional-dependencies] numpy = [ - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "numpy-typing-compat", version = "20260602.2.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy-typing-compat", version = "20260602.2.5", source = { registry = "https://pypi.org/simple" } }, ] [[package]] @@ -1149,11 +1149,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.4" +version = "4.11.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/bb/ebc6636e1ae41314f796ebb7215fd28febb45f9aac72f2b04cb74b5071dc/platformdirs-4.11.4.tar.gz", hash = "sha256:f3373be828247211d0febabea97e238c3dfde8a60b3c90c32756fb52cb21556d", size = 34079, upload-time = "2026-08-24T14:53:49.676Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/06/cf1564dcc2e2261c8c8c6c05628dc8b418943bdae2a4e58640ceb2f770fa/platformdirs-4.11.5.tar.gz", hash = "sha256:e8b31f4f8bcbbedef91a6b57a706255e4f148d2a4e01648382a0a47342539173", size = 34823, upload-time = "2026-08-27T21:36:37.46Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/be/0ff05fcd2938fb58ad9219bd54135968342d214737e012d62d43f06a2dd6/platformdirs-4.11.4-py3-none-any.whl", hash = "sha256:e34ff91a24bcddc6d939b878bdf3f5c437c9c46fe9e212b1bf455fdf1ee57586", size = 23741, upload-time = "2026-08-24T14:53:48.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/12/6f3fcd5067a9cbf4f8664b32957973498da8b083455203c8d9cab83a725c/platformdirs-4.11.5-py3-none-any.whl", hash = "sha256:89f8d42695853b89c7170bd49bc3dc593f98a71e695ede88e06a3b247bc4563b", size = 23900, upload-time = "2026-08-27T21:36:36.227Z" }, ] [[package]] @@ -1196,7 +1196,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.13.4" +version = "2.13.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1204,111 +1204,111 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, ] [[package]] name = "pydantic-core" -version = "2.46.4" +version = "2.46.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, - { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, - { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, - { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, - { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, - { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, - { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, - { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, - { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, - { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, - { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, - { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, - { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, - { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, - { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, - { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, - { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, - { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, - { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, - { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, - { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, - { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, - { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, - { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, - { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, - { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, - { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737, upload-time = "2026-08-28T09:57:57.711Z" }, + { url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751, upload-time = "2026-08-28T09:57:59.265Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231, upload-time = "2026-08-28T09:58:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708, upload-time = "2026-08-28T09:58:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914, upload-time = "2026-08-28T09:58:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622, upload-time = "2026-08-28T09:58:05.516Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091, upload-time = "2026-08-28T09:58:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904, upload-time = "2026-08-28T09:58:08.814Z" }, + { url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244, upload-time = "2026-08-28T09:58:10.435Z" }, + { url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901, upload-time = "2026-08-28T09:58:12.033Z" }, + { url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425, upload-time = "2026-08-28T09:58:13.614Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566, upload-time = "2026-08-28T09:58:15.199Z" }, + { url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258, upload-time = "2026-08-28T09:58:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030, upload-time = "2026-08-28T09:58:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234, upload-time = "2026-08-28T09:58:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, + { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" }, + { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" }, + { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" }, + { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, + { url = "https://files.pythonhosted.org/packages/af/1e/ecca01fce348f7e8afa9572441ff6f7d1cc70d21e4859f33944d10877e1e/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2", size = 2075342, upload-time = "2026-08-28T10:00:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4c/af80c7a8032dfc897040ad5cb772bebde529a381186499e6e29987f23f8c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c", size = 1907219, upload-time = "2026-08-28T10:00:53.438Z" }, + { url = "https://files.pythonhosted.org/packages/be/3e/54d89e2b092e778716bf6153634ef479e955f48c261090be23aa1e0fb0b5/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47", size = 1953393, upload-time = "2026-08-28T10:00:55.58Z" }, + { url = "https://files.pythonhosted.org/packages/ea/89/828ee90cda28ce17bdefaa3a6eaf74fe430e113295a10e6126beca559d6c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a", size = 2099024, upload-time = "2026-08-28T10:00:57.794Z" }, + { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333, upload-time = "2026-08-28T10:01:09.636Z" }, + { url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713, upload-time = "2026-08-28T10:01:12.285Z" }, + { url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926, upload-time = "2026-08-28T10:01:15.19Z" }, + { url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303, upload-time = "2026-08-28T10:01:17.937Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128, upload-time = "2026-08-28T10:01:20.694Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560, upload-time = "2026-08-28T10:01:23.495Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531, upload-time = "2026-08-28T10:01:26.334Z" }, + { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" }, ] [[package]] @@ -1386,14 +1386,14 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.5.3" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/8f/3c92c45737f654f2488ab3662b7604a55d3d35146d37c9ce80f5c95b95a6/python_discovery-1.5.3.tar.gz", hash = "sha256:e500eb24025fb7c4876c1fdcfbafd9028a10c71b661aee38cb6fb0de594518c1", size = 82477, upload-time = "2026-08-24T14:48:46.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/96/0f93e27c9f60a650838f2118159aa115fd5732c0716247917b7ba7ede665/python_discovery-1.6.0.tar.gz", hash = "sha256:6393b4eae1be8b2182670635e7baff89ac21cb9f8e86fd1ff40c7b1144febb4c", size = 82849, upload-time = "2026-08-28T17:30:02.366Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/12/823d9a321904ccfd2969a24b84fdfd1e6614c707ec569c62879bf1dbc6c5/python_discovery-1.5.3-py3-none-any.whl", hash = "sha256:8305296358f1aa2ed302a25b84be7df84fef8ca47c7dce2da63cb7325333044e", size = 38290, upload-time = "2026-08-24T14:48:45.305Z" }, + { url = "https://files.pythonhosted.org/packages/43/5e/21abf578182fb15006a57faf3711a1e659e29d600d19b6e557eae908c81d/python_discovery-1.6.0-py3-none-any.whl", hash = "sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a", size = 38451, upload-time = "2026-08-28T17:30:01.236Z" }, ] [[package]] @@ -1477,27 +1477,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" }, - { url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" }, - { url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" }, - { url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" }, - { url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" }, - { url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" }, - { url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" }, - { url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" }, - { url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" }, +version = "0.16.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" }, + { url = "https://files.pythonhosted.org/packages/4b/46/6cf67cf6411885a1d6f7f6d801682f155536a85176d10b605e2ceffed8bd/ruff-0.16.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eda58a5802de40e7ed5b32b64e0b32539338cc6fcd2c78f61e3ad6a0d79f51c3", size = 10204049, upload-time = "2026-08-27T16:33:44.056Z" }, + { url = "https://files.pythonhosted.org/packages/46/fd/c8720ca7a090abf0c2fef4abe8a5ef6e5127ed15196d8886ff75a2b370e2/ruff-0.16.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ae9a7b9a8875131f40f8fe967cc86abf899779efd663cb7ce3d572d01da7eb", size = 9809037, upload-time = "2026-08-27T16:33:46.257Z" }, + { url = "https://files.pythonhosted.org/packages/43/45/a684caacdedaca180f52bacccc40bf0789d2c5a7c75f25324853e9eaedb5/ruff-0.16.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b719b0a1f4d59710d283ab2965f621684a108a9e41da622e3b23f0326cd0025", size = 9964129, upload-time = "2026-08-27T16:33:48.352Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/5d2bcdaca6b5b93d1b4dfc166cd2aebf7680143a1b38a28759df13a94d31/ruff-0.16.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2298f2780ed1be0c5cb1361e32ab7b1467f3cce7dabe101d2210a314f2fe42e9", size = 9821518, upload-time = "2026-08-27T16:33:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ff/011cce29accf9257d5974145b733fc653a37985ed6825413a3987cefbfe0/ruff-0.16.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:258f29035a2dd021e7861e631b227a5b3f14e50c1184c9a6a122c5f4576154d7", size = 10534835, upload-time = "2026-08-27T16:33:52.522Z" }, + { url = "https://files.pythonhosted.org/packages/d7/5a/f0cf109bada9bba0e96c90c21c9f9251803f57225c32d293327a03c710d6/ruff-0.16.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a4f0432966834019c74d1b7e5c51224305d7713f3d7faf3e7451f1a3be3cde", size = 11252550, upload-time = "2026-08-27T16:33:54.521Z" }, + { url = "https://files.pythonhosted.org/packages/63/4d/1d481aaea2046c6a7ed7c291f9004c669cce3c087b6b376ed5b08271e3fe/ruff-0.16.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5eb3a8c3d0ade9cea42b591fd530368e8798380e30e0a308b85a5cf718f09ea", size = 10777949, upload-time = "2026-08-27T16:33:56.88Z" }, + { url = "https://files.pythonhosted.org/packages/ee/34/ee245ca55f64443233034b3d02b03236b19242004281247c079390b7facd/ruff-0.16.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0f69e191a13a3c9816f63163c88790cb12cd157bbbb384e9c44745702ab105", size = 10311656, upload-time = "2026-08-27T16:33:59.12Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/c33a333e341c0a2b96c715b52d89a606f5a34cd4ac493cd9b8d0187186b8/ruff-0.16.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0eeab41fbea2c42f98dfb9822cdccda9d24ba38d49f6dc945b5c236d48f0ef29", size = 10532125, upload-time = "2026-08-27T16:34:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/a64cef78b40192497bb98a27a8aa8f2c98ee9ee15bc97f7712d94ef32937/ruff-0.16.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f0768e9df4300713fff30733c87575f68b6f1d8de41184e505b7fdd9c0c95eaf", size = 10097648, upload-time = "2026-08-27T16:34:03.16Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4e/4cdc9ed3c3e109d2f71e62572a37457298d7bc7501ec3138babb7ed32bbd/ruff-0.16.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:95cc70cdc7aa80c338de356279d2adbeb2de0f520b9ecd8aba75b94e95e02f91", size = 9829344, upload-time = "2026-08-27T16:34:05.134Z" }, + { url = "https://files.pythonhosted.org/packages/39/4a/31ed35ce31729955fc583ee0d176d6e784c1290cb0b0a75cb2134c1ab72a/ruff-0.16.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d185c8398ded1bfd91c0c2cb258346307571eccc473a8490af8c3977399c384a", size = 10277117, upload-time = "2026-08-27T16:34:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a0/60356d86687b4b666d593df213f4dc3041750d024cb7bf2cfa81cfd65c2e/ruff-0.16.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fb8e3a3c4c6a784150a7ced53b015f4b253fc2bf97a610886419ead64b4756ef", size = 10711653, upload-time = "2026-08-27T16:34:09.712Z" }, + { url = "https://files.pythonhosted.org/packages/ed/20/656d67f5b25ca9bda4e02b1de25867b2954e1d19e03648060f167ad0f4cc/ruff-0.16.5-py3-none-win32.whl", hash = "sha256:288b0a5f080492fe5635db849f9e2e84aa3cce7b7f0e955997d416c507c76a26", size = 10034250, upload-time = "2026-08-27T16:34:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/5b/42/ee8e68a207b9127fcde6c3d7e197def432f346cb1af159e1fa14ca0d1cdc/ruff-0.16.5-py3-none-win_amd64.whl", hash = "sha256:ddc6385fb2137f616357ca03d6c74f4be987f80fed4008566b754f6032b8546f", size = 10516714, upload-time = "2026-08-27T16:34:13.963Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" }, ] [[package]] @@ -1557,7 +1557,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -1636,7 +1636,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" } wheels = [ @@ -1692,7 +1692,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "optype", version = "0.17.1", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version < '3.12'" }, + { name = "optype", version = "0.17.1", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"] }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/30/7a2e621918d1317ab972f797161131f2635648ad5d92baf0695dd009e4f9/scipy_stubs-1.17.1.5.tar.gz", hash = "sha256:284b1dd1dd46107a614971d170030d310cd88b2ac6b483f85285ee0ff87720bd", size = 399933, upload-time = "2026-05-25T21:34:33.6Z" } wheels = [ @@ -1712,7 +1712,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "optype", version = "0.18.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.12'" }, + { name = "optype", version = "0.18.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"] }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/99/02c608a58cf99774c577f22e457427f87c8e608652c5de95178daa003e1f/scipy_stubs-1.18.1.0.tar.gz", hash = "sha256:87bec0df883cd9cd6b7dc4c74a33362cf550e9cad679643a29a886ab2b438ddc", size = 448822, upload-time = "2026-08-22T09:22:52.944Z" } wheels = [ @@ -1747,23 +1747,23 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.12'" }, - { name = "babel", marker = "python_full_version < '3.12'" }, - { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version < '3.12'" }, - { name = "imagesize", marker = "python_full_version < '3.12'" }, - { name = "jinja2", marker = "python_full_version < '3.12'" }, - { name = "packaging", marker = "python_full_version < '3.12'" }, - { name = "pygments", marker = "python_full_version < '3.12'" }, - { name = "requests", marker = "python_full_version < '3.12'" }, - { name = "roman-numerals", marker = "python_full_version < '3.12'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.12'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.12'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.12'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.12'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.12'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.12'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.12'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ @@ -1783,23 +1783,23 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.12'" }, - { name = "babel", marker = "python_full_version >= '3.12'" }, - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.12'" }, - { name = "imagesize", marker = "python_full_version >= '3.12'" }, - { name = "jinja2", marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "requests", marker = "python_full_version >= '3.12'" }, - { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ @@ -1834,7 +1834,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/1d/f6/bdd93582b2aaad2cfe9eb5695a44883c8bc44572dd3c351a947acbb13789/sphinx_autodoc_typehints-3.6.1.tar.gz", hash = "sha256:fa0b686ae1b85965116c88260e5e4b82faec3687c2e94d6a10f9b36c3743e2fe", size = 37563, upload-time = "2026-01-02T15:23:46.543Z" } wheels = [ @@ -1854,7 +1854,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/87/71b5530a4657d5dda36742907d6ba77c63db724a9099eda5e114a62016b4/sphinx_autodoc_typehints-3.13.4.tar.gz", hash = "sha256:9429680faa192fec9797edb9575923177f9d2ab277481d561d4cbd4aeb9cd472", size = 92020, upload-time = "2026-08-24T15:37:44.181Z" } wheels = [ @@ -2030,11 +2030,11 @@ wheels = [ [[package]] name = "types-openpyxl" -version = "3.1.5.20260807" +version = "3.1.5.20260827" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/89/e81814aac1c6ec46ee0006b723254ae737faa68c1ac80a7c3b81b3aa9f22/types_openpyxl-3.1.5.20260807.tar.gz", hash = "sha256:1a0a42b125f8023d3ae83cc057e379d301a87f45e60b6160917824fef28ab015", size = 101740, upload-time = "2026-08-07T04:17:25.557Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/6b/ce650ce7754a2bce3ca1dfbce7f7441df092e2dc6047d00e04f840c2b56e/types_openpyxl-3.1.5.20260827.tar.gz", hash = "sha256:be8b605fb99cfd7d5f5576d4a508e8ec44be2dd15b85157c559080de6384be34", size = 101985, upload-time = "2026-08-27T12:06:18.927Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/50/c7faba803c8a2e822ccc43a091c979cdbdc28e792e1eb0fb5ff172c81ee5/types_openpyxl-3.1.5.20260807-py3-none-any.whl", hash = "sha256:e64e9342cdac8a2d7b09f992d3606c532b75da43874f8107b6b5a122dc9d5681", size = 165826, upload-time = "2026-08-07T04:17:24.341Z" }, + { url = "https://files.pythonhosted.org/packages/12/23/9708c0895d237205ab2b31c06f97d72294f69789ff9923ff7f4aa2126d6b/types_openpyxl-3.1.5.20260827-py3-none-any.whl", hash = "sha256:94e176d871d12e3cbc34f8fb03dc14db2a4245a6690791daf16fc7b08fd67869", size = 165885, upload-time = "2026-08-27T12:06:17.88Z" }, ] [[package]] @@ -2112,7 +2112,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.7.5" +version = "21.7.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -2120,9 +2120,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/60/fc54e876e34f94dd0cf0185aaecfd4bfa906653f003d9b2fb21428642fca/virtualenv-21.7.5.tar.gz", hash = "sha256:a73c4246fba3c8901ff9717399f466e00eeca5a3834981f1a6ebb4f1e94de2f8", size = 5346743, upload-time = "2026-08-25T05:39:16.14Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/41/c3f34799487924f2a6f43b8a8b7acd345a6c61aac2211d4bced8621ca4f1/virtualenv-21.7.7.tar.gz", hash = "sha256:6874376f99ba6b8d4e3ee8bde67f9285412400c7d5b29ba41ee6daa5e0221bdc", size = 5347022, upload-time = "2026-08-28T18:59:50.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/d8/401141bf45637be916c86d325bd821c5838c7eff83294b934cd94e774e4f/virtualenv-21.7.5-py3-none-any.whl", hash = "sha256:e36ca889510ab6cb0b1dca93c59e5431dd4422a3c88f487358d470c90af8c07a", size = 5324697, upload-time = "2026-08-25T05:39:14.229Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/dcec8dc767b812193316c7debed1e2b53e586e59c267bfe517688ff90275/virtualenv-21.7.7-py3-none-any.whl", hash = "sha256:67a6a68fef3ad8ca16b8b89f33fd8f97996cc0bf0db31629d07ecf8dec539a2c", size = 5324620, upload-time = "2026-08-28T18:59:48.056Z" }, ] [[package]]